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";
import { computeBalance } from "@app/inventory-engine";

describe("quick reconciliation", () => {
  const testDb = createDatabase(process.env.TEST_DATABASE_URL!);
  const config = loadConfig();
  let app: Awaited<ReturnType<typeof buildServer>>;
  let token: string;
  let householdId: string;
  const email = "recon-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) {
      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.inventoryItems).where(eq(schema.inventoryItems.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.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: "Recon 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;

    // Skapa en vara att avstämma
    const locations = await testDb.db
      .select({ id: schema.storageLocations.id })
      .from(schema.storageLocations)
      .where(eq(schema.storageLocations.householdId, householdId));
    const [location] = locations;
    await app.inject({
      method: "POST",
      url: "/v1/inventory/items",
      headers: { authorization: `Bearer ${token}` },
      payload: {
        displayName: "Mjölk",
        quantity: 1,
        unit: "LITER",
        storageLocationId: location!.id,
        bestBeforeDate: new Date(Date.now() + 2 * 86_400_000).toISOString().slice(0, 10),
      },
    });
  });

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

  it("starts reconciliation and returns candidates with reasons", async () => {
    const res = await app.inject({
      method: "POST",
      url: "/v1/reconciliations/start",
      headers: { authorization: `Bearer ${token}` },
      payload: {},
    });
    expect(res.statusCode).toBe(200);
    const body = JSON.parse(res.body) as { candidates: Array<{ itemId: string; reasons: unknown[]; suggestedAction: string }> };
    expect(body.candidates.length).toBeGreaterThan(0);
    expect(body.candidates[0]?.reasons.length).toBeGreaterThan(0);
  });

  it("resolves 'exists' and marks item verified", async () => {
    const start = await app.inject({
      method: "POST",
      url: "/v1/reconciliations/start",
      headers: { authorization: `Bearer ${token}` },
      payload: {},
    });
    const { candidates } = JSON.parse(start.body) as { candidates: Array<{ itemId: string }> };
    const itemId = candidates[0]!.itemId;

    const res = await app.inject({
      method: "POST",
      url: `/v1/reconciliations/items/${itemId}/resolve`,
      headers: { authorization: `Bearer ${token}` },
      payload: { action: "exists", quantity: 0.5 },
    });
    expect(res.statusCode).toBe(200);
    const body = JSON.parse(res.body) as { action: string; quantity: number; verifiedByUser: boolean };
    expect(body.action).toBe("exists");
    expect(body.quantity).toBe(0.5);
    expect(body.verifiedByUser).toBe(true);
  });

  it("transaction invariant holds for exists/depleted/uncertain", async () => {
    const start = await app.inject({
      method: "POST",
      url: "/v1/reconciliations/start",
      headers: { authorization: `Bearer ${token}` },
      payload: {},
    });
    const { candidates } = JSON.parse(start.body) as { candidates: Array<{ itemId: string }> };
    const itemId = candidates[0]!.itemId;

    async function assertBalanceMatches(itemId: string) {
      const [item] = await testDb.db
        .select({ quantity: schema.inventoryItems.quantity })
        .from(schema.inventoryItems)
        .where(eq(schema.inventoryItems.id, itemId))
        .limit(1);
      const txs = await testDb.db
        .select({ type: schema.inventoryTransactions.type, quantityDelta: schema.inventoryTransactions.quantityDelta, unit: schema.inventoryTransactions.unit })
        .from(schema.inventoryTransactions)
        .where(eq(schema.inventoryTransactions.inventoryItemId, itemId));
      const balance = computeBalance(txs.map((tx) => ({ type: tx.type, quantityDelta: tx.quantityDelta, unit: tx.unit })));
      expect(balance.balance).toBeCloseTo(item!.quantity, 5);
    }

    // exists med justering
    await app.inject({
      method: "POST",
      url: `/v1/reconciliations/items/${itemId}/resolve`,
      headers: { authorization: `Bearer ${token}` },
      payload: { action: "exists", quantity: 0.7 },
    });
    await assertBalanceMatches(itemId);

    // depleted ska alltid skriva transaktion
    await app.inject({
      method: "POST",
      url: `/v1/reconciliations/items/${itemId}/resolve`,
      headers: { authorization: `Bearer ${token}` },
      payload: { action: "depleted" },
    });
    await assertBalanceMatches(itemId);

    // uncertain med justering
    await app.inject({
      method: "POST",
      url: `/v1/reconciliations/items/${itemId}/resolve`,
      headers: { authorization: `Bearer ${token}` },
      payload: { action: "uncertain", quantity: 0.2 },
    });
    await assertBalanceMatches(itemId);
  });
});
