# frozen_string_literal: true

require "test_helper"
require "webmock/minitest"
require "faraday"
require "mcp/client/oauth"

module MCP
  class Client
    module OAuth
      class IDJAGTokenExchangeTest < Minitest::Test
        def setup
          WebMock.enable!
          @token_endpoint = "https://idp.example.com/token"
        end

        def teardown
          WebMock.reset!
        end

        def request_exchange
          IDJAGTokenExchange.request(
            token_endpoint: @token_endpoint,
            id_token: "idp-id-token",
            client_id: "idp-client",
            audience: "https://auth.example.com",
            resource: "https://srv.example.com/mcp",
          )
        end

        def test_request_sends_rfc8693_token_exchange_parameters
          stub_request(:post, @token_endpoint).to_return(
            status: 200,
            headers: { "Content-Type" => "application/json" },
            body: JSON.generate(
              access_token: "id-jag-assertion",
              issued_token_type: "urn:ietf:params:oauth:token-type:id-jag",
              token_type: "N_A",
            ),
          )

          assertion = request_exchange

          assert_equal("id-jag-assertion", assertion)
          assert_requested(:post, @token_endpoint) do |req|
            form = URI.decode_www_form(req.body).to_h
            form["grant_type"] == "urn:ietf:params:oauth:grant-type:token-exchange" &&
              form["subject_token"] == "idp-id-token" &&
              form["subject_token_type"] == "urn:ietf:params:oauth:token-type:id_token" &&
              form["requested_token_type"] == "urn:ietf:params:oauth:token-type:id-jag" &&
              form["audience"] == "https://auth.example.com" &&
              form["resource"] == "https://srv.example.com/mcp" &&
              form["client_id"] == "idp-client"
          end
        end

        def test_request_rejects_response_without_id_jag_token_type
          # A plain access token is not an ID-JAG; presenting it as a
          # jwt-bearer assertion would fail confusingly downstream.
          stub_request(:post, @token_endpoint).to_return(
            status: 200,
            headers: { "Content-Type" => "application/json" },
            body: JSON.generate(
              access_token: "ordinary-token",
              issued_token_type: "urn:ietf:params:oauth:token-type:access_token",
            ),
          )

          error = assert_raises(IDJAGTokenExchange::ExchangeError) { request_exchange }
          assert_match(/did not issue an ID-JAG/, error.message)
        end

        def test_request_rejects_missing_access_token
          stub_request(:post, @token_endpoint).to_return(
            status: 200,
            headers: { "Content-Type" => "application/json" },
            body: JSON.generate(issued_token_type: "urn:ietf:params:oauth:token-type:id-jag"),
          )

          error = assert_raises(IDJAGTokenExchange::ExchangeError) { request_exchange }
          assert_match(/missing `access_token`/, error.message)
        end

        def test_request_raises_on_non_2xx_response
          stub_request(:post, @token_endpoint).to_return(status: 400, body: JSON.generate(error: "invalid_grant"))

          error = assert_raises(IDJAGTokenExchange::ExchangeError) { request_exchange }
          assert_match(/status 400/, error.message)
        end

        def test_request_raises_on_unparseable_response
          stub_request(:post, @token_endpoint).to_return(status: 200, body: "not json")

          error = assert_raises(IDJAGTokenExchange::ExchangeError) { request_exchange }
          assert_match(/Failed to parse/, error.message)
        end

        def test_request_raises_on_non_object_json_response
          stub_request(:post, @token_endpoint).to_return(status: 200, body: "[]")

          error = assert_raises(IDJAGTokenExchange::ExchangeError) { request_exchange }
          assert_match(/not a JSON object/, error.message)
        end

        def test_request_refuses_a_response_body_over_the_cap
          stub_request(:post, @token_endpoint).to_return(
            status: 200,
            headers: { "Content-Type" => "application/json" },
            body: "a" * (4 * 1024 * 1024 + 1),
          )

          error = assert_raises(IDJAGTokenExchange::ExchangeError) { request_exchange }
          assert_match(/exceeds \d+ bytes/, error.message)
        end
      end
    end
  end
end
