import { expect, Page } from "@playwright/test";
import { userTest as test } from "../fixtures";
import {
  goToShopping,
  addToShoppingList,
  markShoppingItemCompleted,
  removeFromShoppingList,
  loginByEmailAndPassword,
  addRecipeToMealPlan,
  goToRecipes,
  getShoppingItemCompletedQuantiy as getCompletedQuantity,
  getQuantitySuggestions,
  completeShopping,
  goToLeftovers,
  markLeftoverAsLost,
} from "../helpers";
import openIngredientBottomSheet from "../helpers/ingredient/openIngredientBottomSheet";
import markIngredientAsInStock from "../helpers/ingredient/markIngredientAsInStock";
import specifyCompletedQuantity from "../helpers/shopping-list/specifyCompletedQuantiy";

test("user can add ingredient to shopping list and mark it as completed", async ({
  page,
  user,
}) => {
  await user.kitchen.cleanMealPlans();

  await loginByEmailAndPassword(page, user.email, user.password);

  // Navigate to shopping section
  await goToShopping(page);

  // Add ingredient to shopping list
  await addToShoppingList(page, "Говядина");

  // Mark ingredient as completed
  await markShoppingItemCompleted(page, "Говядина");

  // Specify completed quantity
  await specifyCompletedQuantity(page, "Говядина", "Граммы", 500);

  // Check specified quantity
  expect(await getCompletedQuantity(page, "Говядина")).toBe("500 г");
});

test("user can remove ingredient from shopping list", async ({
  page,
  user,
}) => {
  await user.kitchen.cleanMealPlans();

  await loginByEmailAndPassword(page, user.email, user.password);

  // Navigate to shopping section
  await goToShopping(page);

  // Add ingredient to shopping list
  await addToShoppingList(page, "Курица");

  // Verify ingredient was added
  await expect(page.getByText("Курица")).toBeVisible({ timeout: 5000 });

  // Mark ingredient as completed (so we can test removal from completed section)
  await markShoppingItemCompleted(page, "Курица");

  // Specify completed quantity
  await specifyCompletedQuantity(page, "Курица", "Граммы", 300);

  // Check specified quantity
  expect(await getCompletedQuantity(page, "Курица")).toBe("300 г");

  // Remove ingredient from shopping list
  await removeFromShoppingList(page, "Курица");

  // Verify ingredient is no longer visible in the shopping list
  await expect(page.getByText("Курица")).not.toBeVisible({ timeout: 5000 });
});

test("user can add recipe to plan and see only missing ingredients in shopping list", async ({
  page,
  user,
}) => {
  // Clean meal plans
  await user.kitchen.cleanMealPlans();

  // Create a test recipe with known ingredients
  const recipeTitle = "Тестовое блюдо для покупок";
  await user.createRecipe({
    title: recipeTitle,
    servings: 2,
    ingredients: [
      ["Говядина", "WEIGHT", 300], // Will add exact match (300g)
      ["Яйцо куриное", "QUANTITY", 4], // Will add half (2)
      ["Помидоры", "WEIGHT", 200], // Will add rough "left some"
      ["Молоко", "VOLUME", 0.5], // Will add in different unit (grams instead of liters)
      "Соль", // Will add specific quantity (500g)
      "Перец чёрный", // Will add rough "left some"
    ],
    dishTypes: ["Main course"],
  });

  // Login to the app
  await loginByEmailAndPassword(page, user.email, user.password);

  // Add recipe to meal plan via UI (navigates to journal and clicks search icon)
  await addRecipeToMealPlan(page, recipeTitle, "Ужин");

  // Navigate to shopping list
  await goToShopping(page);

  // Missing quantities are shown for ingredients that have them
  await expect(getItem(page, "Говядина")).toContainText(recipeTitle);
  await expect(getItem(page, "Говядина")).toContainText("300 г");

  await expect(getItem(page, "Яйцо куриное")).toContainText(recipeTitle);
  await expect(getItem(page, "Яйцо куриное")).toContainText("4 шт");

  await expect(getItem(page, "Помидоры")).toContainText(recipeTitle);
  await expect(getItem(page, "Помидоры")).toContainText("200 г");

  await expect(getItem(page, "Молоко")).toContainText(recipeTitle);
  await expect(getItem(page, "Молоко")).toContainText("500 мл");

  // All ingredients should appear in the shopping list with recipe context
  await expect(getItem(page, "Соль")).toContainText(recipeTitle);
  await expect(getItem(page, "Перец чёрный")).toContainText(recipeTitle);

  // Navigate to recipes to access the created recipe
  await goToRecipes(page);
  await page
    .getByRole("button", {
      name: recipeTitle,
    })
    .click();

  // Wait for recipe details page to load
  await expect(page.getByRole("heading", { name: recipeTitle })).toBeVisible();

  // Set quantity for "Говядина" (300g)
  await openIngredientBottomSheet(page, "Говядина");
  await markIngredientAsInStock(page, "Говядина", "Граммы", 300);

  // Set quantity for "Яйцо куриное" (2)
  await openIngredientBottomSheet(page, "Яйцо куриное");
  await markIngredientAsInStock(page, "Яйцо куриное", "Штуки", 2);

  // Set quantity for "Помидоры" (left some)
  await openIngredientBottomSheet(page, "Помидоры");
  await markIngredientAsInStock(page, "Помидоры", "Примерно");

  // Set quantity for "Молоко" (1000g)
  await openIngredientBottomSheet(page, "Молоко");
  await markIngredientAsInStock(page, "Молоко", "Граммы", 1000);

  // Set quantity for "Соль" (500g)
  await openIngredientBottomSheet(page, "Соль");
  await markIngredientAsInStock(page, "Соль", "Граммы", 500);

  // Set quantity for "Перец чёрный" (left some)
  await openIngredientBottomSheet(page, "Перец чёрный");
  await markIngredientAsInStock(page, "Перец чёрный", "Примерно");

  // Navigate to shopping list
  await goToShopping(page);

  // Verify that only missing ingredients appear in the shopping list
  await expect(getItem(page, "Яйцо куриное")).toContainText(recipeTitle);
  await expect(getItem(page, "Яйцо куриное")).toContainText("2 шт");
  await expect(getItem(page, "Говядина")).not.toBeVisible();
  await expect(getItem(page, "Помидоры")).not.toBeVisible();
  await expect(getItem(page, "Молоко")).not.toBeVisible();
  await expect(getItem(page, "Соль")).not.toBeVisible();
  await expect(getItem(page, "Перец чёрный")).not.toBeVisible();
});

test("user sees quantity suggestions in shopping list", async ({
  page,
  user,
}) => {
  test.setTimeout(120_000);
  // Clean meal plans
  await user.kitchen.cleanMealPlans();

  // Create a test recipe with known ingredients
  const recipeTitle = "Тестовое блюдо для проверки подсказок";
  await user.createRecipe({
    title: recipeTitle,
    servings: 2,
    ingredients: [
      ["Говядина", "WEIGHT", 300],
      ["Яйцо куриное", "QUANTITY", 2],
      ["Молоко", "VOLUME", 0.5],
      ["Помидоры", "WEIGHT", 200],
      "Соль",
      "Перец чёрный",
    ],
    dishTypes: ["Main course"],
  });

  // Login to the app
  await loginByEmailAndPassword(page, user.email, user.password);

  // ------------------------------------------------------------------
  // Round 1: seed shopping history with the recipe's ingredients plus a
  // manually-added ingredient (Морковь) that isn't part of any recipe.
  // ------------------------------------------------------------------
  await addRecipeToMealPlan(page, recipeTitle, "Ужин");
  await goToShopping(page);

  // Морковь is not part of the recipe - add it manually
  await addToShoppingList(page, "Морковь");

  // Говядина - specify exact recipe quantity (300г)
  await markShoppingItemCompleted(page, "Говядина");
  expect(await getCompletedQuantity(page, "Говядина")).toBeUndefined();
  await specifyCompletedQuantity(page, "Говядина", "Граммы", 300);
  expect(await getCompletedQuantity(page, "Говядина")).toBe("300 г");

  // Яйцо куриное - specify excess (10шт vs recipe's 2)
  await markShoppingItemCompleted(page, "Яйцо куриное");
  expect(await getCompletedQuantity(page, "Яйцо куриное")).toBeUndefined();
  await specifyCompletedQuantity(page, "Яйцо куриное", "Штуки", 10);
  expect(await getCompletedQuantity(page, "Яйцо куриное")).toBe("10 шт");

  // Молоко - specify 500г (different unit than recipe's 500мл)
  await markShoppingItemCompleted(page, "Молоко");
  expect(await getCompletedQuantity(page, "Молоко")).toBeUndefined();
  await specifyCompletedQuantity(page, "Молоко", "Граммы", 500);
  expect(await getCompletedQuantity(page, "Молоко")).toBe("500 г");

  // Помидоры - specify 200г (exact recipe quantity)
  await markShoppingItemCompleted(page, "Помидоры");
  expect(await getCompletedQuantity(page, "Помидоры")).toBeUndefined();
  await specifyCompletedQuantity(page, "Помидоры", "Граммы", 200);
  expect(await getCompletedQuantity(page, "Помидоры")).toBe("200 г");

  // Соль - not in recipe, specify 500г
  await markShoppingItemCompleted(page, "Соль");
  expect(await getCompletedQuantity(page, "Соль")).toBeUndefined();
  await specifyCompletedQuantity(page, "Соль", "Граммы", 500);
  expect(await getCompletedQuantity(page, "Соль")).toBe("500 г");

  // Перец чёрный - leave unspecified (will be filtered out of future suggestions)
  await markShoppingItemCompleted(page, "Перец чёрный");
  expect(await getCompletedQuantity(page, "Перец чёрный")).toBeUndefined();

  // Морковь (manual) - specify 400г
  await markShoppingItemCompleted(page, "Морковь");
  expect(await getCompletedQuantity(page, "Морковь")).toBeUndefined();
  await specifyCompletedQuantity(page, "Морковь", "Граммы", 400);
  expect(await getCompletedQuantity(page, "Морковь")).toBe("400 г");

  // Complete shopping to record history, then reset stock
  await completeShopping(page);
  await goToLeftovers(page);
  await markLeftoverAsLost(page, "Говядина");
  await markLeftoverAsLost(page, "Яйцо куриное");
  await markLeftoverAsLost(page, "Молоко");
  await markLeftoverAsLost(page, "Помидоры");
  await markLeftoverAsLost(page, "Соль");
  await markLeftoverAsLost(page, "Перец чёрный");
  await markLeftoverAsLost(page, "Морковь");

  // ------------------------------------------------------------------
  // Round 2: plan the recipe a second time (required quantities = 2x)
  // so we can verify auto-apply respects sufficiency against the history
  // from round 1, and extend history with new purchases.
  // ------------------------------------------------------------------
  await addRecipeToMealPlan(page, recipeTitle, "Ужин");
  await goToShopping(page);

  await addToShoppingList(page, "Морковь");

  // Говядина - recipe needs 600г (2x300g), previously bought 300г
  // 300г is below required amount so not auto-applied
  await markShoppingItemCompleted(page, "Говядина");
  expect(await getCompletedQuantity(page, "Говядина")).toBeUndefined();
  // Buy a larger, distinct amount so round 3 has two unique history entries
  await specifyCompletedQuantity(page, "Говядина", "Граммы", 500);
  expect(await getCompletedQuantity(page, "Говядина")).toBe("500 г");

  // Яйцо куриное - recipe needs 4шт (2x2), previously bought 10шт
  // 10шт >= 4шт so suggestion is auto-applied
  await markShoppingItemCompleted(page, "Яйцо куриное");
  expect(await getCompletedQuantity(page, "Яйцо куриное")).toBe("10 шт");
  // Override the auto-applied value to add a distinct history entry
  await specifyCompletedQuantity(page, "Яйцо куриное", "Штуки", 8);
  expect(await getCompletedQuantity(page, "Яйцо куриное")).toBe("8 шт");

  // Молоко - recipe needs 1000мл (2x500ml), previously bought 500г (different unit!)
  // Cross-unit comparison can't prove sufficiency, so not auto-applied
  await markShoppingItemCompleted(page, "Молоко");
  expect(await getCompletedQuantity(page, "Молоко")).toBeUndefined();
  // This time buy in the recipe's unit so the suggestion survives round 3
  await specifyCompletedQuantity(page, "Молоко", "Миллилитры", 500);
  expect(await getCompletedQuantity(page, "Молоко")).toBe("500 мл");

  // Помидоры - recipe needs 400г (2x200g), previously bought 200г
  // 200г is below required amount so not auto-applied
  await markShoppingItemCompleted(page, "Помидоры");
  expect(await getCompletedQuantity(page, "Помидоры")).toBeUndefined();
  // Buy 200г again (same as round 1) - exercises dedup in round 3 (#94)
  await specifyCompletedQuantity(page, "Помидоры", "Граммы", 200);
  expect(await getCompletedQuantity(page, "Помидоры")).toBe("200 г");

  // Соль - no recipe requirement, previously bought 500г → auto-applied
  await markShoppingItemCompleted(page, "Соль");
  expect(await getCompletedQuantity(page, "Соль")).toBe("500 г");
  // Keep 500г (same as round 1) - exercises dedup in round 3 (#94)
  await specifyCompletedQuantity(page, "Соль", "Граммы", 500);
  expect(await getCompletedQuantity(page, "Соль")).toBe("500 г");

  // Перец чёрный - no recipe requirement, round 1 was unspecified so no suggestion
  await markShoppingItemCompleted(page, "Перец чёрный");
  expect(await getCompletedQuantity(page, "Перец чёрный")).toBeUndefined();
  // Record a first concrete quantity so round 3 has something to suggest
  await specifyCompletedQuantity(page, "Перец чёрный", "Граммы", 10);
  expect(await getCompletedQuantity(page, "Перец чёрный")).toBe("10 г");

  // Морковь (manual) - not in any recipe, no preselected quantity expected
  await markShoppingItemCompleted(page, "Морковь");
  expect(await getCompletedQuantity(page, "Морковь")).toBeUndefined();
  // Buy a different amount than round 1 (250г vs 400г)
  await specifyCompletedQuantity(page, "Морковь", "Граммы", 250);
  expect(await getCompletedQuantity(page, "Морковь")).toBe("250 г");

  // Complete shopping to record history, then reset stock
  await completeShopping(page);
  await goToLeftovers(page);
  await markLeftoverAsLost(page, "Говядина");
  await markLeftoverAsLost(page, "Яйцо куриное");
  await markLeftoverAsLost(page, "Молоко");
  await markLeftoverAsLost(page, "Помидоры");
  await markLeftoverAsLost(page, "Соль");
  await markLeftoverAsLost(page, "Перец чёрный");
  await markLeftoverAsLost(page, "Морковь");

  // ------------------------------------------------------------------
  // Round 3: clear meal plans and re-plan once (1x requirements) so the
  // widest possible set of past purchases qualifies as suggestions.
  // Then verify suggestions via the modal — including dedup behavior
  // from issue #94 and full-history behavior for the manual ingredient.
  // ------------------------------------------------------------------
  await user.kitchen.cleanMealPlans();
  await addRecipeToMealPlan(page, recipeTitle, "Ужин");
  await goToShopping(page);

  await addToShoppingList(page, "Морковь");

  // Mark all items as completed so we can open the edit-quantity modal
  await markShoppingItemCompleted(page, "Говядина");
  await markShoppingItemCompleted(page, "Яйцо куриное");
  await markShoppingItemCompleted(page, "Молоко");
  await markShoppingItemCompleted(page, "Помидоры");
  await markShoppingItemCompleted(page, "Соль");
  await markShoppingItemCompleted(page, "Перец чёрный");
  await markShoppingItemCompleted(page, "Морковь");

  // Говядина - history [300г, 500г], both ≥ 300г recipe requirement
  expect(await getQuantitySuggestions(page, "Говядина")).toEqual([
    "300 г",
    "500 г",
  ]);

  // Яйцо куриное - history [10шт, 8шт], both ≥ 2шт recipe requirement
  // Sorted ascending
  expect(await getQuantitySuggestions(page, "Яйцо куриное")).toEqual([
    "8 шт",
    "10 шт",
  ]);

  // Молоко - history [500г WEIGHT, 500мл VOLUME], recipe requires 500мл.
  // Milk has a known g↔ml ratio so both purchases cover the requirement.
  expect(await getQuantitySuggestions(page, "Молоко")).toEqual([
    "500 г",
    "500 мл",
  ]);

  // Помидоры - history [200г, 200г], same quantity must appear only once (#94)
  expect(await getQuantitySuggestions(page, "Помидоры")).toEqual(["200 г"]);

  // Соль - history [500г, 500г], same quantity must appear only once (#94)
  expect(await getQuantitySuggestions(page, "Соль")).toEqual(["500 г"]);

  // Перец чёрный - round 1 unspecified (filtered out), round 2 = 10г
  expect(await getQuantitySuggestions(page, "Перец чёрный")).toEqual(["10 г"]);

  // Морковь (manual, not in any recipe):
  //  - No preselected quantity — auto-apply should not fire for ingredients
  //    that aren't tied to any recipe requirement
  //  - Full shopping history shows as suggestions, unfiltered
  expect(await getCompletedQuantity(page, "Морковь")).toBeUndefined();
  expect(await getQuantitySuggestions(page, "Морковь")).toEqual([
    "250 г",
    "400 г",
  ]);
});

function getItem(page: Page, ingredientName: string) {
  return page.getByRole("listitem", { name: ingredientName });
}
