# frozen_string_literal: true

require 'norad_cli/support/results_server.rb'
require 'net/http'
require 'securerandom'
require 'rainbow'

module NoradCli
  class SecTestContainer
    attr_accessor :container
    attr_accessor :assessment_id
    attr_accessor :sectest_image
    attr_accessor :results_server
    attr_accessor :debug
    attr_reader   :sectest_options

    def initialize(sectest_name, registry, version, options)
      # Generate a random assessment id
      @assessment_id = SecureRandom.hex(32)

      @sectest_image = "#{registry}/#{sectest_name}:#{version}"

      # Set whether debugging is configured
      @debug = options[:debug]

      # Create a results server
      @results_server = NoradCli::ResultsServer.new('docker-images-test-results-server')

      ENV['ENABLE_LOGS'] = 'true'
      env = [
        'NORAD_ROOT=http://results:3000',
        %(ASSESSMENT_PATHS=[{"id":"singletest", "assessment": "/results/#{@assessment_id}"}]),
        'NORAD_SECRET=1234'
      ]

      # Create the container
      @container = Docker::Container.create(Image: @sectest_image,
                                            Cmd: prog_args(sectest_name, options),
                                            Env: env,
                                            HostConfig: { Links: ["#{@results_server.container.id}:results"] })
    end

    # Format the prog_args appropriately for the container
    def prog_args(sectest_name, options)
      # Grab the program arguments (minus other function options)
      # Options is a Thor::CoreExt::HashWithIndifferentAccess (except does not work)
      prog_arg_hash = options.each_with_object({}) do |(k, v), hsh|
        hsh[k.to_sym] = v unless k == 'debug'
      end

      # Load the prog_arg format
      @sectest_options ||= YAML.safe_load(File.read("sectests/#{sectest_name}/manifest.yml"))

      # Load an ssh key if necessary (default to Norad's key pair)
      prog_arg_hash[:ssh_user] = 'testuser' if !prog_arg_hash[:ssh_user] && progarg_present?('ssh_user')
      prog_arg_hash[:ssh_key] = load_ssh_key(prog_arg_hash[:ssh_key]) if progarg_present?('ssh_key')

      # Fill out the prog_args and return
      begin
        format(sectest_options['prog_args'], prog_arg_hash).split(' ')
      rescue KeyError
        puts Rainbow('Error: The containers required arguments were not set.').red
        puts Rainbow("Arguments in %{} should be set: #{sectest_options['prog_args']}").red
        puts Rainbow("Arguments given: #{prog_arg_hash}").red
        puts Rainbow("Run 'norad sectest execute #{sectest_name} -h' to see how to set arguments!").red
        puts Rainbow('Exiting...').red
        exit(1)
      end
    end

    def progarg_present?(key)
      sectest_options['prog_args'].include?("{#{key}}")
    end

    def start
      # Start the results server container
      @results_server.start

      # Start the sectest container
      @container.start
      @container.wait(60 * 10)
    end

    def output(target)
      # Output container logs for debugging
      @container.stop
      c_state = @container.json['State']

      # Print the entire state regardless of error or not to aid in debugging
      puts Rainbow("[DEBUG] Container #{@sectest_image}'s Final State").green
      puts Rainbow('-------------------------').green
      c_state.each do |key, value|
        puts Rainbow("#{key}: #{value}").green
      end

      puts Rainbow("\n[DEBUG] Logs for target #{@sectest_image} run against #{target}:").green

      # Print logs regardless of ExitCode
      puts Rainbow(@container.logs(stdout: true, stderr: true)).green
    end

    def results
      # Get the results
      url = "http://localhost:#{@results_server.host_port}/results?assessment_id=#{@assessment_id}"
      uri = URI.parse(url)
      http = Net::HTTP.new(uri.host, uri.port)
      request = Net::HTTP::Get.new(uri)
      response = http.request(request)
      if response.code == '200'
        response.body
      else
        puts Rainbow('Error retrieving results\nExiting...').red
        shutdown
        exit(1)
      end
    end

    def shutdown
      # Stop the sectest image container and delete
      @container.stop

      # Delete the container only if not in debug mode
      @container.delete(force: true) unless @debug

      # Cleanup/Garbage collect the results server
      @results_server.shutdown
    end

    private

    # Replace ssh key file with encoded ssh key
    def load_ssh_key(ssh_key_file)
      # Check for no ssh_key specified
      if !ssh_key_file
        Base64.strict_encode64(File.read(NoradCli.ssh_key_path))
      elsif File.exist?(ssh_key_file)
        Base64.strict_encode64(File.read(ssh_key_file))
      else
        puts Rainbow("Error: SSH Key file: #{ssh_key_file} does not exist!\nExiting..\n").red
        exit(1)
      end
    end
  end
end