# frozen_string_literal: true

require 'json'
require 'socket'
require 'resolv'

class IrkerWorker # rubocop:disable Scalability/IdempotentWorker
  include ApplicationWorker

  data_consistency :always
  sidekiq_options retry: 3
  feature_category :integrations
  urgency :low

  def perform(project_id, channels, colors, push_data, settings)
    # Establish connection to irker server
    return false unless start_connection(settings['server_host'],
                                         settings['server_port'])

    @project = Project.find(project_id)
    @colors = colors
    @channels = channels

    @repo_path = @project.full_path
    @repo_name = push_data['repository']['name']
    @committer = push_data['user_name']
    @branch = push_data['ref'].gsub(%r'refs/[^/]*/', '')

    if @colors
      @repo_name = "\x0304#{@repo_name}\x0f"
      @branch = "\x0305#{@branch}\x0f"
    end

    # First messages are for branch creation/deletion
    send_branch_updates(push_data)

    # Next messages are for commits
    send_commits(push_data)

    close_connection
    true
  end

  private

  def start_connection(irker_server, irker_port)
    ip_address = Resolv.getaddress(irker_server)
    # handle IP6 addresses
    domain = Resolv::IPv6::Regex.match?(ip_address) ? "[#{ip_address}]" : ip_address

    begin
      Gitlab::UrlBlocker.validate!(
        "irc://#{domain}",
        allow_localhost: allow_local_requests?,
        allow_local_network: allow_local_requests?,
        schemes: ['irc'])
      @socket = TCPSocket.new ip_address, irker_port
    rescue Errno::ECONNREFUSED, Gitlab::UrlBlocker::BlockedUrlError => e
      logger.fatal "Can't connect to Irker daemon: #{e}"
      return false
    end

    true
  end

  def allow_local_requests?
    Gitlab::CurrentSettings.allow_local_requests_from_web_hooks_and_services?
  end

  def send_to_irker(privmsg)
    to_send = { to: @channels, privmsg: privmsg }

    @socket.puts Gitlab::Json.dump(to_send)
  end

  def close_connection
    @socket.close
  end

  def send_branch_updates(push_data)
    message =
      if Gitlab::Git.blank_ref?(push_data['before'])
        new_branch_message
      elsif Gitlab::Git.blank_ref?(push_data['after'])
        delete_branch_message
      end

    send_to_irker(message)
  end

  def new_branch_message
    newbranch = "#{Gitlab.config.gitlab.url}/#{@repo_path}/-/branches"
    newbranch = "\x0302\x1f#{newbranch}\x0f" if @colors

    "[#{@repo_name}] #{@committer} has created a new branch #{@branch}: #{newbranch}"
  end

  def delete_branch_message
    "[#{@repo_name}] #{@committer} has deleted the branch #{@branch}"
  end

  def send_commits(push_data)
    return if push_data['total_commits_count'] == 0

    # Next message is for number of commit pushed, if any
    if Gitlab::Git.blank_ref?(push_data['before'])
      # Tweak on push_data["before"] in order to have a nice compare URL
      push_data['before'] = before_on_new_branch(push_data)
    end

    send_commits_count(push_data)

    # One message per commit, limited by 3 messages (same limit as the
    # github irc hook)
    commits = push_data['commits'].first(3)
    commits.each do |commit_attrs|
      send_one_commit(commit_attrs)
    end
  end

  def before_on_new_branch(push_data)
    commit = commit_from_id(push_data['commits'][0]['id'])
    parents = commit.parents

    # Return old value if there's no new one
    return push_data['before'] if parents.empty?

    # Or return the first parent-commit
    parents[0].id
  end

  def send_commits_count(push_data)
    url = compare_url(push_data['before'], push_data['after'])
    commits = colorize_commits(push_data['total_commits_count'])
    new_commits = 'new commit'.pluralize(push_data['total_commits_count'])

    send_to_irker("[#{@repo_name}] #{@committer} pushed #{commits} #{new_commits} " \
                  "to #{@branch}: #{url}")
  end

  def compare_url(sha_before, sha_after)
    sha1 = Commit.truncate_sha(sha_before)
    sha2 = Commit.truncate_sha(sha_after)
    compare_url = "#{Gitlab.config.gitlab.url}/#{@repo_path}/-/compare" \
                  "/#{sha1}...#{sha2}"

    colorize_url(compare_url)
  end

  def send_one_commit(commit_attrs)
    commit = commit_from_id(commit_attrs['id'])
    sha = colorize_sha(Commit.truncate_sha(commit_attrs['id']))
    author = commit_attrs['author']['name']
    files = colorize_nb_files(files_count(commit))
    title = commit.title

    send_to_irker("#{@repo_name}/#{@branch} #{sha} #{author} (#{files}): #{title}")
  end

  def commit_from_id(id)
    @project.commit(id)
  end

  def files_count(commit)
    diff_size = commit.raw_deltas.size

    "#{diff_size} file".pluralize(diff_size)
  end

  def colorize_sha(sha)
    sha = "\x0314#{sha}\x0f" if @colors
    sha
  end

  def colorize_nb_files(nb_files)
    nb_files = "\x0312#{nb_files}\x0f" if @colors
    nb_files
  end

  def colorize_url(url)
    url = "\x0302\x1f#{url}\x0f" if @colors
    url
  end

  def colorize_commits(commits)
    commits = "\x02#{commits}\x0f" if @colors
    commits
  end
end
