# Copyright (c) 2023 Contrast Security, Inc. See https://www.contrastsecurity.com/enduser-terms-0317a for more details.
# frozen_string_literal: true

require 'yaml'
require 'contrast/configuration'
require 'contrast/agent/reporting/reporter'
require 'base64'

module Contrast
  module Config
    # Helper module with methods to validate the existing yaml file.
    module Validate
      # rubocop:disable Rails/Output
      SKIP_LOG = %w[service_key api_key].cs__freeze
      REQUIRED = %i[url api_key service_key user_name].cs__freeze
      PASS = " \u{2705}  "
      FAIL = " \u{274C}  "
      REQUIRED_HEADERS = %i[server_type server_name server_path app_language app_name].cs__freeze

      def validate_file
        puts("\u{1F9EA} Validating Agent Configuration...\n")
        Contrast::Config.validate_config
        puts('done...')
        puts("Validating Contrast Reporter Headers...\n")
        reporter = Contrast::Config.validate_headers
        puts('done...')
        puts("Testing Reporter Client Connection...\n")
        Contrast::Config.test_connection(reporter) if reporter
        puts('Validation complete')
      end

      def validate_config
        config = Contrast::Configuration.new
        abort("Unable to Build Config #{ FAIL }") unless config
        missing = []

        api_hash = config.api.to_contrast_hash

        api_hash.each_key do |key|
          value = mask_keys(api_hash, key)
          if value.is_a?(Contrast::Config::ApiProxyConfiguration)
            Contrast::Config.validate_proxy(value)
          elsif value.is_a?(Contrast::Config::CertificationConfiguration)
            Contrast::Config.validate_cert(value)
            next
          elsif value.is_a?(Contrast::Config::RequestAuditConfiguration)
            Contrast::Config.validate_audit(value)
            next
          elsif value.nil? && REQUIRED.include?(key.to_sym)
            missing << key
          end
        end
        return if missing.empty?

        abort("Validation failed: Missing required API configuration values: #{ missing.join(', ') } #{ FAIL }")
      end

      def validate_proxy config
        puts("  Proxy Enabled: #{ config.enable }")
        return unless config.enable

        mark = config.url.nil? ? FAIL : PASS
        puts("  Proxy URL: #{ config.url } #{ mark }")
        abort('Proxy Enabled but no Proxy URL given') unless config.url
      end

      def validate_cert config
        puts("  Certification Enabled: #{ config.enable }")
        return unless config.enable

        mark = config.ca_file.nil? ? FAIL : PASS
        puts("  CA File: #{ config.ca_file } #{ mark }")
        abort('CA file path not provided') unless config.ca_file
        mark = config.cert_file.nil? ? FAIL : PASS
        puts("  Cert File: #{ config.cert_file } #{ mark }")
        abort('Cert file path not provided') unless config.cert_file
        mark = config.key_file.nil? ? FAIL : PASS
        puts("  Key File: #{ config.key_file } #{ mark }")
        abort('Key file path not provided') unless config.key_file
      end

      def validate_audit config
        puts("  Request Audit Enabled: #{ config.enable }")
        return unless config.enable

        config.cs__class::CONFIG_VALUES.each do |value_name|
          puts("  #{ value_name }::#{ config.send(value_name.to_sym) }")
        end
      end

      def init_reporter
        Contrast::Agent::Reporter.new
      end

      def validate_headers
        missing = []
        reporter = init_reporter
        reporter_headers = reporter.client.headers.to_contrast_hash
        REQUIRED_HEADERS.each do |key|
          value = decode_header(reporter_headers, key)
          missing << key if value.nil?
        end
        unless missing.empty?
          abort("Validation failed: Missing required header values: #{ missing.join(', ') } #{ FAIL }")
        end
        reporter
      end

      def test_connection reporter
        puts("  Connection failed #{ FAIL }") unless reporter
        connection = reporter.connection
        abort("Failed to Initialize Connection please check error logs for details #{ FAIL } ") unless connection
        abort('Failed to Start Client please check error logs for details') unless reporter.client.startup!(connection)
        last_response = reporter.client.response_handler.last_response_code
        if last_response.include?('40')
          puts("  Last response code: #{ last_response  } #{ FAIL }")
          abort("Failed to Initialize Connection please check error logs for details #{ FAIL } ")
        elsif connection
          puts("  Last response code: #{ last_response  } #{ PASS }")
          puts("  Connection successful #{ PASS }") if connection
        end
      end

      def mask_keys hash, key
        value = hash[key]
        redacted_value = Contrast::Configuration::REDACTED if SKIP_LOG.include?(key.to_s)
        puts("  #{ key }::#{ redacted_value || value }") unless value.is_a?(Contrast::Config::BaseConfiguration)
        value
      end

      def decode_header hash, key
        value = hash[key]
        decoded_header = Base64.strict_decode64(value)
        puts("  #{ key }::#{ key == :app_language ? value : decoded_header }")
        value
      end
      # rubocop:enable Rails/Output
    end
  end
end