# frozen_string_literal: true

module Mcp
  module Tools
    class ApiTool
      attr_reader :name, :route, :settings, :version

      # Grape types are represented as a string by calling `.to_s` on a type
      # The values are built based on the existing routes:
      # - [String, Integer] is usually a type of an id, which can be represented as a string
      # - Grape::API::Boolean is a boolean
      # - [Integer] is an array of integers
      # - [String] represents a comma-separated string
      TYPE_CONVERSIONS = {
        '[String, Integer]' => 'string',
        'Grape::API::Boolean' => 'boolean',
        '[Integer]' => { type: 'array', items: { type: 'integer' } },
        '[String]' => 'string'
      }.freeze

      ARRAY_TYPE_PATTERN = /^Array\[(\w+)\]$/

      GENERIC_NOT_FOUND_MESSAGE = '404 Not Found'

      def initialize(name:, route:)
        @name = name
        @route = route
        @settings = route.app.route_setting(:mcp)
        @version = @settings[:version] || "0.1.0"
      end

      def description
        route.description
      end

      def input_schema
        params = route.params.slice(*settings[:params].map(&:to_s))
        required_fields = params.filter_map do |param, values|
          param if values[:required]
        end

        properties = params.transform_values do |value|
          parsed_type = parse_type(value[:type])
          if parsed_type.is_a?(Hash)
            parsed_type.merge(description: value[:desc])
          else
            { type: parsed_type, description: value[:desc] }
          end
        end

        {
          type: 'object',
          properties: properties,
          required: required_fields,
          additionalProperties: false
        }
      end

      def icons
        IconConfig.gitlab_icons
      end

      def execute(request: nil, params: nil)
        args = params[:arguments]&.slice(*settings[:params]) || {}
        request.env[Grape::Env::GRAPE_ROUTING_ARGS].merge!(args)
        request.env[Rack::REQUEST_METHOD] = route.request_method

        original_format = request.env['api.format']
        begin
          status, _, body = route.exec(request.env)
        ensure
          request.env['api.format'] = original_format
        end
        process_response(status, Array(body)[0])
      end

      def annotations
        return settings[:annotations] if settings[:annotations].present?

        auto_annotations = {}
        auto_annotations[:readOnlyHint] = true if route.request_method == 'GET'
        auto_annotations
      end

      private

      def parse_type(type)
        return TYPE_CONVERSIONS[type] if TYPE_CONVERSIONS.key?(type)

        # Handle Array[Type] format (e.g., 'Array[Integer]', 'Array[String]')
        if type.match?(ARRAY_TYPE_PATTERN)
          inner_type = type.match(ARRAY_TYPE_PATTERN)[1].downcase
          return { type: 'array', items: { type: inner_type } }
        end

        type.downcase
      end

      def process_response(status, body)
        parsed_response = Gitlab::Json.safe_parse(body)
        if status >= 400
          message =
            if status == 404 && settings[:resource_name] && generic_not_found?(parsed_response)
              resource_not_found_message
            else
              parsed_response['error'] || parsed_response['message'] || "HTTP #{status}"
            end

          ::Mcp::Tools::Response.error(message, parsed_response)
        else
          formatted_content = [{ type: 'text', text: body }]
          ::Mcp::Tools::Response.success(formatted_content, parsed_response)
        end
      rescue JSON::ParserError
        if status >= 400
          ::Mcp::Tools::Response.error("HTTP #{status}", { body: body })
        else
          # Plain text response (e.g. job trace); return as-is
          formatted_content = [{ type: 'text', text: body }]
          ::Mcp::Tools::Response.success(formatted_content)
        end
      end

      def resource_not_found_message
        "404 #{settings[:resource_name].capitalize} Not Found"
      end

      def generic_not_found?(parsed_response)
        parsed_response['message']&.casecmp?(GENERIC_NOT_FOUND_MESSAGE)
      end
    end
  end
end
