# frozen_string_literal: true

require 'securerandom'
require 'net/http'
require 'uri'
require 'forwardable'
require 'openssl'

module Gitlab
  module QA
    module Component
      class Gitlab < Base
        extend Forwardable

        attr_reader :release, :omnibus_configuration
        attr_accessor :tls, :skip_availability_check, :runner_network
        attr_writer :name, :relative_path

        def_delegators :release, :tag, :image, :edition

        AUTHORITY_CERTIFICATES_PATH = File.expand_path('../../../../tls_certificates/authority', __dir__)
        GITLAB_CERTIFICATES_PATH = File.expand_path('../../../../tls_certificates/gitlab', __dir__)
        GITALY_CERTIFICATES_PATH = File.expand_path('../../../../tls_certificates/gitaly', __dir__)

        SSL_PATH = '/etc/gitlab/ssl'
        TRUSTED_PATH = '/etc/gitlab/trusted-certs'

        def initialize
          super

          @skip_availability_check = false

          @volumes[GITLAB_CERTIFICATES_PATH] = SSL_PATH
          @volumes[AUTHORITY_CERTIFICATES_PATH] = TRUSTED_PATH

          @omnibus_configuration ||= OmnibusConfiguration.new

          self.release = 'CE'
        end

        def set_formless_login_token
          return if Runtime::Env.gitlab_qa_formless_login_token.to_s.strip.empty?

          @omnibus_configuration << "gitlab_rails['env'] = { 'GITLAB_QA_FORMLESS_LOGIN_TOKEN' => '#{Runtime::Env.gitlab_qa_formless_login_token}' }"
        end

        def elastic_url=(url)
          @environment['ELASTIC_URL'] = url
        end

        def release=(release)
          @release = QA::Release.new(release)
        end

        def name
          @name ||= "gitlab-#{edition}-#{SecureRandom.hex(4)}"
        end

        def address
          "#{scheme}://#{hostname}#{relative_path}"
        end

        def scheme
          tls ? 'https' : 'http'
        end

        def port
          tls ? '443' : '80'
        end

        def gitaly_tls
          @volumes.delete(GITLAB_CERTIFICATES_PATH)
          @volumes[GITALY_CERTIFICATES_PATH] = SSL_PATH
        end

        def relative_path
          @relative_path ||= ''
        end

        def set_accept_insecure_certs
          Runtime::Env.accept_insecure_certs = 'true'
        end

        def prepare
          prepare_gitlab_omnibus_config

          super
        end

        def pull
          docker.login(**release.login_params) if release.login_params

          super
        end

        def prepare_gitlab_omnibus_config
          set_formless_login_token
        end

        def start # rubocop:disable Metrics/AbcSize
          ensure_configured!

          docker.run(image: image, tag: tag) do |command|
            command << "-d -p #{port}"
            command << "--name #{name}"
            command << "--net #{network}"
            command << "--hostname #{hostname}"

            @volumes.to_h.each do |to, from|
              command.volume(to, from, 'Z')
            end

            command.volume(File.join(Runtime::Env.host_artifacts_dir, name, 'logs'), '/var/log/gitlab', 'Z')

            @environment.to_h.each do |key, value|
              command.env(key, value)
            end

            @network_aliases.to_a.each do |network_alias|
              command << "--network-alias #{network_alias}"
            end
          end
          Docker::Command.execute("network connect --alias #{name}.#{network} --alias #{name}.#{runner_network} #{runner_network} #{name}") if runner_network
        end

        def reconfigure
          setup_omnibus

          @docker.attach(name) do |line, wait|
            puts line
            # TODO, workaround which allows to detach from the container
            #
            break if line =~ /gitlab Reconfigured!/
          end
        end

        def wait_until_ready
          return if skip_availability_check

          if Availability.new(name, relative_path: relative_path, scheme: scheme, protocol_port: port.to_i).check(Runtime::Env.gitlab_availability_timeout)
            sleep 12 # TODO, handle that better
            puts ' -> GitLab is available.'
          else
            abort ' -> GitLab unavailable!'
          end
        end

        def sha_version
          json = @docker.read_file(
            @release.image, @release.tag,
            '/opt/gitlab/version-manifest.json'
          )

          manifest = JSON.parse(json)
          manifest['software']['gitlab-rails']['locked_version']
        end

        private

        def ensure_configured!
          raise 'Please configure an instance first!' unless [name, release, network].all?
        end

        def setup_omnibus
          @docker.write_files(name) do |f|
            f.write('/etc/gitlab/gitlab.rb', @omnibus_configuration.to_s)
          end
        end

        class Availability
          def initialize(name, relative_path: '', scheme: 'http', protocol_port: 80)
            @docker = Docker::Engine.new

            host = @docker.hostname
            port = @docker.port(name, protocol_port).split(':').last

            @uri = URI.join("#{scheme}://#{host}:#{port}", "#{relative_path}/", 'help')
          end

          def check(retries)
            print "Waiting for GitLab at `#{@uri}` to become available "

            retries.times do
              return true if service_available?

              print '.'
              sleep 1
            end

            false
          end

          private

          def service_available?
            response = Net::HTTP.start(@uri.host, @uri.port, opts) do |http|
              http.head2(@uri.request_uri)
            end

            response.code.to_i == 200
          rescue Errno::ECONNREFUSED, Errno::ECONNRESET, EOFError
            false
          end

          def opts
            @uri.scheme == 'https' ? { use_ssl: true, verify_mode: OpenSSL::SSL::VERIFY_NONE } : {}
          end
        end

        class OmnibusConfiguration
          def initialize
            @config = Runtime::Scenario.attributes[:omnibus_configuration].clone || []
          end

          def <<(configuration)
            @config << configuration
          end

          def to_s
            @config.to_s
          end
        end
      end
    end
  end
end