/**
 * EN: Unit tests for analytics tools (popular_items, cost_analysis).
 * VI: Unit tests cho analytics tools.
 */
import { describe, it, expect, vi, beforeEach } from "vitest";

// --- Mock API clients (hoisted so vi.mock factory can reference them) ---
const { mockOrderGet, mockInventoryGet } = vi.hoisted(() => ({
  mockOrderGet: vi.fn(),
  mockInventoryGet: vi.fn(),
}));

vi.mock("../services/api-client.js", () => ({
  catalogApi: { get: vi.fn(), post: vi.fn(), put: vi.fn(), delete: vi.fn() },
  inventoryApi: { get: mockInventoryGet, post: vi.fn() },
  fnbApi: { get: vi.fn(), post: vi.fn() },
  orderApi: { get: mockOrderGet },
  DEFAULT_SHOP_ID: "00000000-0000-0000-0000-000000000001",
}));

// --- Mock MCP server to capture registered tool handlers ---
type ToolHandler = (args: Record<string, unknown>) => Promise<unknown>;

const registeredTools: Record<string, ToolHandler> = {};

vi.mock("@modelcontextprotocol/sdk/server/mcp.js", () => ({
  McpServer: class {
    tool(name: string, _desc: string, _schema: unknown, handler: ToolHandler) {
      registeredTools[name] = handler;
    }
  },
}));

import { McpServer } from "@modelcontextprotocol/sdk/server/mcp.js";
import { registerAnalyticsTools } from "../tools/analytics-tools.js";

const SHOP_ID = "00000000-0000-0000-0000-000000000001";

const SAMPLE_DASHBOARD = {
  revenue: 5000000,
  orderCount: 120,
  itemsSold: 350,
  avgOrderValue: 41667,
  popularItems: [
    { productName: "Cà phê sữa đá", quantitySold: 80, revenue: 2800000 },
    { productName: "Trà đào", quantitySold: 60, revenue: 2400000 },
  ],
  hourlyRevenue: [],
};

const SAMPLE_INVENTORY = [
  { id: "i1", name: "Cà phê", quantity: 10, costPerUnit: 200000, unit: "kg", reorderLevel: 5 },
  { id: "i2", name: "Sữa tươi", quantity: 20, costPerUnit: 30000, unit: "lít", reorderLevel: 10 },
];

describe("analytics-tools", () => {
  let server: McpServer;

  beforeEach(() => {
    vi.clearAllMocks();
    server = new McpServer({ name: "test", version: "1.0" } as never);
    registerAnalyticsTools(server);
  });

  // ── popular_items ─────────────────────────────────────────────────────────

  describe("popular_items", () => {
    it("returns top selling products with revenue and summary stats", async () => {
      mockOrderGet.mockResolvedValueOnce({ data: { data: SAMPLE_DASHBOARD } });

      const result = (await registeredTools["popular_items"]({
        shopId: SHOP_ID,
        period: "7d",
      })) as { content: Array<{ text: string }> };

      const text = result.content[0].text;
      expect(text).toContain("Top Selling Products");
      expect(text).toContain("7d");
      expect(text).toContain("Cà phê sữa đá");
      expect(text).toContain("80 sold");
      expect(text).toContain("Trà đào");
      expect(text).toContain("Total Revenue");
      expect(text).toContain("120"); // orderCount
    });

    it("handles flat response without data wrapper", async () => {
      mockOrderGet.mockResolvedValueOnce({ data: SAMPLE_DASHBOARD });

      const result = (await registeredTools["popular_items"]({
        period: "today",
      })) as { content: Array<{ text: string }> };

      expect(result.content[0].text).toContain("Cà phê sữa đá");
    });

    it("returns 'No sales data found' when dashboard is empty", async () => {
      mockOrderGet.mockResolvedValueOnce({
        data: { revenue: 0, orderCount: 0, itemsSold: 0, avgOrderValue: 0, popularItems: [] },
      });

      const result = (await registeredTools["popular_items"]({
        period: "today",
      })) as { content: Array<{ text: string }> };

      expect(result.content[0].text).toContain("No sales data found");
    });

    it("shows '(No product rankings available)' when popularItems is empty but revenue > 0", async () => {
      mockOrderGet.mockResolvedValueOnce({
        data: { ...SAMPLE_DASHBOARD, popularItems: [] },
      });

      const result = (await registeredTools["popular_items"]({
        period: "30d",
      })) as { content: Array<{ text: string }> };

      expect(result.content[0].text).toContain("No product rankings available");
    });

    it("uses DEFAULT_SHOP_ID when shopId not provided", async () => {
      mockOrderGet.mockResolvedValueOnce({ data: SAMPLE_DASHBOARD });

      await registeredTools["popular_items"]({ period: "7d" });

      expect(mockOrderGet).toHaveBeenCalledWith(
        "/orders/dashboard",
        expect.objectContaining({ params: expect.objectContaining({ shopId: "00000000-0000-0000-0000-000000000001" }) }),
      );
    });

    it("returns error response on API failure", async () => {
      mockOrderGet.mockRejectedValueOnce(new Error("Timeout"));

      const result = (await registeredTools["popular_items"]({
        period: "7d",
      })) as { isError: boolean };

      expect(result.isError).toBe(true);
    });
  });

  // ── cost_analysis ─────────────────────────────────────────────────────────

  describe("cost_analysis", () => {
    it("returns cost analysis report with inventory and revenue data", async () => {
      mockInventoryGet.mockResolvedValueOnce({
        data: { data: { items: SAMPLE_INVENTORY } },
      });
      mockOrderGet.mockResolvedValueOnce({ data: { data: SAMPLE_DASHBOARD } });

      const result = (await registeredTools["cost_analysis"]({
        shopId: SHOP_ID,
      })) as { content: Array<{ text: string }> };

      const text = result.content[0].text;
      expect(text).toContain("Cost Analysis Report");
      expect(text).toContain("Revenue");
      expect(text).toContain("Inventory Value");
      expect(text).toContain("Cà phê");
    });

    it("calculates total inventory value correctly", async () => {
      // Cà phê: 10 * 200000 = 2,000,000
      // Sữa tươi: 20 * 30000 = 600,000
      // Total: 2,600,000
      mockInventoryGet.mockResolvedValueOnce({
        data: { data: { items: SAMPLE_INVENTORY } },
      });
      mockOrderGet.mockResolvedValueOnce({ data: { data: SAMPLE_DASHBOARD } });

      const result = (await registeredTools["cost_analysis"]({
        shopId: SHOP_ID,
      })) as { content: Array<{ text: string }> };

      // 2,600,000 formatted as vi-VN locale
      expect(result.content[0].text).toContain("2.600.000");
    });

    it("still returns partial report when inventory API fails", async () => {
      mockInventoryGet.mockRejectedValueOnce(new Error("Service down"));
      mockOrderGet.mockResolvedValueOnce({ data: { data: SAMPLE_DASHBOARD } });

      const result = (await registeredTools["cost_analysis"]({
        shopId: SHOP_ID,
      })) as { content: Array<{ text: string }> };

      const text = result.content[0].text;
      expect(text).toContain("Cost Analysis Report");
      expect(text).toContain("Inventory data unavailable");
    });

    it("still returns partial report when dashboard API fails", async () => {
      mockInventoryGet.mockResolvedValueOnce({
        data: { data: { items: SAMPLE_INVENTORY } },
      });
      mockOrderGet.mockRejectedValueOnce(new Error("Dashboard down"));

      const result = (await registeredTools["cost_analysis"]({
        shopId: SHOP_ID,
      })) as { content: Array<{ text: string }> };

      const text = result.content[0].text;
      expect(text).toContain("Revenue data unavailable");
      expect(text).toContain("Cà phê"); // inventory still shows
    });

    it("returns error response when both APIs fail with unexpected error", async () => {
      // cost_analysis uses Promise.allSettled so it handles partial failures gracefully
      // When all data is unavailable it should still return a report (not throw)
      mockInventoryGet.mockRejectedValueOnce(new Error("Down"));
      mockOrderGet.mockRejectedValueOnce(new Error("Down"));

      const result = (await registeredTools["cost_analysis"]({
        shopId: SHOP_ID,
      })) as { content: Array<{ text: string }> };

      // Should return a partial report, not an error
      expect(result.content[0].text).toContain("Cost Analysis Report");
    });

    it("uses DEFAULT_SHOP_ID when shopId not provided", async () => {
      mockInventoryGet.mockResolvedValueOnce({ data: { data: { items: [] } } });
      mockOrderGet.mockResolvedValueOnce({ data: { data: SAMPLE_DASHBOARD } });

      await registeredTools["cost_analysis"]({});

      expect(mockInventoryGet).toHaveBeenCalledWith(
        "/inventory",
        expect.objectContaining({ params: expect.objectContaining({ shopId: "00000000-0000-0000-0000-000000000001" }) }),
      );
    });
  });
});
