class LegacyDiffNote < Note
  serialize :st_diff

  validates :line_code, presence: true, line_code: true

  before_create :set_diff

  class << self
    def build_discussion_id(noteable_type, noteable_id, line_code, active = true)
      [super(noteable_type, noteable_id), line_code, active].join("-")
    end
  end

  def diff_note?
    true
  end

  def legacy_diff_note?
    true
  end

  def discussion_id
    @discussion_id ||= self.class.build_discussion_id(noteable_type, noteable_id || commit_id, line_code, active?)
  end

  def diff_file_hash
    line_code.split('_')[0] if line_code
  end

  def diff_old_line
    line_code.split('_')[1].to_i if line_code
  end

  def diff_new_line
    line_code.split('_')[2].to_i if line_code
  end

  def diff
    @diff ||= Gitlab::Git::Diff.new(st_diff) if st_diff.respond_to?(:map)
  end

  def diff_file_path
    diff.new_path.presence || diff.old_path
  end

  def diff_lines
    @diff_lines ||= Gitlab::Diff::Parser.new.parse(diff.diff.each_line)
  end

  def diff_line
    @diff_line ||= diff_lines.find { |line| generate_line_code(line) == self.line_code }
  end

  def diff_line_text
    diff_line.try(:text)
  end

  def diff_line_type
    diff_line.try(:type)
  end

  def highlighted_diff_lines
    Gitlab::Diff::Highlight.new(diff_lines).highlight
  end

  def truncated_diff_lines
    max_number_of_lines = 16
    prev_match_line = nil
    prev_lines = []

    highlighted_diff_lines.each do |line|
      if line.type == "match"
        prev_lines.clear
        prev_match_line = line
      else
        prev_lines << line

        break if generate_line_code(line) == self.line_code

        prev_lines.shift if prev_lines.length >= max_number_of_lines
      end
    end

    prev_lines
  end

  # Check if this note is part of an "active" discussion
  #
  # This will always return true for anything except MergeRequest noteables,
  # which have special logic.
  #
  # If the note's current diff cannot be matched in the MergeRequest's current
  # diff, it's considered inactive.
  def active?
    return @active if defined?(@active)
    return true if for_commit?
    return true unless self.diff
    return false unless noteable

    noteable_diff = find_noteable_diff

    if noteable_diff
      parsed_lines = Gitlab::Diff::Parser.new.parse(noteable_diff.diff.each_line)

      @active = parsed_lines.any? { |line_obj| line_obj.text == diff_line_text }
    else
      @active = false
    end

    @active
  end

  private

  def find_diff
    return nil unless noteable
    return @diff if defined?(@diff)

    @diff = noteable.diffs(Commit.max_diff_options).find do |d|
      d.new_path && Digest::SHA1.hexdigest(d.new_path) == diff_file_hash
    end
  end

  def set_diff
    # First lets find notes with same diff
    # before iterating over all mr diffs
    diff = diff_for_line_code unless for_merge_request?
    diff ||= find_diff

    self.st_diff = diff.to_hash if diff
  end

  def diff_for_line_code
    attributes = {
      noteable_type: noteable_type,
      line_code: line_code
    }

    if for_commit?
      attributes[:commit_id] = commit_id
    else
      attributes[:noteable_id] = noteable_id
    end

    self.class.where(attributes).last.try(:diff)
  end

  def generate_line_code(line)
    Gitlab::Diff::LineCode.generate(diff_file_path, line.new_pos, line.old_pos)
  end

  # Find the diff on noteable that matches our own
  def find_noteable_diff
    diffs = noteable.diffs(Commit.max_diff_options)
    diffs.find { |d| d.new_path == self.diff.new_path }
  end
end
