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

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

vi.mock("../services/api-client.js", () => ({
  catalogApi: { get: vi.fn(), post: vi.fn(), put: vi.fn(), delete: vi.fn() },
  inventoryApi: { get: vi.fn(), post: vi.fn() },
  fnbApi: {
    get: mockFnbGet,
    post: mockFnbPost,
  },
  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 { registerRecipeTools } from "../tools/recipe-tools.js";

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

const SAMPLE_RECIPES = [
  {
    id: RECIPE_ID,
    name: "Cà phê sữa đá",
    productId: PRODUCT_ID,
    shopId: SHOP_ID,
    prepTimeMinutes: 3,
    isActive: true,
    createdAt: "2026-01-01T00:00:00Z",
    ingredients: [
      { id: "ing1", ingredientName: "Cà phê", quantity: 20, unit: "g", costPerUnit: 500, quantityPerServing: 20 },
      { id: "ing2", ingredientName: "Sữa đặc", quantity: 30, unit: "ml", costPerUnit: 50, quantityPerServing: 30 },
    ],
  },
];

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

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

  // ── list_recipes ─────────────────────────────────────────────────────────

  describe("list_recipes", () => {
    it("returns formatted list of recipes with ingredients", async () => {
      mockFnbGet.mockResolvedValueOnce({ data: { data: SAMPLE_RECIPES } });

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

      const text = result.content[0].text;
      expect(text).toContain("Cà phê sữa đá");
      expect(text).toContain("3 min");
      expect(text).toContain("Cà phê");
      expect(text).toContain("Sữa đặc");
    });

    it("handles flat array response (no data wrapper)", async () => {
      mockFnbGet.mockResolvedValueOnce({ data: SAMPLE_RECIPES });

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

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

    it("shows recipe as inactive when isActive is false", async () => {
      const inactiveRecipes = [{ ...SAMPLE_RECIPES[0], isActive: false }];
      mockFnbGet.mockResolvedValueOnce({ data: { data: inactiveRecipes } });

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

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

    it("shows '(no ingredients)' when recipe has no ingredients", async () => {
      const recipesNoIngredients = [{ ...SAMPLE_RECIPES[0], ingredients: [] }];
      mockFnbGet.mockResolvedValueOnce({ data: { data: recipesNoIngredients } });

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

      expect(result.content[0].text).toContain("(no ingredients)");
    });

    it("returns 'No recipes found' when list is empty", async () => {
      mockFnbGet.mockResolvedValueOnce({ data: { data: [] } });

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

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

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

      const result = (await registeredTools["list_recipes"]({
        shopId: SHOP_ID,
      })) as { isError: boolean };

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

  // ── create_recipe ────────────────────────────────────────────────────────

  describe("create_recipe", () => {
    const validIngredients = [
      { ingredientName: "Cà phê", quantity: 20, unit: "g", costPerUnit: 500, quantityPerServing: 20 },
    ];

    it("returns success message with recipe ID and ingredient count", async () => {
      mockFnbPost.mockResolvedValueOnce({ data: { data: RECIPE_ID } });

      const result = (await registeredTools["create_recipe"]({
        shopId: SHOP_ID,
        productId: PRODUCT_ID,
        name: "Cà phê đen",
        prepTimeMinutes: 2,
        ingredients: validIngredients,
      })) as { content: Array<{ text: string }> };

      const text = result.content[0].text;
      expect(text).toContain("Recipe created successfully");
      expect(text).toContain(RECIPE_ID);
      expect(text).toContain("Cà phê đen");
      expect(text).toContain("2 min");
      expect(text).toContain("Cà phê");
    });

    it("includes instructions in request body when provided", async () => {
      mockFnbPost.mockResolvedValueOnce({ data: RECIPE_ID });

      await registeredTools["create_recipe"]({
        shopId: SHOP_ID,
        productId: PRODUCT_ID,
        name: "Test Recipe",
        instructions: "Pha cà phê với nước sôi 90 độ",
        prepTimeMinutes: 5,
        ingredients: validIngredients,
      });

      expect(mockFnbPost).toHaveBeenCalledWith(
        "/kitchen/recipes",
        expect.objectContaining({ instructions: "Pha cà phê với nước sôi 90 độ" }),
      );
    });

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

      await registeredTools["create_recipe"]({
        productId: PRODUCT_ID,
        name: "Test",
        prepTimeMinutes: 5,
        ingredients: validIngredients,
      });

      expect(mockFnbPost).toHaveBeenCalledWith(
        "/kitchen/recipes",
        expect.objectContaining({ shopId: "00000000-0000-0000-0000-000000000001" }),
      );
    });

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

      const result = (await registeredTools["create_recipe"]({
        shopId: SHOP_ID,
        productId: PRODUCT_ID,
        name: "Bad Recipe",
        prepTimeMinutes: 1,
        ingredients: validIngredients,
      })) as { isError: boolean };

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