# frozen_string_literal: true

require "webmock/rspec"

RSpec.describe Dispatch::Adapter::Claude, "#chat (non-streaming)" do
  let(:model_id)  { "claude-sonnet-4-6" }
  let(:api_key)   { "sk-ant-api03-test" }
  let(:base_url)  { "https://api.anthropic.com" }

  # Build a Claude adapter without loading real tokens from disk
  subject(:adapter) do
    described_class.new(
      model: model_id,
      api_key: api_key,
      base_url: base_url
    )
  end

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

  # A minimal valid Anthropic non-streaming response
  def stub_messages(response_body, status: 200)
    stub_request(:post, "#{base_url}/v1/messages")
      .to_return(
        status: status,
        body: JSON.generate(response_body),
        headers: { "Content-Type" => "application/json" }
      )
  end

  let(:text_response) do
    {
      "id" => "msg_01",
      "type" => "message",
      "role" => "assistant",
      "model" => model_id,
      "stop_reason" => "end_turn",
      "content" => [{ "type" => "text", "text" => "Hello from Claude!" }],
      "usage" => {
        "input_tokens" => 20,
        "output_tokens" => 10
      }
    }
  end

  let(:tool_use_response) do
    {
      "id" => "msg_02",
      "type" => "message",
      "role" => "assistant",
      "model" => model_id,
      "stop_reason" => "tool_use",
      "content" => [{
        "type" => "tool_use",
        "id" => "toolu_01",
        "name" => "bash",
        "input" => { "command" => "ls -la" }
      }],
      "usage" => {
        "input_tokens" => 30,
        "output_tokens" => 15
      }
    }
  end

  let(:cache_response) do
    {
      "id" => "msg_03",
      "type" => "message",
      "role" => "assistant",
      "model" => model_id,
      "stop_reason" => "end_turn",
      "content" => [{ "type" => "text", "text" => "Hi" }],
      "usage" => {
        "input_tokens" => 50,
        "output_tokens" => 10,
        "cache_read_input_tokens" => 200,
        "cache_creation_input_tokens" => 400
      }
    }
  end

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

  # ── Returns a correct Response ────────────────────────────────────────────

  describe "text response" do
    before { stub_messages(text_response) }

    it "returns a Response object" do
      expect(adapter.chat(messages)).to be_a(Dispatch::Adapter::Response)
    end

    it "sets content as TextBlock array" do
      response = adapter.chat(messages)
      expect(response.content).to include(an_instance_of(Dispatch::Adapter::TextBlock))
    end

    it "sets the text correctly" do
      response = adapter.chat(messages)
      expect(response.content.first.text).to eq("Hello from Claude!")
    end

    it "sets stop_reason to :end_turn" do
      expect(adapter.chat(messages).stop_reason).to eq(:end_turn)
    end

    it "sets the model" do
      expect(adapter.chat(messages).model).to eq(model_id)
    end
  end

  # ── Tool-call response ────────────────────────────────────────────────────

  describe "tool_use response" do
    before { stub_messages(tool_use_response) }

    it "sets stop_reason to :tool_use" do
      expect(adapter.chat(messages).stop_reason).to eq(:tool_use)
    end

    it "populates tool_calls" do
      response = adapter.chat(messages)
      expect(response.tool_calls).not_to be_empty
    end

    it "populates tool_calls with ToolUseBlock" do
      tc = adapter.chat(messages).tool_calls.first
      expect(tc).to be_a(Dispatch::Adapter::ToolUseBlock)
      expect(tc.id).to eq("toolu_01")
      expect(tc.name).to eq("bash")
      expect(tc.arguments).to eq({ "command" => "ls -la" })
    end
  end

  # ── Usage population ──────────────────────────────────────────────────────

  describe "usage" do
    before { stub_messages(text_response) }

    it "sets input_tokens" do
      expect(adapter.chat(messages).usage.input_tokens).to eq(20)
    end

    it "sets output_tokens" do
      expect(adapter.chat(messages).usage.output_tokens).to eq(10)
    end

    it "populates cost as UsageCost" do
      expect(adapter.chat(messages).usage.cost).to be_a(Dispatch::Adapter::UsageCost)
    end

    it "cost.total is a positive number" do
      expect(adapter.chat(messages).usage.cost.total).to be > 0
    end
  end

  describe "cache token fields" do
    before { stub_messages(cache_response) }

    it "sets cache_read_tokens" do
      expect(adapter.chat(messages).usage.cache_read_tokens).to eq(200)
    end

    it "sets cache_creation_tokens" do
      expect(adapter.chat(messages).usage.cache_creation_tokens).to eq(400)
    end

    it "includes cache costs in cost.total" do
      cost = adapter.chat(messages).usage.cost
      expect(cost.cache_read).to be > 0
      expect(cost.cache_write).to be > 0
    end
  end

  # ── Error propagation ──────────────────────────────────────────────────────

  describe "error handling" do
    it "raises AuthenticationError on 401" do
      stub_messages({ "error" => { "message" => "Unauthorized" } }, status: 401)
      expect { adapter.chat(messages) }.to raise_error(Dispatch::Adapter::AuthenticationError)
    end

    it "raises RateLimitError on 429" do
      stub_messages({ "error" => { "message" => "Rate limited" } }, status: 429)
      expect { adapter.chat(messages) }.to raise_error(Dispatch::Adapter::RateLimitError)
    end

    it "raises ServerError on 500" do
      stub_messages({ "error" => { "message" => "Server error" } }, status: 500)
      expect { adapter.chat(messages) }.to raise_error(Dispatch::Adapter::ServerError)
    end
  end

  # ── Request body sent to API ──────────────────────────────────────────────

  describe "request body construction" do
    before { stub_messages(text_response) }

    it "includes model in the request body" do
      adapter.chat(messages)
      expect(WebMock).to(have_requested(:post, "#{base_url}/v1/messages")
        .with { |req| JSON.parse(req.body)["model"] == model_id })
    end

    it "includes messages in the request body" do
      adapter.chat(messages)
      expect(WebMock).to(have_requested(:post, "#{base_url}/v1/messages")
        .with { |req| JSON.parse(req.body)["messages"].is_a?(Array) })
    end

    it "sets stream: false in the request body" do
      adapter.chat(messages)
      expect(WebMock).to(have_requested(:post, "#{base_url}/v1/messages")
        .with { |req| JSON.parse(req.body)["stream"] == false })
    end

    it "includes max_tokens in the request body" do
      adapter.chat(messages)
      expect(WebMock).to(have_requested(:post, "#{base_url}/v1/messages")
        .with { |req| JSON.parse(req.body).key?("max_tokens") })
    end
  end
end
