import "./setup-env.js";
import { describe, expect, it, beforeAll, afterAll } from "vitest";
import { eq, inArray } from "drizzle-orm";
import { buildServer } from "../src/server.js";
import { loadConfig } from "../src/config.js";
import { createDatabase, closeDatabase, schema } from "@app/database";

describe("S5 — onboarding → minne + smaksignaler", () => {
  const testDb = createDatabase(process.env.TEST_DATABASE_URL!);
  const config = loadConfig();
  let app: Awaited<ReturnType<typeof buildServer>>;

  async function cleanupUser(email: string) {
    const existing = await testDb.db
      .select({ id: schema.users.id })
      .from(schema.users)
      .where(inArray(schema.users.email, [email, `deleted-${email}`]));
    for (const u of existing) {
      await testDb.db.delete(schema.memoryItems).where(eq(schema.memoryItems.userId, u.id));
      await testDb.db.delete(schema.tasteSignals).where(eq(schema.tasteSignals.userId, u.id));
      await testDb.db.delete(schema.userPreferences).where(eq(schema.userPreferences.userId, u.id));
      await testDb.db
        .delete(schema.userHealthProfiles)
        .where(eq(schema.userHealthProfiles.userId, u.id));
      await testDb.db.delete(schema.userCredentials).where(eq(schema.userCredentials.userId, u.id));
      await testDb.db.delete(schema.refreshTokens).where(eq(schema.refreshTokens.userId, u.id));
      await testDb.db.delete(schema.users).where(eq(schema.users.id, u.id));
    }
  }

  async function registerUser(email: string) {
    const res = await app.inject({
      method: "POST",
      url: "/v1/auth/register",
      payload: { email, password: "Password123!", displayName: "S5 Test" },
    });
    const body = JSON.parse(res.body) as { accessToken: string };
    const token = body.accessToken;
    const userId = (JSON.parse(atob(token.split(".")[1]!)) as { sub: string }).sub;
    return { token, userId };
  }

  beforeAll(async () => {
    app = await buildServer(config);
    await app.ready();
  });

  afterAll(async () => {
    await closeDatabase();
    await app.close();
  });

  it("legacy POST /v1/me/onboarding skapar memory_items och taste_signals", async () => {
    const email = "s5-legacy@example.invalid";
    await cleanupUser(email);
    const { token, userId } = await registerUser(email);

    const res = await app.inject({
      method: "POST",
      url: "/v1/me/onboarding",
      headers: { authorization: `Bearer ${token}` },
      payload: {
        precisionMode: "simple",
        preferences: {
          primaryGoal: "less_waste",
          goals: ["less_waste", "cook_more"],
          allergens: ["gluten", "milk"],
          favoriteCuisines: ["italian", "thai"],
          avoidIngredientIds: ["broccoli", "anchovy"],
          spiceLevelMax: 2,
        },
        householdChoice: { kind: "skip" },
      },
    });

    expect(res.statusCode).toBe(200);

    const memory = await testDb.db
      .select()
      .from(schema.memoryItems)
      .where(eq(schema.memoryItems.userId, userId));
    const memoryKeys = memory.map((m) => m.key).sort();
    expect(memoryKeys).toEqual(
      [
        "goal:less_waste",
        "goal:cook_more",
        "goal:primary:less_waste",
        "allergen:gluten",
        "allergen:milk",
        "favorite-cuisine:italian",
        "favorite-cuisine:thai",
        "avoid-ingredient:broccoli",
        "avoid-ingredient:anchovy",
        "spice-level-max",
      ].sort(),
    );
    for (const m of memory) {
      expect(m.origin).toBe("user_stated");
      expect(m.verifiedByUser).toBe(true);
      expect(m.confidence).toBe(1);
    }

    const signals = await testDb.db
      .select()
      .from(schema.tasteSignals)
      .where(eq(schema.tasteSignals.userId, userId));
    const cuisineSignals = signals
      .filter((s) => s.axis === "cuisine")
      .sort((a, b) => (a.target ?? "").localeCompare(b.target ?? ""));
    const avoidSignals = signals
      .filter((s) => s.axis === "ingredient_avoid")
      .sort((a, b) => (a.target ?? "").localeCompare(b.target ?? ""));

    expect(cuisineSignals).toHaveLength(2);
    expect(cuisineSignals[0]).toMatchObject({
      target: "italian",
      direction: 1,
      origin: "user_stated",
    });
    expect(cuisineSignals[1]).toMatchObject({
      target: "thai",
      direction: 1,
      origin: "user_stated",
    });

    expect(avoidSignals).toHaveLength(2);
    expect(avoidSignals[0]).toMatchObject({
      target: "anchovy",
      direction: -1,
      origin: "user_stated",
    });
    expect(avoidSignals[1]).toMatchObject({
      target: "broccoli",
      direction: -1,
      origin: "user_stated",
    });

    await cleanupUser(email);
  });

  it("är idempotent – upprepade anrop skapar inga dubletter", async () => {
    const email = "s5-idempotent@example.invalid";
    await cleanupUser(email);
    const { token, userId } = await registerUser(email);

    const payload = {
      precisionMode: "simple",
      preferences: {
        primaryGoal: "more_protein",
        goals: ["more_protein", "lower_cost"],
        allergens: ["peanuts"],
        favoriteCuisines: ["japanese"],
        avoidIngredientIds: ["liver"],
        spiceLevelMax: 3,
      },
      householdChoice: { kind: "skip" },
    };

    for (let i = 0; i < 2; i++) {
      const res = await app.inject({
        method: "POST",
        url: "/v1/me/onboarding",
        headers: { authorization: `Bearer ${token}` },
        payload,
      });
      expect(res.statusCode).toBe(200);
    }

    const memoryCount = await testDb.db
      .select({ id: schema.memoryItems.id })
      .from(schema.memoryItems)
      .where(eq(schema.memoryItems.userId, userId));
    expect(memoryCount).toHaveLength(7);

    const signalCount = await testDb.db
      .select({ id: schema.tasteSignals.id })
      .from(schema.tasteSignals)
      .where(eq(schema.tasteSignals.userId, userId));
    expect(signalCount).toHaveLength(2);

    await cleanupUser(email);
  });

  it("progressive onboarding complete-c skapar memory_items och taste_signals", async () => {
    const email = "s5-progressive@example.invalid";
    await cleanupUser(email);
    const { token, userId } = await registerUser(email);

    const stepA = await app.inject({
      method: "POST",
      url: "/v1/onboarding/quick-start",
      headers: { authorization: `Bearer ${token}` },
      payload: { goals: ["less_waste"], precisionMode: "simple" },
    });
    expect(stepA.statusCode).toBe(200);

    const stepB = await app.inject({
      method: "POST",
      url: "/v1/onboarding/complete-b",
      headers: { authorization: `Bearer ${token}` },
      payload: {
        precisionMode: "simple",
        preferences: {
          dietPattern: "vegetarian",
          allergens: ["gluten"],
        },
        householdChoice: { kind: "skip" },
      },
    });
    expect(stepB.statusCode).toBe(200);

    const stepC = await app.inject({
      method: "POST",
      url: "/v1/onboarding/complete-c",
      headers: { authorization: `Bearer ${token}` },
      payload: {
        precisionMode: "simple",
        preferences: {
          favoriteCuisines: ["italian"],
          avoidIngredientIds: ["mushroom"],
          spiceLevelMax: 2,
        },
      },
    });
    expect(stepC.statusCode).toBe(200);

    const memory = await testDb.db
      .select()
      .from(schema.memoryItems)
      .where(eq(schema.memoryItems.userId, userId));
    const keys = memory.map((m) => m.key).sort();
    expect(keys).toContain("goal:less_waste");
    expect(keys).toContain("goal:primary:less_waste");
    expect(keys).toContain("allergen:gluten");
    expect(keys).toContain("favorite-cuisine:italian");
    expect(keys).toContain("avoid-ingredient:mushroom");
    expect(keys).toContain("spice-level-max");

    const signals = await testDb.db
      .select()
      .from(schema.tasteSignals)
      .where(eq(schema.tasteSignals.userId, userId));
    expect(signals).toHaveLength(2);
    expect(
      signals.some((s) => s.axis === "cuisine" && s.target === "italian" && s.direction === 1),
    ).toBe(true);
    expect(
      signals.some(
        (s) => s.axis === "ingredient_avoid" && s.target === "mushroom" && s.direction === -1,
      ),
    ).toBe(true);

    await cleanupUser(email);
  });

  it("DELETE /v1/me tömmer onboarding-skapade memory_items och taste_signals", async () => {
    const email = "s5-gdpr@example.invalid";
    await cleanupUser(email);
    const { token, userId } = await registerUser(email);

    await app.inject({
      method: "POST",
      url: "/v1/me/onboarding",
      headers: { authorization: `Bearer ${token}` },
      payload: {
        precisionMode: "simple",
        preferences: {
          primaryGoal: "less_waste",
          goals: ["less_waste"],
          allergens: ["gluten"],
          favoriteCuisines: ["italian"],
          avoidIngredientIds: ["broccoli"],
          spiceLevelMax: 2,
        },
        householdChoice: { kind: "skip" },
      },
    });

    const deleteRes = await app.inject({
      method: "DELETE",
      url: "/v1/me",
      headers: { authorization: `Bearer ${token}` },
    });
    expect(deleteRes.statusCode).toBe(200);

    const remainingMemory = await testDb.db
      .select({ id: schema.memoryItems.id })
      .from(schema.memoryItems)
      .where(eq(schema.memoryItems.userId, userId));
    expect(remainingMemory).toHaveLength(0);

    const remainingSignals = await testDb.db
      .select({ id: schema.tasteSignals.id })
      .from(schema.tasteSignals)
      .where(eq(schema.tasteSignals.userId, userId));
    expect(remainingSignals).toHaveLength(0);

    await cleanupUser(email);
  });
});
