# frozen_string_literal: true

require 'English'
require 'socket'
require 'resolv'

class Environment
  attr_accessor :ctx

  def initialize(role, state)
    @ctx = {}

    @ctx['__ROLE__'] = role
    @ctx['__STATE__'] = state

    domain_suffix = '.example.com'
    @ctx['__DOMAIN_SUFFIX__'] = domain_suffix
    @ctx['__EXTERNAL_HOSTNAME__'] = "gitlab#{domain_suffix}"
    @ctx['__GEO_EXTERNAL_HOSTNAME__'] = "gitlab-geo#{domain_suffix}"

    hostname = Socket.gethostname
    site_a = 'site-a'
    site_b = 'site-b'

    # All hostnames are expected to have format "<site>sgl<host_type><host_id>".
    site, host_type, host_id = hostname.match(/^(#{site_a}|#{site_a})sgl(web|inf|sk|sql|git|mon)(0\d\d)$/)[1, 3]
    host_id = @ctx['__HOST_ID__'] = host_id.to_i
    site_short = @ctx['__SITE_SHORT__'] = site
    raise "ERROR: Invalid host format #{hostname}" if site.nil? || host_type.nil?

    # Determine which nodes are consul or redis. inf001..3 are consul, inf004..6 are redis
    if host_type == 'inf'
      host_type = host_id <= 3 ? 'consul' : 'redis'
    end
    @ctx['__HOST_TYPE__'] = host_type
    @ctx['__HOST_FQDN__'] = Addrinfo.getaddrinfo(hostname).first

    expected = hostname + domain_suffix
    if @ctx['__HOST_FQDN__'] != expected
      err = "ERROR: #{hostname}: Invalid fqdn setup #{@ctx['__HOST_FQDN__']}. Expecting #{expected}"
      warn err
    end

    # Get current nodes PUBLIC IP ADDRESS. Verify ip address matches with DNS.
    ip1 = UDPSocket.open do |s|
      s.connect('8.8.8.8', 1)
      s.addr.last
    end
    ip2 = IPSocket.getaddress(hostname)
    if ip1 != ip2
      err = "ERROR: #{hostname}: IP of public interface #{ip1} != #{ip2} - DNSIP for #{Socket.gethostname}"
      warn err
    end
    @ctx['__PUBLIC_IP_ADDRESS__'] = ip1

    # Setup MON node IPs.
    [1, 2].each do |i|
      @ctx["__MON#{i}_NODE_IP__"] = IPSocket.getaddress("#{site}sglmon00#{i}#{domain_suffix}")
    end

    other_site = (site == site_a.nil? ? site_b : site_a)
    @ctx['__GEO_NODE_NAME__'] = "gitlab#{other_site}"

    # Generate list expected hosts for each type of host
    @ctx['__SK_NODES__'] = (1..4).map { |i| "#{site}sglsk00#{i}#{domain_suffix}" }
    @ctx['__CONSUL_NODES__'] = (1..3).map { |i| "#{site}sglinf00#{i}#{domain_suffix}" }
    @ctx['__SQL_NODES__'] = (1..3).map { |i| "#{site}sglsql00#{i}#{domain_suffix}" }
    @ctx['__OTHER_SQL_NODES__'] = (1..3).map { |i| "#{other_site}sglsql00#{i}#{domain_suffix}" }
    @ctx['__REDIS_NODES__'] = [4, 5, 6].map { |i| "#{site}sglinf00#{i}#{domain_suffix}" }
    @ctx['__WEB_NODES__'] = (1..8).map { |i| "#{site}sglweb00#{i}#{domain_suffix}" }

    @ctx['__ALL_WEB_NODES__'] = [site_a, site_b].map do |s|
      (1..8).map do |i|
        "#{s}sglweb00#{i}#{domain_suffix}"
      end
    end.flatten
    @ctx['__ALL_SQL_NODES__'] = [site_a, site_b].map do |s|
      (1..3).map do |i|
        "#{s}sglsql00#{i}#{domain_suffix}"
      end
    end.flatten
    @ctx['__ALL_PGBOUNCER_NODES__'] = ["#{site_a}-", "#{site_b}-", ''].map do |s|
      "gitlab-#{s}pgbouncer#{domain_suffix}"
    end

    # Generate Gitaly DIRs.
    @ctx['__GITALY_DATA_DIRS_TLS__'] = {}
    %w[default repos2 repos3 repos4].each_with_index do |r, i|
      @ctx['__GITALY_DATA_DIRS_TLS__'][r] =
        { 'gitaly_address' => "tls://gitlab-#{site_short}-gitaly#{i + 1}#{domain_suffix}:9999" }
    end
    @ctx['__DB_TRACKING_HOST__'] = @ctx['__SQL_NODES__'][1]

    # Load passwords.
    encrypted_passwords = load_secrets # Load secrets that are needed in the gitlab.rb files (e.g. MD5 hashes, etc.)
    encrypted_passwords.each_key { |k| @ctx[k] = encrypted_passwords[k] }

    # Name of the gitaly repo.
    if host_type == 'git'
      @ctx['__GITALY_REPO_NAME__'] = host_id == 1 ? 'default' : "repos#{host_id}"
    end
    @ctx['__DMZ_PROXY__'] = "http://proxy#{domain_suffix}"
    @ctx['__NO_PROXY__'] = "#{domain_suffix},127.0.0.1,localhost"

    # Should include validation as part of the audit.
    return unless role == 'GEO'

    @ctx['__GEO_REPLICA_NODE__'] = @ctx['__SQL_NODES__'][0]
    @ctx['__GEO_TRACKING_NODE__'] = @ctx['__SQL_NODES__'][1]
  end

  private

  def vars_each
    @ctx.each do |k, v|
      yield k, v.inspect if k =~ /^__[A-Z0-9_]+__$/
    end
  end

  def reference_rb_file
    role = @ctx['__ROLE__'].downcase
    base_ref_file = "reference/#{@ctx['__HOST_TYPE__']}_template.rb"
    return base_ref_file if File.exist?(base_ref_file)

    ref_file = "reference/#{role}_#{@ctx['__HOST_TYPE__']}_template.rb"
    raise "Reference file #{ref_file} not found on host #{Socket.gethostname}" unless File.exist?(ref_file)

    ref_file
  end

  def redis_master_node_port
    @ctx['__WEB_NODES__'].each do |web_node|
      cmd = "ssh -q #{web_node} /opt/gitlab/bin/gitlab-ctl get-redis-master"
      cmd_out = `#{cmd}`

      if $CHILD_STATUS.exitstatus != 0
        warn "ERROR: getting redis-master from #{web_node} using cmd: #{cmd} - ExitStatus $?"
        next
      end

      m = cmd_out.match(/^Redis master found at host (\S+) listening on port (\d+)/)
      if m.nil?
        warn "ERROR: invalid ouptut from '#{cmd}': '#{cmd_out}'"
        next
      end

      ip, port = m[1, 2]
      name = Resolv.getname(ip)
      return name, port.to_i
    end

    raise 'ERROR: failed to find Redis master'
  end
end
