import { ErrorCode, type SignupInput } from "@batch-cooking/shared";
import { faker } from "@faker-js/faker";
import { expect } from "chai";
import request from "supertest";
import { createApp } from "../src/app.js";
import { prisma } from "../src/db/prisma.js";
import { resetDatabase } from "../test-support/reset-db.js";

function buildSignupPayload(): SignupInput {
  const firstName = faker.person.firstName();
  const lastName = faker.person.lastName();
  return {
    firstName,
    lastName,
    email: faker.internet.email({ firstName, lastName }).toLowerCase(),
    password: faker.internet.password({ length: 16 }),
  };
}

describe("Profile", () => {
  const app = createApp();

  beforeEach(async () => {
    await resetDatabase();
  });

  after(async () => {
    await prisma.$disconnect();
  });

  describe("PATCH /profile/diet", () => {
    it("rejects requests without a session cookie with 401 NOT_AUTHENTICATED", async () => {
      const res = await request(app).patch("/profile/diet").send({ dietId: 1 });

      expect(res.status).to.equal(401);
      expect(res.body.code).to.equal(ErrorCode.NOT_AUTHENTICATED);
    });

    it("sets the profile's regime to a valid, seeded diet", async () => {
      const agent = request.agent(app);
      await agent.post("/auth/signup").send(buildSignupPayload());
      const diet = await prisma.diet.findFirstOrThrow({
        where: { key: "vegetarian" },
      });

      const res = await agent.patch("/profile/diet").send({ dietId: diet.id });

      expect(res.status).to.equal(200);
      expect(res.body.dietId).to.equal(diet.id);
    });

    it("clears the regime when dietId is null", async () => {
      const agent = request.agent(app);
      await agent.post("/auth/signup").send(buildSignupPayload());
      const diet = await prisma.diet.findFirstOrThrow({ where: { key: "vegan" } });
      await agent.patch("/profile/diet").send({ dietId: diet.id });

      const res = await agent.patch("/profile/diet").send({ dietId: null });

      expect(res.status).to.equal(200);
      expect(res.body.dietId).to.equal(null);
    });

    it("rejects an unknown dietId with 404 DIET_NOT_FOUND", async () => {
      const agent = request.agent(app);
      await agent.post("/auth/signup").send(buildSignupPayload());

      const res = await agent.patch("/profile/diet").send({ dietId: 999_999 });

      expect(res.status).to.equal(404);
      expect(res.body.code).to.equal(ErrorCode.DIET_NOT_FOUND);
    });
  });

  describe("GET /profile/allergies + PATCH /profile/allergies", () => {
    it("rejects requests without a session cookie with 401 NOT_AUTHENTICATED", async () => {
      const getRes = await request(app).get("/profile/allergies");
      const patchRes = await request(app).patch("/profile/allergies").send({ allergyIds: [] });

      expect(getRes.status).to.equal(401);
      expect(patchRes.status).to.equal(401);
    });

    it("starts empty, then reflects a saved selection", async () => {
      const agent = request.agent(app);
      await agent.post("/auth/signup").send(buildSignupPayload());
      const allergies = await prisma.allergy.findMany({ include: { category: true } });
      const peanuts = allergies.find((a) => a.category.key === "peanuts");
      const gluten = allergies.find((a) => a.category.key === "gluten");
      if (!peanuts || !gluten) throw new Error("expected seeded allergens missing");

      const initial = await agent.get("/profile/allergies");
      expect(initial.body).to.deep.equal([]);

      const patchRes = await agent
        .patch("/profile/allergies")
        .send({ allergyIds: [peanuts.id, gluten.id] });
      expect(patchRes.status).to.equal(200);
      expect(patchRes.body.sort()).to.deep.equal([peanuts.id, gluten.id].sort());

      const refetch = await agent.get("/profile/allergies");
      expect(refetch.body.sort()).to.deep.equal([peanuts.id, gluten.id].sort());
    });

    it("replaces (not merges) the previous selection", async () => {
      const agent = request.agent(app);
      await agent.post("/auth/signup").send(buildSignupPayload());
      const allergies = await prisma.allergy.findMany({ include: { category: true } });
      const peanuts = allergies.find((a) => a.category.key === "peanuts");
      const gluten = allergies.find((a) => a.category.key === "gluten");
      if (!peanuts || !gluten) throw new Error("expected seeded allergens missing");

      await agent.patch("/profile/allergies").send({ allergyIds: [peanuts.id] });
      await agent.patch("/profile/allergies").send({ allergyIds: [gluten.id] });

      const res = await agent.get("/profile/allergies");
      expect(res.body).to.deep.equal([gluten.id]);
    });

    it("rejects an unknown allergyId with 404 ALLERGY_NOT_FOUND", async () => {
      const agent = request.agent(app);
      await agent.post("/auth/signup").send(buildSignupPayload());

      const res = await agent.patch("/profile/allergies").send({ allergyIds: [999_999] });

      expect(res.status).to.equal(404);
      expect(res.body.code).to.equal(ErrorCode.ALLERGY_NOT_FOUND);
    });
  });

  describe("GET /profile/disliked-ingredients + PATCH /profile/disliked-ingredients", () => {
    it("rejects requests without a session cookie with 401 NOT_AUTHENTICATED", async () => {
      const getRes = await request(app).get("/profile/disliked-ingredients");
      const patchRes = await request(app)
        .patch("/profile/disliked-ingredients")
        .send({ dislikedIngredientIds: [] });

      expect(getRes.status).to.equal(401);
      expect(patchRes.status).to.equal(401);
    });

    it("starts empty, then reflects a saved selection", async () => {
      const agent = request.agent(app);
      await agent.post("/auth/signup").send(buildSignupPayload());
      const tomate = await prisma.ingredient.findFirstOrThrow({
        where: { key: "tomato" },
      });
      const oignon = await prisma.ingredient.findFirstOrThrow({
        where: { key: "onion" },
      });

      const initial = await agent.get("/profile/disliked-ingredients");
      expect(initial.body).to.deep.equal([]);

      const patchRes = await agent
        .patch("/profile/disliked-ingredients")
        .send({ dislikedIngredientIds: [tomate.id, oignon.id] });
      expect(patchRes.status).to.equal(200);
      expect(patchRes.body.sort()).to.deep.equal([tomate.id, oignon.id].sort());

      const refetch = await agent.get("/profile/disliked-ingredients");
      expect(refetch.body.sort()).to.deep.equal([tomate.id, oignon.id].sort());
    });

    it("replaces (not merges) the previous selection", async () => {
      const agent = request.agent(app);
      await agent.post("/auth/signup").send(buildSignupPayload());
      const tomate = await prisma.ingredient.findFirstOrThrow({
        where: { key: "tomato" },
      });
      const oignon = await prisma.ingredient.findFirstOrThrow({
        where: { key: "onion" },
      });

      await agent
        .patch("/profile/disliked-ingredients")
        .send({ dislikedIngredientIds: [tomate.id] });
      await agent
        .patch("/profile/disliked-ingredients")
        .send({ dislikedIngredientIds: [oignon.id] });

      const res = await agent.get("/profile/disliked-ingredients");
      expect(res.body).to.deep.equal([oignon.id]);
    });

    it("rejects an unknown ingredientId with 404 INGREDIENT_NOT_FOUND", async () => {
      const agent = request.agent(app);
      await agent.post("/auth/signup").send(buildSignupPayload());

      const res = await agent
        .patch("/profile/disliked-ingredients")
        .send({ dislikedIngredientIds: [999_999] });

      expect(res.status).to.equal(404);
      expect(res.body.code).to.equal(ErrorCode.INGREDIENT_NOT_FOUND);
    });
  });
});
