# frozen_string_literal: true

require "webmock/rspec"

RSpec.describe Dispatch::Adapter::Claude, "error mapping integration" do
  let(:model_id) { "claude-sonnet-4-5-20250929" }
  let(:api_key)  { "sk-ant-api03-test" }
  let(:base_url) { "https://api.anthropic.com" }

  subject(:adapter) do
    described_class.new(
      model: model_id,
      api_key: api_key,
      base_url: base_url
    )
  end

  let(:messages) do
    [Dispatch::Adapter::Message.new(
      role: "user",
      content: [Dispatch::Adapter::TextBlock.new(text: "Hello")]
    )]
  end

  before { WebMock.disable_net_connect! }
  after  { WebMock.reset! }

  # ── Helpers ───────────────────────────────────────────────────────────────

  def stub_api_error(status:, type:, message:, retry_after: nil)
    headers = { "Content-Type" => "application/json" }
    headers["Retry-After"] = retry_after.to_s if retry_after
    stub_request(:post, "#{base_url}/v1/messages")
      .to_return(
        status: status,
        body: JSON.generate({ "type" => "error", "error" => { "type" => type, "message" => message } }),
        headers: headers
      )
  end

  def stub_network_error(error_class)
    stub_request(:post, "#{base_url}/v1/messages").to_raise(error_class)
  end

  # ── 400 invalid_request_error → RequestError ─────────────────────────────

  describe "400 invalid_request_error" do
    before { stub_api_error(status: 400, type: "invalid_request_error", message: "Bad request body") }

    it "raises RequestError" do
      expect { adapter.chat(messages) }.to raise_error(Dispatch::Adapter::RequestError)
    end

    it "e.status_code == 400" do
      adapter.chat(messages)
    rescue Dispatch::Adapter::RequestError => e
      expect(e.status_code).to eq(400)
    end

    it "e.provider == 'Anthropic (Claude)'" do
      adapter.chat(messages)
    rescue Dispatch::Adapter::RequestError => e
      expect(e.provider).to eq("Anthropic (Claude)")
    end

    it "e.message includes the error message from body" do
      adapter.chat(messages)
    rescue Dispatch::Adapter::RequestError => e
      expect(e.message).to include("Bad request body")
    end
  end

  # ── 401 authentication_error → AuthenticationError ───────────────────────

  describe "401 authentication_error" do
    before { stub_api_error(status: 401, type: "authentication_error", message: "Invalid API key") }

    it "raises AuthenticationError" do
      expect { adapter.chat(messages) }.to raise_error(Dispatch::Adapter::AuthenticationError)
    end

    it "e.status_code == 401" do
      adapter.chat(messages)
    rescue Dispatch::Adapter::AuthenticationError => e
      expect(e.status_code).to eq(401)
    end

    it "e.provider == 'Anthropic (Claude)'" do
      adapter.chat(messages)
    rescue Dispatch::Adapter::AuthenticationError => e
      expect(e.provider).to eq("Anthropic (Claude)")
    end

    it "e.message includes the error message from body" do
      adapter.chat(messages)
    rescue Dispatch::Adapter::AuthenticationError => e
      expect(e.message).to include("Invalid API key")
    end
  end

  # ── 403 permission_error → AuthenticationError ───────────────────────────

  describe "403 permission_error" do
    before { stub_api_error(status: 403, type: "permission_error", message: "Access denied") }

    it "raises AuthenticationError" do
      expect { adapter.chat(messages) }.to raise_error(Dispatch::Adapter::AuthenticationError)
    end

    it "e.status_code == 403" do
      adapter.chat(messages)
    rescue Dispatch::Adapter::AuthenticationError => e
      expect(e.status_code).to eq(403)
    end

    it "e.provider == 'Anthropic (Claude)'" do
      adapter.chat(messages)
    rescue Dispatch::Adapter::AuthenticationError => e
      expect(e.provider).to eq("Anthropic (Claude)")
    end

    it "e.message includes the error message from body" do
      adapter.chat(messages)
    rescue Dispatch::Adapter::AuthenticationError => e
      expect(e.message).to include("Access denied")
    end
  end

  # ── 422 unprocessable_entity → RequestError ──────────────────────────────

  describe "422 unprocessable_entity" do
    before { stub_api_error(status: 422, type: "unprocessable_entity", message: "Invalid field value") }

    it "raises RequestError" do
      expect { adapter.chat(messages) }.to raise_error(Dispatch::Adapter::RequestError)
    end

    it "e.status_code == 422" do
      adapter.chat(messages)
    rescue Dispatch::Adapter::RequestError => e
      expect(e.status_code).to eq(422)
    end

    it "e.provider == 'Anthropic (Claude)'" do
      adapter.chat(messages)
    rescue Dispatch::Adapter::RequestError => e
      expect(e.provider).to eq("Anthropic (Claude)")
    end

    it "e.message includes the error message from body" do
      adapter.chat(messages)
    rescue Dispatch::Adapter::RequestError => e
      expect(e.message).to include("Invalid field value")
    end
  end

  # ── 429 rate_limit_error → RateLimitError w/ retry_after ─────────────────

  describe "429 rate_limit_error" do
    before { stub_api_error(status: 429, type: "rate_limit_error", message: "Too many requests", retry_after: 30) }

    it "raises RateLimitError" do
      expect { adapter.chat(messages) }.to raise_error(Dispatch::Adapter::RateLimitError)
    end

    it "e.status_code == 429" do
      adapter.chat(messages)
    rescue Dispatch::Adapter::RateLimitError => e
      expect(e.status_code).to eq(429)
    end

    it "e.provider == 'Anthropic (Claude)'" do
      adapter.chat(messages)
    rescue Dispatch::Adapter::RateLimitError => e
      expect(e.provider).to eq("Anthropic (Claude)")
    end

    it "e.retry_after == 30" do
      adapter.chat(messages)
    rescue Dispatch::Adapter::RateLimitError => e
      expect(e.retry_after).to eq(30)
    end

    it "e.message includes the error message from body" do
      adapter.chat(messages)
    rescue Dispatch::Adapter::RateLimitError => e
      expect(e.message).to include("Too many requests")
    end
  end

  # ── 500 internal_error → ServerError ─────────────────────────────────────

  describe "500 internal_error" do
    before { stub_api_error(status: 500, type: "internal_error", message: "Internal server error") }

    it "raises ServerError" do
      expect { adapter.chat(messages) }.to raise_error(Dispatch::Adapter::ServerError)
    end

    it "e.status_code == 500" do
      adapter.chat(messages)
    rescue Dispatch::Adapter::ServerError => e
      expect(e.status_code).to eq(500)
    end

    it "e.provider == 'Anthropic (Claude)'" do
      adapter.chat(messages)
    rescue Dispatch::Adapter::ServerError => e
      expect(e.provider).to eq("Anthropic (Claude)")
    end

    it "e.message includes the error message from body" do
      adapter.chat(messages)
    rescue Dispatch::Adapter::ServerError => e
      expect(e.message).to include("Internal server error")
    end
  end

  # ── 502 bad_gateway → ServerError ────────────────────────────────────────

  describe "502 bad_gateway" do
    before { stub_api_error(status: 502, type: "bad_gateway", message: "Bad gateway") }

    it "raises ServerError" do
      expect { adapter.chat(messages) }.to raise_error(Dispatch::Adapter::ServerError)
    end

    it "e.status_code == 502" do
      adapter.chat(messages)
    rescue Dispatch::Adapter::ServerError => e
      expect(e.status_code).to eq(502)
    end

    it "e.provider == 'Anthropic (Claude)'" do
      adapter.chat(messages)
    rescue Dispatch::Adapter::ServerError => e
      expect(e.provider).to eq("Anthropic (Claude)")
    end

    it "e.message includes the error message from body" do
      adapter.chat(messages)
    rescue Dispatch::Adapter::ServerError => e
      expect(e.message).to include("Bad gateway")
    end
  end

  # ── 503 service_unavailable → ServerError ────────────────────────────────

  describe "503 service_unavailable" do
    before { stub_api_error(status: 503, type: "service_unavailable", message: "Service temporarily unavailable") }

    it "raises ServerError" do
      expect { adapter.chat(messages) }.to raise_error(Dispatch::Adapter::ServerError)
    end

    it "e.status_code == 503" do
      adapter.chat(messages)
    rescue Dispatch::Adapter::ServerError => e
      expect(e.status_code).to eq(503)
    end

    it "e.provider == 'Anthropic (Claude)'" do
      adapter.chat(messages)
    rescue Dispatch::Adapter::ServerError => e
      expect(e.provider).to eq("Anthropic (Claude)")
    end

    it "e.message includes the error message from body" do
      adapter.chat(messages)
    rescue Dispatch::Adapter::ServerError => e
      expect(e.message).to include("Service temporarily unavailable")
    end
  end

  # ── 529 overloaded_error → OverloadedError (subclass of RateLimitError) ──

  describe "529 overloaded_error" do
    before { stub_api_error(status: 529, type: "overloaded_error", message: "Overloaded", retry_after: 60) }

    it "raises OverloadedError" do
      expect { adapter.chat(messages) }.to raise_error(Dispatch::Adapter::OverloadedError)
    end

    it "also caught by RateLimitError (OverloadedError is a subclass)" do
      expect { adapter.chat(messages) }.to raise_error(Dispatch::Adapter::RateLimitError)
    end

    it "e.status_code == 529" do
      adapter.chat(messages)
    rescue Dispatch::Adapter::OverloadedError => e
      expect(e.status_code).to eq(529)
    end

    it "e.provider == 'Anthropic (Claude)'" do
      adapter.chat(messages)
    rescue Dispatch::Adapter::OverloadedError => e
      expect(e.provider).to eq("Anthropic (Claude)")
    end

    it "e.retry_after == 60" do
      adapter.chat(messages)
    rescue Dispatch::Adapter::OverloadedError => e
      expect(e.retry_after).to eq(60)
    end

    it "e.message includes the error message from body" do
      adapter.chat(messages)
    rescue Dispatch::Adapter::OverloadedError => e
      expect(e.message).to include("Overloaded")
    end
  end

  # ── Network connection refused → ConnectionError ──────────────────────────

  describe "network connection refused" do
    before { stub_network_error(Errno::ECONNREFUSED) }

    it "raises ConnectionError" do
      expect { adapter.chat(messages) }.to raise_error(Dispatch::Adapter::ConnectionError)
    end

    it "e.provider == 'Anthropic (Claude)'" do
      adapter.chat(messages)
    rescue Dispatch::Adapter::ConnectionError => e
      expect(e.provider).to eq("Anthropic (Claude)")
    end

    it "e.status_code is nil (no HTTP response)" do
      adapter.chat(messages)
    rescue Dispatch::Adapter::ConnectionError => e
      expect(e.status_code).to be_nil
    end

    it "e.message includes the provider name" do
      adapter.chat(messages)
    rescue Dispatch::Adapter::ConnectionError => e
      expect(e.message).to include("Anthropic (Claude)")
    end
  end

  # ── Network timeout → ConnectionError ────────────────────────────────────

  describe "network read timeout" do
    before { stub_network_error(Net::ReadTimeout) }

    it "raises ConnectionError" do
      expect { adapter.chat(messages) }.to raise_error(Dispatch::Adapter::ConnectionError)
    end

    it "e.provider == 'Anthropic (Claude)'" do
      adapter.chat(messages)
    rescue Dispatch::Adapter::ConnectionError => e
      expect(e.provider).to eq("Anthropic (Claude)")
    end
  end

  # ── OverloadedError class hierarchy ──────────────────────────────────────

  describe "OverloadedError class hierarchy" do
    it "OverloadedError.ancestors includes RateLimitError" do
      expect(Dispatch::Adapter::OverloadedError.ancestors).to include(Dispatch::Adapter::RateLimitError)
    end

    it "OverloadedError.ancestors includes Error" do
      expect(Dispatch::Adapter::OverloadedError.ancestors).to include(Dispatch::Adapter::Error)
    end

    it "OverloadedError.superclass is RateLimitError" do
      expect(Dispatch::Adapter::OverloadedError.superclass).to eq(Dispatch::Adapter::RateLimitError)
    end
  end
end
