# frozen_string_literal: true

module Dispatch
  module Adapter
    class Claude < Base
      module Cloaking
        CLAUDE_AGENT_INSTRUCTION =
          "You are a Claude agent, built on Anthropic's Claude Agent SDK."

        # Models that skip the Claude-Agent instruction block (per oh-my-pi).
        SKIP_AGENT_INSTRUCTION_PATTERN = /claude-3-5-haiku/i

        TOOL_PREFIX = "proxy_"
        BUILTINS    = %w[web_search code_execution text_editor computer].freeze

        USER_ID_REGEX = /\A
          user_[0-9a-fA-F]{64}
          _account_[0-9a-f]{8}-[0-9a-f]{4}-[0-9a-f]{4}-[0-9a-f]{4}-[0-9a-f]{12}
          _session_[0-9a-f]{8}-[0-9a-f]{4}-[0-9a-f]{4}-[0-9a-f]{4}-[0-9a-f]{12}
        \z/x

        module_function

        # Generate the billing header string that gets prepended to the system
        # block array on OAuth requests.
        #
        # @param payload [Hash, nil] the full request params hash
        # @return [String] the billing header value
        def billing_header(payload)
          payload_json = JSON.generate(payload || {})
          cch = Digest::SHA256.hexdigest(payload_json)[0, 5]
          build_hash = SecureRandom.hex(2)[0, 3]
          "x-anthropic-billing-header: cc_version=#{Headers::CLAUDE_CODE_VERSION}.#{build_hash}; cc_entrypoint=cli; cch=#{cch};"
        end

        # Prefix a tool name with "proxy_" unless it is a builtin or already prefixed.
        #
        # @param name [String]
        # @return [String]
        def apply_prefix(name)
          return name if BUILTINS.include?(name.downcase)
          return name if name.downcase.start_with?(TOOL_PREFIX)

          "#{TOOL_PREFIX}#{name}"
        end

        # Strip the "proxy_" prefix from a tool name (inverse of apply_prefix).
        #
        # @param name [String]
        # @return [String]
        def strip_prefix(name)
          return name unless name.downcase.start_with?(TOOL_PREFIX)

          name.sub(/\A#{TOOL_PREFIX}/i, "")
        end

        # Returns true if the string matches the cloaking user-id format.
        def cloaking_user_id?(str)
          str.is_a?(String) && USER_ID_REGEX.match?(str)
        end

        # Generate a fresh cloaking user id.
        def generate_cloaking_user_id
          user    = SecureRandom.hex(32)
          account = SecureRandom.uuid.downcase
          session = SecureRandom.uuid.downcase
          "user_#{user}_account_#{account}_session_#{session}"
        end

        # Resolve the user_id for a request's metadata.
        # In OAuth mode, only pass through an already-cloaked id; otherwise generate one.
        # In API-key mode, pass through any String as-is; return nil for nil.
        def resolve_user_id(provided, is_oauth)
          return provided if provided.is_a?(String) && (!is_oauth || cloaking_user_id?(provided))
          return nil unless is_oauth

          generate_cloaking_user_id
        end

        # Build the system-block array for the request body.
        #
        # @param user_system [String, Array<Hash>, nil]
        # @param is_oauth [Boolean]
        # @param model_id [String]
        # @param cache_control [Hash, nil] attached to the last user block
        # @param billing_payload [Hash, nil] forwarded to billing_header
        # @return [Array<Hash>, nil]
        def build_system_blocks(user_system, is_oauth:, model_id:, cache_control: nil, billing_payload: nil)
          # Normalise user_system into an Array<Hash>
          user_blocks = normalise_system(user_system)

          # Short-circuit: pre-existing billing header → forward as-is
          return attach_cache_control(user_blocks, cache_control) if user_blocks.any? { |b| b["text"].to_s.start_with?("x-anthropic-billing-header:") }

          # Non-OAuth: no cloaking
          unless is_oauth
            return nil if user_blocks.empty?

            return attach_cache_control(user_blocks, cache_control)
          end

          # OAuth: inject billing + optional agent instruction
          prefix_blocks = []

          billing_text = billing_header(billing_payload)
          prefix_blocks << text_block(billing_text)

          prefix_blocks << text_block(CLAUDE_AGENT_INSTRUCTION) unless model_id.to_s.match?(SKIP_AGENT_INSTRUCTION_PATTERN)

          all_blocks = prefix_blocks + user_blocks
          attach_cache_control(all_blocks, cache_control)
        end

        # Normalise a String, Array of TextBlock/Hash, or nil into Array<Hash>
        def normalise_system(user_system)
          case user_system
          when nil
            []
          when String
            user_system.empty? ? [] : [text_block(user_system)]
          when Array
            user_system.map do |block|
              if block.respond_to?(:to_h)
                h = block.to_h
                # Convert symbol keys to string keys
                h.transform_keys(&:to_s)
              else
                h = block.transform_keys(&:to_s)
                h
              end
            end
          else
            [text_block(user_system.to_s)]
          end
        end

        # Build a plain text block hash.
        def text_block(text)
          { "type" => "text", "text" => text }
        end

        # Attach cache_control to the last block (if provided).
        def attach_cache_control(blocks, cache_control)
          return blocks if cache_control.nil? || blocks.empty?

          result = blocks.map(&:dup)
          result.last["cache_control"] = cache_control
          result
        end
      end
    end
  end
end
