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("scan-to-scan-diff", () => {
  const testDb = createDatabase(process.env.TEST_DATABASE_URL!);
  const config = loadConfig();
  let app: Awaited<ReturnType<typeof buildServer>>;
  let token: string;
  let householdId: string;
  let scanJobId: string;
  const email = "scan-diff-test@example.invalid";

  async function cleanup() {
    const existing = await testDb.db
      .select({ id: schema.users.id })
      .from(schema.users)
      .where(inArray(schema.users.email, [email]));
    for (const u of existing) {
      await testDb.db.delete(schema.idempotencyKeys).where(eq(schema.idempotencyKeys.userId, u.id));
      const memberships = await testDb.db
        .select({ householdId: schema.householdMembers.householdId })
        .from(schema.householdMembers)
        .where(eq(schema.householdMembers.userId, u.id));
      for (const m of memberships) {
        const items = await testDb.db
          .select({ id: schema.inventoryItems.id })
          .from(schema.inventoryItems)
          .where(eq(schema.inventoryItems.householdId, m.householdId));
        for (const it of items) {
          await testDb.db
            .delete(schema.inventoryTransactions)
            .where(eq(schema.inventoryTransactions.inventoryItemId, it.id));
          await testDb.db
            .delete(schema.inventoryConflicts)
            .where(eq(schema.inventoryConflicts.inventoryItemId, it.id));
        }
        await testDb.db
          .delete(schema.inventoryItems)
          .where(eq(schema.inventoryItems.householdId, m.householdId));
        await testDb.db
          .delete(schema.inventoryConflicts)
          .where(eq(schema.inventoryConflicts.householdId, m.householdId));
        await testDb.db
          .delete(schema.storageLocations)
          .where(eq(schema.storageLocations.householdId, m.householdId));
        await testDb.db
          .delete(schema.householdMembers)
          .where(eq(schema.householdMembers.householdId, m.householdId));
        await testDb.db.delete(schema.households).where(eq(schema.households.id, m.householdId));
      }
      await testDb.db.delete(schema.scanJobs).where(eq(schema.scanJobs.userId, u.id));
      await testDb.db.delete(schema.userPreferences).where(eq(schema.userPreferences.userId, u.id));
      await testDb.db.delete(schema.users).where(eq(schema.users.id, u.id));
    }
  }

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

    const res = await app.inject({
      method: "POST",
      url: "/v1/auth/register",
      payload: { email, password: "Password123!", displayName: "Scan Diff Test" },
    });
    const body = JSON.parse(res.body) as { accessToken: string };
    token = body.accessToken;

    const quick = await app.inject({
      method: "POST",
      url: "/v1/onboarding/quick-start",
      headers: { authorization: `Bearer ${token}` },
      payload: { goals: ["less_waste"], precisionMode: "simple" },
    });
    householdId = (JSON.parse(quick.body) as { householdId: string }).householdId;

    const [location] = await testDb.db
      .select({ id: schema.storageLocations.id })
      .from(schema.storageLocations)
      .where(eq(schema.storageLocations.householdId, householdId))
      .limit(1);

    // Skapa två varor att diffa mot
    await app.inject({
      method: "POST",
      url: "/v1/inventory/items",
      headers: { authorization: `Bearer ${token}` },
      payload: {
        displayName: "Mjölk",
        quantity: 1,
        unit: "LITER",
        storageLocationId: location!.id,
      },
    });
    await app.inject({
      method: "POST",
      url: "/v1/inventory/items",
      headers: { authorization: `Bearer ${token}` },
      payload: {
        displayName: "Yoghurt",
        quantity: 2,
        unit: "COUNT",
        storageLocationId: location!.id,
      },
    });

    // Skapa ett färdigt scan-jobb med resultat
    const [job] = await testDb.db
      .insert(schema.scanJobs)
      .values({
        userId: (JSON.parse(atob(token.split(".")[1]!)) as { sub: string }).sub,
        householdId,
        scanType: "fridge",
        jobType: "ANALYZE_FRIDGE_IMAGE",
        status: "completed",
        result: [
          {
            displayName: "Mjölk",
            quantity: 0.5,
            unit: "LITER",
            storageLocationId: location!.id,
            confidence: 0.9,
          },
          {
            displayName: "Ost",
            quantity: 1,
            unit: "COUNT",
            storageLocationId: location!.id,
            confidence: 0.9,
          },
        ],
        modelVersion: "v1",
        promptVersion: "p1",
      })
      .returning();
    scanJobId = job!.id;
  });

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

  it("returns diff rows with model/prompt versions", async () => {
    const res = await app.inject({
      method: "POST",
      url: `/v1/scans/${scanJobId}/diff`,
      headers: { authorization: `Bearer ${token}` },
      payload: {},
    });
    expect(res.statusCode).toBe(200);
    const body = JSON.parse(res.body) as {
      rows: Array<{ kind: string; displayName: string }>;
      modelVersion: string;
      promptVersion: string;
    };
    expect(body.modelVersion).toBe("v1");
    expect(body.promptVersion).toBe("p1");
    const kinds = body.rows.map((r) => r.kind);
    expect(kinds).toContain("quantity_changed");
    expect(kinds).toContain("new_item");
    expect(kinds).toContain("vanished");
  });

  it("applies accepted diff rows idempotently", async () => {
    const diff = await app.inject({
      method: "POST",
      url: `/v1/scans/${scanJobId}/diff`,
      headers: { authorization: `Bearer ${token}` },
      payload: {},
    });
    const { rows } = JSON.parse(diff.body) as {
      rows: Array<{
        kind: string;
        itemId?: string;
        previousItemId?: string;
        displayName: string;
        newQuantity?: number;
        newLocationId?: string;
        confidence: number;
      }>;
    };
    const changed = rows.find((r) => r.kind === "quantity_changed")!;

    const apply = await app.inject({
      method: "POST",
      url: `/v1/scans/${scanJobId}/diff/apply`,
      headers: { authorization: `Bearer ${token}` },
      payload: {
        idempotencyKey: "diff-apply-1",
        rows: [
          {
            kind: "quantity_changed",
            previousItemId: changed.previousItemId,
            displayName: changed.displayName,
            unit: "LITER",
            newQuantity: changed.newQuantity,
            confidence: changed.confidence,
          },
        ],
      },
    });
    expect(apply.statusCode).toBe(200);
    const applied = JSON.parse(apply.body) as { appliedItemIds: string[] };
    expect(applied.appliedItemIds.length).toBeGreaterThan(0);

    // Idempotency: samma nyckel ska ge samma svar
    const apply2 = await app.inject({
      method: "POST",
      url: `/v1/scans/${scanJobId}/diff/apply`,
      headers: { authorization: `Bearer ${token}` },
      payload: {
        idempotencyKey: "diff-apply-1",
        rows: [],
      },
    });
    expect(apply2.statusCode).toBe(200);
    expect(JSON.parse(apply2.body)).toEqual(applied);
  });
});
