// Mocks the API via cy.intercept — this job doesn't run a live backend (see
// .github/workflows/ci.yml); apps/api's own Mocha suite covers real API
// behavior against a real database.
//
// The favorite-toggle and delete-recipe journeys moved to recipes.feature —
// this file now only covers catalog browsing/display.

const authenticatedProfile = {
  id: 1,
  firstName: "Alice",
  lastName: "Martin",
  email: "alice@example.com",
  tokenVersion: 0,
  houseId: 1,
  dietId: null,
};

const vegetarien = { id: 1, key: "vegetarian" };
const _gluten = { id: 1, key: "gluten", kind: "INTOLERANCE" };
const oeufs = { id: 2, key: "eggs", kind: "ALLERGY" };

const ratatouille = {
  id: 1,
  name: "Ratatouille",
  description: null,
  picture: null,
  portions: 4,
  authorId: 1,
  visibility: "PERSONAL",
  allergens: [],
  diets: [vegetarien],
  isFavorite: true,
};

const omelette = {
  id: 2,
  name: "Omelette",
  description: null,
  picture: null,
  portions: 2,
  authorId: 1,
  visibility: "PERSONAL",
  allergens: [oeufs],
  diets: [],
  isFavorite: false,
};

const omeletteDetail = {
  ...omelette,
  description: "Une omelette toute simple.",
  ingredients: [
    {
      ingredient: {
        id: 10,
        key: "egg",
        icon: "EGG",
        category: "dairyAndCheese",
        subcategory: "eggs",
        allergens: [oeufs],
        diets: [],
      },
      quantity: 3,
      unit: { id: 1, key: "piece", type: "COUNT", toBaseFactor: 1 },
    },
  ],
  steps: [
    { id: 1, description: "Battre les œufs.", picture: null, order: 1, techSteps: [] },
    { id: 2, description: "Cuire à la poêle.", picture: null, order: 2, techSteps: [] },
  ],
};

function interceptAuth() {
  cy.intercept("GET", "**/auth/me", { statusCode: 200, body: authenticatedProfile });
  cy.intercept("GET", "**/profile/disliked-ingredients", { statusCode: 200, body: [] });
}

describe("Recipe catalog", () => {
  beforeEach(() => {
    interceptAuth();
  });

  it("defaults to the Favoris tab and lists its recipes", () => {
    cy.intercept("GET", /\/recipes\?/, (req) => {
      expect(req.url).to.include("tab=favoris");
      req.reply({ statusCode: 200, body: [ratatouille] });
    }).as("listRecipes");

    cy.visit("/recettes");
    cy.wait("@listRecipes");

    cy.contains("h1", "Recettes").should("be.visible");
    cy.get(".recipe-tabs__tab.active").should("contain.text", "Favoris");
    cy.contains(".recipe-table__name", "Ratatouille").should("be.visible");
    // The favorited row carries the ★ fav-mark.
    cy.contains(".recipe-table__name", "Ratatouille")
      .find(".recipe-table__fav-mark")
      .should("exist");
    cy.contains(".recipe-table__name", "Ratatouille")
      .parents("tr")
      .find(".diet-badge")
      .should("contain.text", "Végétarien");
  });

  it("shows the empty state when a tab has no recipes", () => {
    cy.intercept("GET", /\/recipes\?/, { statusCode: 200, body: [] });

    cy.visit("/recettes");

    cy.contains("Aucune recette pour le moment.").should("be.visible");
  });

  it("shows an error state when the catalog fails to load", () => {
    cy.intercept("GET", /\/recipes\?/, { statusCode: 500, body: { code: 5000, message: "boom" } });

    cy.visit("/recettes");

    cy.contains(".recipes-page__status--error", "Impossible de charger").should("be.visible");
  });

  it("switches tabs, re-fetching each one's own recipes", () => {
    cy.intercept("GET", /\/recipes\?/, (req) => {
      const tab = new URL(req.url).searchParams.get("tab");
      const body = tab === "perso" ? [omelette] : [ratatouille];
      req.reply({ statusCode: 200, body });
    }).as("listRecipes");

    cy.visit("/recettes");
    cy.wait("@listRecipes");
    cy.contains(".recipe-table__name", "Ratatouille").should("be.visible");

    // Not asserting the specific request URL here — React StrictMode (see
    // main.tsx) double-invokes mount/update effects in dev, so this can
    // legitimately fire twice; the rendered result converges either way
    // (same reasoning as planning-page.cy.ts's week-navigation tests).
    cy.contains(".recipe-tabs__tab", "Perso").click();
    cy.get(".recipe-tabs__tab.active").should("contain.text", "Perso");
    cy.contains(".recipe-table__name", "Omelette").should("be.visible");
    cy.contains(".recipe-table__name", "Ratatouille").should("not.exist");
  });

  it("searches within the active tab, debounced", () => {
    cy.intercept("GET", /\/recipes\?/, (req) => {
      const search = new URL(req.url).searchParams.get("search");
      req.reply({ statusCode: 200, body: search ? [omelette] : [ratatouille, omelette] });
    }).as("listRecipes");

    cy.visit("/recettes");
    cy.wait("@listRecipes");
    cy.contains(".recipe-table__name", "Ratatouille").should("be.visible");

    // Same "assert the rendered result, not the request count/URL" reasoning
    // as the tab-switch test above — the 300ms debounce plus StrictMode's
    // double-invoked effects make the exact number/order of requests an
    // implementation detail, not something worth pinning down here.
    cy.get(".recipes-page__search").type("Omel");
    cy.contains(".recipe-table__name", "Omelette").should("be.visible");
    cy.contains(".recipe-table__name", "Ratatouille").should("not.exist");
  });

  it("opens a recipe's detail alongside the table when its row is selected", () => {
    // Desktop-only master-detail layout, same reasoning as
    // planning-page.cy.ts's "Planning grid" tests — wider/taller than
    // Cypress's default 1000×660, which doesn't leave the detail panel
    // (photo + header + ingredients + steps) enough height to show
    // everything without needing its own internal scroll (by design, see
    // `.recipe-detail-panel` in recipes.scss) — this test asserts full
    // visibility without scrolling, so it needs the room.
    cy.viewport(1600, 900);
    cy.intercept("GET", /\/recipes\?/, { statusCode: 200, body: [ratatouille, omelette] });
    cy.intercept("GET", "**/recipes/2", { statusCode: 200, body: omeletteDetail }).as("getRecipe");

    cy.visit("/recettes");
    cy.contains(".recipe-table__name", "Omelette").click();
    cy.wait("@getRecipe");

    cy.url().should("include", "/recettes/2");
    // The table stays mounted (master-detail, not a page navigation) —
    // both rows are still visible next to the detail panel.
    cy.contains(".recipe-table__name", "Ratatouille").should("be.visible");
    cy.get("tr.selected .recipe-table__name").should("contain.text", "Omelette");

    cy.get(".recipe-detail-panel").within(() => {
      cy.contains("h2", "Omelette").should("be.visible");
      cy.contains("Une omelette toute simple.").should("be.visible");
      cy.contains("Battre les œufs.").should("be.visible");
      cy.contains("Cuire à la poêle.").should("be.visible");
    });
  });

  it("shows a not-found message for a selected id the API rejects", () => {
    cy.intercept("GET", /\/recipes\?/, { statusCode: 200, body: [] });
    cy.intercept("GET", "**/recipes/999", {
      statusCode: 404,
      // ErrorCode.RECIPE_NOT_FOUND (packages/shared/src/errors/error-codes.ts)
      // — RecipeDetailPanel only renders the "not found" message for this
      // exact code, anything else falls into its generic error state.
      body: { code: 4045, message: "not found" },
    });

    cy.visit("/recettes/999");

    cy.contains(".recipe-detail-panel", "Cette recette n'existe pas.").should("be.visible");
  });

  it("links the new-recipe button to the recipe form", () => {
    cy.intercept("GET", /\/recipes\?/, { statusCode: 200, body: [] });

    cy.visit("/recettes");

    cy.contains(".recipes-page__new-button", "Nouvelle recette").should(
      "have.attr",
      "href",
      "/recettes/nouvelle",
    );
  });
});
