import { describe, it, expect, beforeEach, vi } from "vitest";
import { NextRequest } from "next/server";

vi.mock("@/lib/db", () => ({
  db: {
    marketplaceUsageEvent: {
      create: vi.fn(),
    },
  },
}));

const mockRecordBillingEvent = vi.fn();
vi.mock("@/lib/marketplace-billing", () => ({
  recordBillingEvent: (...args: unknown[]) => mockRecordBillingEvent(...args),
}));

vi.mock("@/lib/logger", () => ({
  logger: { info: vi.fn(), error: vi.fn(), warn: vi.fn() },
}));

import { db } from "@/lib/db";

const VALID_SECRET = "test-internal-secret";

function makeRequest(
  body: Record<string, unknown>,
  secret?: string,
): NextRequest {
  return new NextRequest("http://localhost/api/internal/marketplace/billing", {
    method: "POST",
    headers: {
      "Content-Type": "application/json",
      ...(secret ? { "x-internal-secret": secret } : {}),
    },
    body: JSON.stringify(body),
  });
}

const validBody = {
  operatorId: "op-1",
  consumerId: "consumer-1",
  deviceId: "device-1",
  model: "llama3.3:70b",
  tokensIn: 500_000,
  tokensOut: 500_000,
  pricePerMToken: 0.1,
  requestId: "req-123",
};

describe("POST /api/internal/marketplace/billing", () => {
  beforeEach(() => {
    vi.clearAllMocks();
    process.env.INTERNAL_MARKETPLACE_SECRET = VALID_SECRET;
  });

  it("returns 403 when no secret header is provided", async () => {
    const { POST } = await import(
      "@/app/api/internal/marketplace/billing/route"
    );
    const request = makeRequest(validBody);
    const response = await POST(request);

    expect(response.status).toBe(403);
    const data = await response.json();
    expect(data.error).toBe("Forbidden");
  });

  it("returns 403 when secret header is wrong", async () => {
    const { POST } = await import(
      "@/app/api/internal/marketplace/billing/route"
    );
    const request = makeRequest(validBody, "wrong-secret");
    const response = await POST(request);

    expect(response.status).toBe(403);
  });

  it("returns 403 when INTERNAL_MARKETPLACE_SECRET env is not set", async () => {
    delete process.env.INTERNAL_MARKETPLACE_SECRET;

    const { POST } = await import(
      "@/app/api/internal/marketplace/billing/route"
    );
    const request = makeRequest(validBody, VALID_SECRET);
    const response = await POST(request);

    expect(response.status).toBe(403);
  });

  it("returns 400 for invalid JSON body", async () => {
    const { POST } = await import(
      "@/app/api/internal/marketplace/billing/route"
    );
    const request = new NextRequest(
      "http://localhost/api/internal/marketplace/billing",
      {
        method: "POST",
        headers: {
          "Content-Type": "application/json",
          "x-internal-secret": VALID_SECRET,
        },
        body: "not json",
      },
    );
    const response = await POST(request);

    expect(response.status).toBe(400);
    const data = await response.json();
    expect(data.error).toBe("Invalid JSON body");
  });

  it("returns 400 when required fields are missing", async () => {
    const { POST } = await import(
      "@/app/api/internal/marketplace/billing/route"
    );
    const request = makeRequest(
      { operatorId: "op-1", consumerId: "c-1" },
      VALID_SECRET,
    );
    const response = await POST(request);

    expect(response.status).toBe(400);
    const data = await response.json();
    expect(data.error).toBe("Missing required fields");
  });

  it("creates BOTH usage event and billing event on success", async () => {
    vi.mocked(db.marketplaceUsageEvent.create).mockResolvedValue({
      id: "usage-evt-1",
    } as never);
    mockRecordBillingEvent.mockResolvedValue({
      id: "billing-evt-1",
      operatorShareCents: 8,
      platformFeeCents: 2,
    });

    const { POST } = await import(
      "@/app/api/internal/marketplace/billing/route"
    );
    const request = makeRequest(validBody, VALID_SECRET);
    const response = await POST(request);
    const data = await response.json();

    expect(response.status).toBe(200);

    // Verify usage event was created
    expect(db.marketplaceUsageEvent.create).toHaveBeenCalledWith({
      data: expect.objectContaining({
        userId: "consumer-1",
        requestId: "req-123",
        deviceId: "device-1",
        model: "llama3.3:70b",
        promptTokens: 500_000,
        completionTokens: 500_000,
      }),
    });

    // Verify billing event was created via recordBillingEvent
    expect(mockRecordBillingEvent).toHaveBeenCalledWith({
      deviceId: "device-1",
      operatorId: "op-1",
      buyerId: "consumer-1",
      model: "llama3.3:70b",
      promptTokens: 500_000,
      completionTokens: 500_000,
      priceCents: expect.any(Number),
    });

    // Verify response includes both IDs
    expect(data.eventId).toBe("usage-evt-1");
    expect(data.billingEventId).toBe("billing-evt-1");
    expect(data.revenue).toBeGreaterThan(0);
    expect(data.operatorShare).toBeGreaterThan(0);
    expect(data.platformFee).toBeGreaterThan(0);
  });

  it("uses defaults for optional fields (tokensIn, tokensOut, pricePerMToken)", async () => {
    vi.mocked(db.marketplaceUsageEvent.create).mockResolvedValue({
      id: "usage-evt-2",
    } as never);
    mockRecordBillingEvent.mockResolvedValue({
      id: "billing-evt-2",
    });

    const { POST } = await import(
      "@/app/api/internal/marketplace/billing/route"
    );
    const request = makeRequest(
      {
        operatorId: "op-1",
        consumerId: "consumer-1",
        deviceId: "device-1",
        model: "qwen3:14b",
      },
      VALID_SECRET,
    );
    const response = await POST(request);

    expect(response.status).toBe(200);

    // Should default to 0 tokens and 0.1 pricePerMToken
    expect(db.marketplaceUsageEvent.create).toHaveBeenCalledWith({
      data: expect.objectContaining({
        promptTokens: 0,
        completionTokens: 0,
        priceCents: 0, // 0 tokens * 0.1 = 0
      }),
    });

    expect(mockRecordBillingEvent).toHaveBeenCalledWith(
      expect.objectContaining({
        promptTokens: 0,
        completionTokens: 0,
        priceCents: 0,
      }),
    );
  });

  it("generates a requestId when none is provided", async () => {
    vi.mocked(db.marketplaceUsageEvent.create).mockResolvedValue({
      id: "usage-evt-3",
    } as never);
    mockRecordBillingEvent.mockResolvedValue({ id: "billing-evt-3" });

    const { POST } = await import(
      "@/app/api/internal/marketplace/billing/route"
    );
    const request = makeRequest(
      {
        operatorId: "op-1",
        consumerId: "consumer-1",
        deviceId: "device-1",
        model: "qwen3:14b",
        tokensIn: 100,
        tokensOut: 100,
      },
      VALID_SECRET,
    );
    const response = await POST(request);

    expect(response.status).toBe(200);
    expect(db.marketplaceUsageEvent.create).toHaveBeenCalledWith({
      data: expect.objectContaining({
        requestId: expect.stringMatching(/^mkt_\d+$/),
      }),
    });
  });

  it("returns 500 when usage event creation fails", async () => {
    vi.mocked(db.marketplaceUsageEvent.create).mockRejectedValue(
      new Error("DB down"),
    );

    const { POST } = await import(
      "@/app/api/internal/marketplace/billing/route"
    );
    const request = makeRequest(validBody, VALID_SECRET);
    const response = await POST(request);

    expect(response.status).toBe(500);
    const data = await response.json();
    expect(data.error).toBe("Internal error");
    // Billing event should not be attempted if usage event fails
    expect(mockRecordBillingEvent).not.toHaveBeenCalled();
  });

  it("returns 500 when billing event creation fails", async () => {
    vi.mocked(db.marketplaceUsageEvent.create).mockResolvedValue({
      id: "usage-evt-4",
    } as never);
    mockRecordBillingEvent.mockRejectedValue(new Error("billing DB error"));

    const { POST } = await import(
      "@/app/api/internal/marketplace/billing/route"
    );
    const request = makeRequest(validBody, VALID_SECRET);
    const response = await POST(request);

    expect(response.status).toBe(500);
    const data = await response.json();
    expect(data.error).toBe("Internal error");
  });

  it("calculates revenue correctly for 1M tokens at $0.10/M", async () => {
    vi.mocked(db.marketplaceUsageEvent.create).mockResolvedValue({
      id: "usage-evt-5",
    } as never);
    mockRecordBillingEvent.mockResolvedValue({ id: "billing-evt-5" });

    const { POST } = await import(
      "@/app/api/internal/marketplace/billing/route"
    );
    const request = makeRequest(
      {
        operatorId: "op-1",
        consumerId: "consumer-1",
        deviceId: "device-1",
        model: "llama3.3:70b",
        tokensIn: 500_000,
        tokensOut: 500_000,
        pricePerMToken: 0.1,
      },
      VALID_SECRET,
    );
    const response = await POST(request);
    const data = await response.json();

    // 1M tokens * $0.10 = $0.10 revenue
    expect(data.revenue).toBeCloseTo(0.1);
    // 80% operator share = $0.08
    expect(data.operatorShare).toBeCloseTo(0.08);
    // 20% platform fee = $0.02
    expect(data.platformFee).toBeCloseTo(0.02);
  });
});
