/**
 * EN: Unit tests for catalog tools (list_products, create_product, update_product, delete_product).
 * VI: Unit tests cho catalog tools.
 */
import { describe, it, expect, vi, beforeEach, type Mock } from "vitest";

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

vi.mock("../services/api-client.js", () => ({
  catalogApi: {
    get: mockCatalogGet,
    post: mockCatalogPost,
    put: mockCatalogPut,
    delete: mockCatalogDelete,
  },
  inventoryApi: { get: vi.fn(), post: vi.fn() },
  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 { registerCatalogTools } from "../tools/catalog-tools.js";

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

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

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

  // ── list_products ────────────────────────────────────────────────────────

  describe("list_products", () => {
    it("returns formatted product table when products exist", async () => {
      mockCatalogGet.mockResolvedValueOnce({
        data: {
          items: [
            { name: "Cà phê sữa", price: 35000, categoryName: "Beverages", isActive: true },
            { name: "Bánh mì", price: 20000, categoryName: "Food", isActive: false },
          ],
          totalCount: 2,
        },
      });

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

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

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

      const result = (await registeredTools["list_products"]({
        page: 1,
        pageSize: 20,
      })) as { content: Array<{ text: string }> };

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

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

      const result = (await registeredTools["list_products"]({
        page: 1,
        pageSize: 20,
      })) as { content: Array<{ text: string }>; isError: boolean };

      expect(result.isError).toBe(true);
      expect(result.content[0].text).toContain("Error:");
    });

    it("handles items nested in data.data wrapper", async () => {
      mockCatalogGet.mockResolvedValueOnce({
        data: {
          data: {
            items: [{ name: "Trà đào", price: 40000, categoryName: "Beverages", isActive: true }],
            totalCount: 1,
          },
        },
      });

      const result = (await registeredTools["list_products"]({
        page: 1,
        pageSize: 20,
      })) as { content: Array<{ text: string }> };

      expect(result.content[0].text).toContain("Trà đào");
    });
  });

  // ── create_product ───────────────────────────────────────────────────────

  describe("create_product", () => {
    it("returns success message with product ID", async () => {
      mockCatalogPost.mockResolvedValueOnce({ data: PRODUCT_ID });

      const result = (await registeredTools["create_product"]({
        name: "Sinh tố xoài",
        price: 45000,
        type: "Beverage",
      })) as { content: Array<{ text: string }> };

      expect(result.content[0].text).toContain("Product created successfully");
      expect(result.content[0].text).toContain(PRODUCT_ID);
      expect(result.content[0].text).toContain("Sinh tố xoài");
    });

    it("extracts ID from data.data wrapper", async () => {
      mockCatalogPost.mockResolvedValueOnce({ data: { data: PRODUCT_ID } });

      const result = (await registeredTools["create_product"]({
        name: "Test product",
        price: 10000,
      })) as { content: Array<{ text: string }> };

      expect(result.content[0].text).toContain(PRODUCT_ID);
    });

    it("returns error response on API failure", async () => {
      const axiosError = { isAxiosError: true, response: { status: 400, data: { errors: ["Name required"] } } };
      mockCatalogPost.mockRejectedValueOnce(axiosError);

      const result = (await registeredTools["create_product"]({
        name: "Bad",
        price: 100,
      })) as { isError: boolean; content: Array<{ text: string }> };

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

  // ── update_product ───────────────────────────────────────────────────────

  describe("update_product", () => {
    it("returns success message listing updated fields", async () => {
      mockCatalogGet.mockResolvedValueOnce({
        data: { name: "Old Name", price: 30000, type: "Beverage", shopId: SHOP_ID },
      });
      mockCatalogPut.mockResolvedValueOnce({ data: {} });

      const result = (await registeredTools["update_product"]({
        productId: PRODUCT_ID,
        name: "New Name",
        price: 50000,
      })) as { content: Array<{ text: string }> };

      expect(result.content[0].text).toContain("updated successfully");
      expect(result.content[0].text).toContain("name");
      expect(result.content[0].text).toContain("New Name");
    });

    it("returns 'No fields provided' when no update fields given", async () => {
      const result = (await registeredTools["update_product"]({
        productId: PRODUCT_ID,
      })) as { content: Array<{ text: string }> };

      expect(result.content[0].text).toContain("No fields provided");
    });

    it("returns error response when fetch fails", async () => {
      mockCatalogGet.mockRejectedValueOnce(new Error("Not found"));

      const result = (await registeredTools["update_product"]({
        productId: PRODUCT_ID,
        name: "New Name",
      })) as { isError: boolean };

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

  // ── delete_product ───────────────────────────────────────────────────────

  describe("delete_product", () => {
    it("returns success message after deactivation", async () => {
      mockCatalogDelete.mockResolvedValueOnce({ data: {} });

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

      expect(result.content[0].text).toContain("deactivated successfully");
      expect(result.content[0].text).toContain(PRODUCT_ID);
    });

    it("returns error response when API call fails", async () => {
      mockCatalogDelete.mockRejectedValueOnce(new Error("Server error"));

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

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