/**
 * EN: Unit tests for inventory tools (check_inventory, record_intake, record_usage, low_stock_alerts).
 * VI: Unit tests cho inventory tools.
 */
import { describe, it, expect, vi, beforeEach } from "vitest";

// --- Mock API clients (hoisted so vi.mock factory can reference them) ---
const { mockInventoryGet, mockInventoryPost } = vi.hoisted(() => ({
  mockInventoryGet: vi.fn(),
  mockInventoryPost: 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: mockInventoryPost,
  },
  fnbApi: { get: vi.fn(), post: vi.fn() },
  orderApi: { get: vi.fn() },
  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 { registerInventoryTools } from "../tools/inventory-tools.js";

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

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

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

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

  // ── check_inventory ──────────────────────────────────────────────────────

  describe("check_inventory", () => {
    it("returns formatted inventory table with low stock warnings", async () => {
      mockInventoryGet.mockResolvedValueOnce({
        data: { data: { items: SAMPLE_ITEMS, totalCount: 2 } },
      });

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

      const text = result.content[0].text;
      expect(text).toContain("Cà phê rang");
      expect(text).toContain("Sữa tươi");
      // Sữa tươi qty=5 <= reorderLevel=10 → should show Low warning
      expect(text).toContain("Low");
      // Cà phê rang qty=50 > reorderLevel=10 → should show OK
      expect(text).toContain("OK");
    });

    it("returns 'No inventory items found' when empty", async () => {
      mockInventoryGet.mockResolvedValueOnce({
        data: { data: { items: [], totalCount: 0 } },
      });

      const result = (await registeredTools["check_inventory"]({
        skip: 0,
        take: 50,
      })) as { content: Array<{ text: string }> };

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

    it("handles flat response (no data wrapper)", async () => {
      mockInventoryGet.mockResolvedValueOnce({
        data: { items: SAMPLE_ITEMS, totalCount: 2 },
      });

      const result = (await registeredTools["check_inventory"]({
        skip: 0,
        take: 50,
      })) as { content: Array<{ text: string }> };

      expect(result.content[0].text).toContain("Cà phê rang");
    });

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

      const result = (await registeredTools["check_inventory"]({
        skip: 0,
        take: 50,
      })) as { isError: boolean };

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

  // ── record_intake ────────────────────────────────────────────────────────

  describe("record_intake", () => {
    it("returns success message with intake details", async () => {
      mockInventoryPost.mockResolvedValueOnce({ data: { data: "inv-item-id-123" } });

      const result = (await registeredTools["record_intake"]({
        productId: PRODUCT_ID,
        amount: 100,
        notes: "Delivery from supplier A",
        unitCost: 500,
      })) as { content: Array<{ text: string }> };

      const text = result.content[0].text;
      expect(text).toContain("Stock intake recorded successfully");
      expect(text).toContain("+100");
      expect(text).toContain("inv-item-id-123");
    });

    it("omits optional fields from output when not provided", async () => {
      mockInventoryPost.mockResolvedValueOnce({ data: "inv-item-456" });

      const result = (await registeredTools["record_intake"]({
        productId: PRODUCT_ID,
        amount: 50,
      })) as { content: Array<{ text: string }> };

      const text = result.content[0].text;
      expect(text).toContain("+50");
      expect(text).not.toContain("Unit Cost");
      expect(text).not.toContain("Notes");
    });

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

      const result = (await registeredTools["record_intake"]({
        productId: PRODUCT_ID,
        amount: 10,
      })) as { isError: boolean };

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

  // ── record_usage ─────────────────────────────────────────────────────────

  describe("record_usage", () => {
    it("returns success message with usage details", async () => {
      mockInventoryPost.mockResolvedValueOnce({ data: {} });

      const result = (await registeredTools["record_usage"]({
        productId: PRODUCT_ID,
        amount: 20,
        notes: "Used in kitchen",
      })) as { content: Array<{ text: string }> };

      const text = result.content[0].text;
      expect(text).toContain("Stock usage recorded successfully");
      expect(text).toContain("-20");
      expect(text).toContain("Used in kitchen");
    });

    it("omits notes line when notes not provided", async () => {
      mockInventoryPost.mockResolvedValueOnce({ data: {} });

      const result = (await registeredTools["record_usage"]({
        productId: PRODUCT_ID,
        amount: 5,
      })) as { content: Array<{ text: string }> };

      expect(result.content[0].text).not.toContain("Notes");
    });

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

      const result = (await registeredTools["record_usage"]({
        productId: PRODUCT_ID,
        amount: 5,
      })) as { isError: boolean };

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

  // ── low_stock_alerts ─────────────────────────────────────────────────────

  describe("low_stock_alerts", () => {
    it("returns low stock items with deficit info", async () => {
      const lowItems = [
        { name: "Sữa tươi", quantity: 3, unit: "lít", reorderLevel: 10 },
      ];
      mockInventoryGet.mockResolvedValueOnce({
        data: { data: { items: lowItems, totalCount: 1 } },
      });

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

      const text = result.content[0].text;
      expect(text).toContain("Sữa tươi");
      expect(text).toContain("3 lít");
      expect(text).toContain("reorder at 10");
    });

    it("returns healthy message when no low-stock items", async () => {
      mockInventoryGet.mockResolvedValueOnce({
        data: { data: { items: [], totalCount: 0 } },
      });

      const result = (await registeredTools["low_stock_alerts"]({
        skip: 0,
        take: 50,
      })) as { content: Array<{ text: string }> };

      expect(result.content[0].text).toContain("All stock levels are healthy");
    });

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

      const result = (await registeredTools["low_stock_alerts"]({
        skip: 0,
        take: 50,
      })) as { isError: boolean };

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