# == Schema Information
#
# Table name: issues
#
#  id                   :integer          not null, primary key
#  tracker_id           :integer          not null
#  project_id           :integer          not null
#  subject              :string(255)
#  description          :text(4294967295)
#  due_date             :date
#  category_id          :integer
#  status_id            :integer          not null
#  assigned_to_id       :integer
#  priority_id          :integer
#  fixed_version_id     :integer
#  author_id            :integer          not null
#  created_on           :datetime
#  updated_on           :datetime
#  start_date           :date
#  done_ratio           :integer          default("0"), not null
#  estimated_hours      :float(24)
#  parent_id            :integer
#  root_id              :integer
#  lft                  :integer
#  rgt                  :integer
#  is_private           :boolean          default("0"), not null
#  closed_on            :datetime
#  project_issues_index :integer
#  issue_type           :string(255)
#  token                :integer          default("0")
#  issue_tags_value     :string(255)
#  is_lock              :boolean          default("0")
#  issue_classify       :string(255)
#  ref_name             :string(255)
#  branch_name          :string(255)
#  blockchain_token_num :integer
#  pm_project_id        :integer
#  pm_sprint_id         :integer
#  pm_issue_type        :integer
#  time_scale           :decimal(10, 2)   default("0.00")
#  child_count          :integer          default("0")
#  changer_id           :integer
#
# Indexes
#
#  index_issues_on_assigned_to_id           (assigned_to_id)
#  index_issues_on_author_id                (author_id)
#  index_issues_on_category_id              (category_id)
#  index_issues_on_changer_id               (changer_id)
#  index_issues_on_created_on               (created_on)
#  index_issues_on_fixed_version_id         (fixed_version_id)
#  index_issues_on_priority_id              (priority_id)
#  index_issues_on_root_id_and_lft_and_rgt  (root_id,lft,rgt)
#  index_issues_on_status_id                (status_id)
#  index_issues_on_tracker_id               (tracker_id)
#  issues_project_id                        (project_id)
#

class Issue < ApplicationRecord
  #issue_type 1为普通，2为悬赏
  belongs_to :project, counter_cache: true, touch: true, optional: true
  include DunCheckAble
  belongs_to :tracker,optional: true
  has_many :project_trends, as: :trend, dependent: :destroy
  has_one :pull_request
  # belongs_to :issue_tag,optional: true
  belongs_to :priority, class_name: 'IssuePriority', foreign_key: :priority_id,optional: true
  belongs_to :version, foreign_key: :fixed_version_id,optional: true, counter_cache: true
  belongs_to :user,optional: true, foreign_key: :author_id
  belongs_to :issue_status, foreign_key: :status_id,optional: true
  belongs_to :parent_issue, class_name: 'Issue', optional: true, foreign_key: :root_id, touch: false, counter_cache: :child_count
  has_many :commit_issues
  has_many :attachments, as: :container, dependent: :destroy
  # has_many :memos
  has_many :journals, as: :journalized, dependent: :destroy
  has_many :journal_details, through: :journals
  has_many :claims, dependent: :destroy
  has_many :claim_users, through: :claims, source: :user
  has_many :issue_tags_relates, dependent: :destroy
  has_many :issue_tags, through: :issue_tags_relates
  has_many :issue_times, dependent: :destroy
  has_many :issue_depends, dependent: :destroy
  has_many :issue_assigners, dependent: :destroy
  has_many :assigners, through: :issue_assigners
  has_many :issue_participants, dependent: :destroy
  has_many :participants, through: :issue_participants
  has_many :children_issues, class_name: 'Issue', foreign_key: :root_id, dependent: :destroy
  has_many :show_participants, -> {joins(:issue_participants).where.not(issue_participants: {participant_type: 'atme'}).distinct}, through: :issue_participants, source: :participant
  has_many :show_assigners, -> {joins(:issue_assigners).distinct}, through: :issue_assigners, source: :assigner
  has_many :show_issue_tags, -> {joins(:issue_tags_relates).distinct}, through: :issue_tags_relates, source: :issue_tag

  has_many :comment_journals, -> {where.not(notes: nil)}, class_name: "Journal", :as => :journalized
  has_many :operate_journals, -> {where(notes: nil)}, class_name: "Journal", :as => :journalized
  has_many :pull_attached_issues, dependent: :destroy
  has_many :attach_pull_requests, through: :pull_attached_issues, source: :pull_request
  # PM 关联工作项目
  has_many :pm_links, as: :linkable, dependent: :destroy

  belongs_to :changer, class_name: 'User', foreign_key: :changer_id, optional: true

  scope :issue_includes, ->{includes(:user)}
  scope :issue_many_includes, ->{includes(journals: :user)}
  scope :issue_issue, ->{where(issue_classify: [nil, 'issue'])}
  scope :issue_pull_request, ->{where(issue_classify: 'pull_request')}
  scope :issue_index_includes, ->{includes(:tracker, :priority, :version, :issue_status, :journals,:issue_tags,user: :user_extension)}
  scope :pm_includes, -> {includes(:project, :show_issue_tags, :issue_status, :priority, :version, :user, :show_assigners, :comment_journals, :operate_journals)}
  scope :closed, ->{where(status_id: 5)}
  scope :opened, ->{where.not(status_id: 5)}
  scope :with_assigner_info, -> {
    select("DISTINCT issues.*, u.id AS user_id, CONVERT(COALESCE(u.nickname, u.login) USING gbk) AS user_name").
    joins("LEFT JOIN (
      SELECT ia.issue_id, ia.assigner_id
      FROM issue_assigners ia
      JOIN (
        SELECT issue_id, MIN(id) AS min_assigner_id
        FROM issue_assigners
        GROUP BY issue_id
      ) tmp ON ia.issue_id = tmp.issue_id AND ia.id = tmp.min_assigner_id
    ) assigner ON issues.id = assigner.issue_id
    LEFT JOIN users u ON assigner.assigner_id = u.id")
  }
  
  scope :ordered_by_assigner, -> (sort_direction) {
    order("CASE WHEN u.id IS NULL THEN 1 ELSE 0 END ASC")
    .order("CONVERT(COALESCE(u.nickname, u.login) USING gbk) #{sort_direction}")
  }
  after_create :incre_project_common, :incre_user_statistic, :incre_platform_statistic
  before_save :check_pm_and_update_due_date
  after_save :incre_or_decre_closed_issues_count, :change_versions_count, :send_update_message_to_notice_system, :associate_attachment_container, :generate_uuid
  after_destroy :update_closed_issues_count_in_project!, :decre_project_common, :decre_user_statistic, :decre_platform_statistic, :destroy_be_pm_links


  def pm_issue_type_string
    case pm_issue_type
    when 1
      "requirement"
    when 2
      "task"
    when 3
      "bug"
    else
      "issue"
    end
  end

  def pm_issue_type_chinese_string
    case pm_issue_type
    when 1
      "需求"
    when 2
      "任务"
    when 3
      "缺陷"
    else
      "疑修"
    end
  end


  def destroy_be_pm_links
    PmLink.where(be_linkable_type:"Issue",be_linkable_id:self.id).map(&:destroy)
  end

  def check_pm_and_update_due_date
    if pm_project_id.present? && pm_issue_type.present? && status_id_changed?
      status_ids = case pm_issue_type
                   when 1
                     [3,5]
                   when 2
                     [3,5]
                   when 3
                     [5]
                   else
                     []
                   end
      if status_ids.include? self.status_id
        self.due_date = self.due_date || Time.current
      end
    end
  end

  def is_issuely_issue?
    self.issue_classify.nil? || self.issue_classify == 'issue'
  end

  def incre_or_decre_closed_issues_count
    if previous_changes[:status_id].present? && is_issuely_issue?
      if previous_changes[:status_id][1] == 5
        CacheAsyncSetJob.perform_later("project_common_service", {closed_issues: 1}, self.project_id)
      end
      if previous_changes[:status_id][0] == 5
        CacheAsyncSetJob.perform_later("project_common_service", {closed_issues: -1}, self.project_id)
      end
    end
  end

  def incre_project_common
    CacheAsyncSetJob.perform_later('project_common_service', {issues: 1}, self.project_id) if is_issuely_issue?
  end

  def decre_project_common
    CacheAsyncSetJob.perform_later('project_common_service', {issues: -1}, self.project_id) if is_issuely_issue?
  end

  def incre_user_statistic 
    CacheAsyncSetJob.perform_later('user_statistic_service', {issue_count: 1}, self.author_id) if is_issuely_issue?
  end

  def decre_user_statistic
    CacheAsyncSetJob.perform_later('user_statistic_service', {issue_count: -1}, self.author_id) if is_issuely_issue?
  end

  def refresh_root_issue_count
    return if root_id.nil? || root_id.zero?
    root_issue = Issue.find_by(id: root_id)
    root_count = Issue.where(root_id: root_id).count
    root_issue.update(child_count: root_count)
  end

  def incre_platform_statistic
    CacheAsyncSetJob.perform_later('platform_statistic_service', {issue_count: 1}) if is_issuely_issue?
  end

  def decre_platform_statistic
    CacheAsyncSetJob.perform_later('platform_statistic_service', {issue_count: -1}) if is_issuely_issue?
  end

  def get_assign_user
    User&.find_by_id(self.assigned_to_id) if self.assigned_to_id.present?
  end

  def create_journal_detail(change_files, issue_files, issue_file_ids, user_id)
    journal_params = {
      journalized_id: self.id, journalized_type: 'Issue', user_id: user_id
    }
    journal = Journal.new journal_params

    if journal.save
      if change_files
        old_attachment_names = self.attachments.select(:filename,:id).where(id: issue_file_ids).pluck(:filename).join(',')
        new_attachment_name = self.attachments.select(:filename,:id).where(id: issue_files).pluck(:filename).join(',')
        journal.journal_details.create(property: 'attachment', prop_key: "#{issue_files.size}", old_value: old_attachment_names, value: new_attachment_name)
      end
      change_values = %w(subject description is_private assigned_to_id tracker_id status_id priority_id fixed_version_id start_date due_date estimated_hours done_ratio issue_tags_value issue_type token branch_name)
      change_values.each do |at|
        if self.send("saved_change_to_#{at}?")
          journal.journal_details.create(property: 'attr', prop_key: "#{at}", old_value: self.send("#{at}_before_last_save"), value: self.send(at))
        end
      end
    end
  end

  def custom_journal_detail(prop_key, old_value, value, user_id)
    journal_params = {
      journalized_id: self.id, journalized_type: 'Issue', user_id: user_id
    }
    journal = Journal.new journal_params
    if journal.save
      journal.journal_details.create(property: 'attr', prop_key: prop_key, old_value: old_value, value: value)
    end
  end

  def get_journals_size
    journals.size
  end

  def self.issues_count(tracker_id)
    includes(:trakcer).where(tracker_id: tracker_id).size
  end

  def get_issue_tags
    if issue_tags.present?
      issue_tags.select(:id,:name,:color).uniq.as_json
    else
      nil
    end
  end

  def generate_uuid
    # return if pm_project_id.nil?
    # attachments.map(&:generate_uuid)
  end

  def is_collaborators?
    if self.assigned_to_id.present? && self.project.present?
      self.project.member?(self.assigned_to_id)
    else
      false
    end
  end

  def get_issue_tags_name
    if issue_tags.present?
      issue_tags.select(:name).uniq.pluck(:name).join(',')
    else
      nil
    end
  end

  def only_reply_journals
    journals.where.not(notes: [nil, '']).journal_includes.limit(2)
  end

  def change_versions_count
    if self.saved_change_to_fixed_version_id?
      before_version = Version.find_by_id(self.fixed_version_id_before_last_save)
      Rails.logger.info self.fixed_version_id_before_last_save
      if before_version.present?
        Rails.logger.info self.status_id
        Rails.logger.info self.status_id_before_last_save
        # 更改前状态为完成 或者 更改前后都为完成状态
        if self.status_id_before_last_save == 5
          percent = before_version.issues_count == 0 ? 0.0 : ((before_version.closed_issues_count - 1).to_f / before_version.issues_count)
          before_version.update_attributes(closed_issues_count: (before_version.closed_issues_count - 1), percent: percent)
        end
      end
    end

    if self.version.present? && (self.saved_change_to_status_id? || self.saved_change_to_fixed_version_id?)
      if self.status_id == 5
        percent = self.version.issues_count == 0 ? 0.0 : ((self.version.closed_issues_count + 1).to_f / self.version.issues_count)
        self.version.update_attributes(closed_issues_count: (self.version.closed_issues_count + 1), percent: percent)
      elsif self.status_id_before_last_save == 5 && !self.saved_change_to_fixed_version_id?
        percent = self.version.issues_count == 0 ? 0.0 : ((self.version.closed_issues_count - 1).to_f / self.version.issues_count)
        self.version.update_attributes(closed_issues_count: (self.version.closed_issues_count - 1), percent: percent)
      end
    end
  end

  def update_closed_issues_count_in_project!
    self.project.decrement!(:closed_issues_count) if self.status_id == 5 && self.project.present?
  end

  # def self.find_issues_by_pm(enterprise_identifier, pm_issue_type)
  #   case pm_issue_type.to_i
  #   when 1
  #     Issue.issue_issue.where(enterprise_identifier: enterprise_identifier)
  #          .where(pm_issue_type: pm_issue_type)
  #          .where(pm_issue_type_index1: index)
  #   when 2
  #     Issue.issue_issue.where(enterprise_identifier: enterprise_identifier)
  #          .where(pm_issue_type: pm_issue_type)
  #          .where(pm_issue_type_index2: index)
  #   when 3
  #     Issue.issue_issue.where(enterprise_identifier: enterprise_identifier)
  #          .where(pm_issue_type: pm_issue_type)
  #          .where(pm_issue_type_index3: index)
  #   end
  # end
  #
  # def self.build_pm_index(enterprise_identifier, pm_issue_type)
  #   last_issue = Issue.find_issues_by_pm(enterprise_identifier, pm_issue_type).order("pm_issue_type_index#{pm_issue_type.to_i} asc").last
  #   deleted_issue_count = ($redis_cache.hget("pm_issue_cache_delete_count", enterprise_identifier) || 0).to_i
  #
  #   last_issue.send("pm_issue_type_index#{pm_issue_type.to_i}").present? ? last_issue.send("pm_issue_type_index#{pm_issue_type.to_i}") + deleted_issue_count : 0
  # end
  #
  # def incre_pm_issue_cache_delete_count(count=1)
  #   $redis_cache.hincrby("pm_issue_cache_delete_count", self.enterprise_identifier, count)
  # end
  #
  # def del_pm_issue_cache_delete_count
  #   $redis_cache.hdel("pm_issue_cache_delete_count", self.enterprise_identifier)
  # end

  def send_update_message_to_notice_system
    SendTemplateMessageJob.perform_later('IssueExpire', self.id) if Site.has_notice_menu? && self.due_date == Date.today + 1.days
  end

  # 关附件到功能
  def associate_attachment_container
    return if self.project_id == 0
    att_ids = []
    # 附件的格式为（/api/attachments/ + 附件id）的形式，提取出id进行附件属性关联，做附件访问权限控制
    att_ids += self.description.to_s.scan(/\(\/api\/attachments\/.+\)/).map{|s|s.match(/\d+/)[0]}
    att_ids += self.description.to_s.scan(/\/api\/attachments\/.+\"/).map{|s|s.match(/\d+/)[0]}
    att_ids += self.description.to_s.scan(/\/api\/attachments\/\d+/).map{|s|s.match(/\d+/)[0]}
    if att_ids.present?
      Attachment.where(id: att_ids).where("container_type IS NULL OR container_type = 'Issue'").update_all(container_id: self.project_id, container_type: 'Project')
    end

    att_ids2 = []
    # uuid_regex= /[0-9a-fA-F]{8}-[0-9a-fA-F]{4}-[0-9a-fA-F]{4}-[0-9a-fA-F]{4}-[0-9a-fA-F]{12}/
    # 附件的格式为（/api/attachments/ + uuid）的形式，提取出id进行附件属性关联，做附件访问权限控制
    att_ids2 += self.description.to_s.scan(/\(\/api\/attachments\/[0-9a-fA-F]{8}-[0-9a-fA-F]{4}-[0-9a-fA-F]{4}-[0-9a-fA-F]{4}-[0-9a-fA-F]{12}\)/).map{|s|s.match(/[0-9a-fA-F]{8}-[0-9a-fA-F]{4}-[0-9a-fA-F]{4}-[0-9a-fA-F]{4}-[0-9a-fA-F]{12}/)[0]}
    att_ids2 += self.description.to_s.scan(/\/api\/attachments\/[0-9a-fA-F]{8}-[0-9a-fA-F]{4}-[0-9a-fA-F]{4}-[0-9a-fA-F]{4}-[0-9a-fA-F]{12}/).map{|s|s.match(/[0-9a-fA-F]{8}-[0-9a-fA-F]{4}-[0-9a-fA-F]{4}-[0-9a-fA-F]{4}-[0-9a-fA-F]{12}/)[0]}
    if att_ids2.present?
      Attachment.where(uuid: att_ids2).where("container_type IS NULL OR container_type = 'Issue'").update_all(container_id: self.project_id, container_type: 'Project')
    end
  end

  def to_builder
    Jbuilder.new do |issue|
      issue.(self, :id, :project_issues_index, :subject, :description, :branch_name, :start_date, :due_date)
      issue.created_at self.created_on.strftime('%Y-%m-%d %H:%M')
      issue.updated_at self.updated_on.strftime('%Y-%m-%d %H:%M')
      issue.tags self.show_issue_tags.map{|t| JSON.parse(t.to_builder.target!)}
      issue.status self.issue_status.to_builder
      if self.priority.present?
        issue.priority self.priority.to_builder
      else
        issue.priority nil
      end
      if self.version.present?
        issue.milestone self.version.to_builder
      else
        issue.milestone nil
      end
      issue.author self.user.to_builder
      issue.assigners self.show_assigners.map{|t| JSON.parse(t.to_builder.target!)}
      issue.participants self.participants.distinct.map{|t| JSON.parse(t.to_builder.target!)}
      issue.comment_journals_count self.comment_journals.size
      issue.operate_journals_count self.operate_journals.size
      issue.attachments self.attachments.map{|t| JSON.parse(t.to_builder.target!)}
    end
  end

  def self.full_children_issues(issue, issues = [])
    issue.children_issues.each do |i|
      issues << i
      full_children_issues(i, issues)
    end
    issues
  end

end
