import { describe, expect, it } from "vitest";
import {
  allocateFefo,
  classifyExpiry,
  computeBalance,
  findDuplicateCandidates,
  forecastDepletion,
  normalizeDelta,
} from "../src/index.js";

const TODAY = new Date("2026-08-02T00:00:00Z"); // UTC – testet ska vara sant i alla tidszoner

describe("bäst före-klassning (spec §13)", () => {
  it("sista förbrukningsdag passerad → expired (hård gräns)", () => {
    const result = classifyExpiry({ useByDate: "2026-08-01" }, TODAY);
    expect(result.status).toBe("expired");
    expect(result.limitingFactor).toBe("use_by");
  });
  it("bäst före passerad → expiring + pastBestBefore, ALDRIG auto-expired", () => {
    const result = classifyExpiry({ bestBeforeDate: "2026-07-30" }, TODAY);
    expect(result.status).toBe("expiring");
    expect(result.pastBestBefore).toBe(true);
  });
  it("trösklar: ≤2 dagar expiring, ≤5 use_soon, annars fresh", () => {
    expect(classifyExpiry({ bestBeforeDate: "2026-08-04" }, TODAY).status).toBe("expiring");
    expect(classifyExpiry({ bestBeforeDate: "2026-08-06" }, TODAY).status).toBe("use_soon");
    expect(classifyExpiry({ bestBeforeDate: "2026-08-20" }, TODAY).status).toBe("fresh");
  });
  it("fryst vara pausar klockan", () => {
    const result = classifyExpiry({ bestBeforeDate: "2026-07-01", frozenAt: "2026-06-20" }, TODAY);
    expect(result.status).toBe("fresh");
  });
  it("upptinad vara räknas igen", () => {
    const result = classifyExpiry(
      { bestBeforeDate: "2026-07-01", frozenAt: "2026-06-20", thawedAt: "2026-08-01" },
      TODAY,
    );
    expect(result.status).not.toBe("fresh");
  });
  it("öppnad vara: riktvärde per plats kan vara mest bindande", () => {
    const result = classifyExpiry(
      {
        bestBeforeDate: "2026-09-01",
        openedAt: "2026-07-30",
        storageLocationType: "fridge",
        shelfLifeGuidance: { fridge: 5 },
      },
      TODAY,
    );
    expect(result.limitingFactor).toBe("opened_guidance");
    expect(result.daysLeft).toBe(2);
    expect(result.status).toBe("expiring");
  });
  it("ingen data → unknown, aldrig gissning", () => {
    expect(classifyExpiry({}, TODAY).status).toBe("unknown");
  });
  it("mjölkprincipen: gick ut igår → använd sinnena, inte soptunnan", () => {
    // Bäst före är en KVALITETSgräns: mjölken som gick ut igår är inte
    // automatiskt dålig – användaren ska lukta/smaka. Appen får aldrig
    // klassa den som "expired" eller föreslå att den slängs.
    const result = classifyExpiry({ bestBeforeDate: "2026-08-01" }, TODAY);
    expect(result.status).toBe("expiring"); // synlig och prioriterad – inte dömd
    expect(result.pastBestBefore).toBe(true); // UI: "lukta och smaka"
    expect(result.daysLeft).toBe(-1);
    expect(result.status).not.toBe("expired");
  });
});

describe("transaktionssaldo (spec §8)", () => {
  it("summerar spec-exemplet korrekt", () => {
    const result = computeBalance([
      { type: "purchase", quantityDelta: 1000, unit: "GRAM" },
      { type: "cook_use", quantityDelta: -300, unit: "GRAM" },
      { type: "discard", quantityDelta: -150, unit: "GRAM" },
    ]);
    expect(result.balance).toBe(550);
    expect(result.valid).toBe(true);
  });
  it("flaggar negativt saldo som inkonsistens", () => {
    const result = computeBalance([
      { type: "purchase", quantityDelta: 100, unit: "GRAM" },
      { type: "consume", quantityDelta: -200, unit: "GRAM" },
    ]);
    expect(result.valid).toBe(false);
    expect(result.balance).toBe(0);
  });
  it("normaliserar tecken per transaktionstyp", () => {
    expect(normalizeDelta("consume", 300)).toBe(-300);
    expect(normalizeDelta("purchase", -300)).toBe(300);
    expect(normalizeDelta("adjust", -50)).toBe(-50);
  });
});

describe("FEFO-allokering (spec §17)", () => {
  it("plockar från den som går ut först", () => {
    const result = allocateFefo(
      400,
      "GRAM",
      [
        { id: "fresh", quantity: 500, unit: "GRAM", bestBeforeDate: "2026-08-20" },
        { id: "old", quantity: 300, unit: "GRAM", bestBeforeDate: "2026-08-03" },
      ],
      {},
      TODAY,
    );
    expect(result.covered).toBe(true);
    expect(result.allocations[0]?.itemId).toBe("old");
    expect(result.allocations[0]?.quantity).toBe(300);
    expect(result.allocations[1]?.itemId).toBe("fresh");
    expect(result.allocations[1]?.quantity).toBe(100);
  });
  it("rapporterar shortfall ärligt", () => {
    const result = allocateFefo(
      1000,
      "GRAM",
      [{ id: "a", quantity: 300, unit: "GRAM", bestBeforeDate: "2026-08-10" }],
      {},
      TODAY,
    );
    expect(result.covered).toBe(false);
    expect(result.shortfall).toBe(700);
  });
  it("mjölkprincipen: passerat bäst före utesluts ALDRIG – räddas först", () => {
    const result = allocateFefo(
      400,
      "GRAM",
      [
        { id: "fresh", quantity: 500, unit: "GRAM", bestBeforeDate: "2026-08-20" },
        { id: "past", quantity: 300, unit: "GRAM", bestBeforeDate: "2026-08-01" }, // gick ut igår
      ],
      {},
      TODAY,
    );
    // Varan som passerat bäst före plockas FÖRST (rädda den), inte bort.
    expect(result.covered).toBe(true);
    expect(result.allocations[0]?.itemId).toBe("past");
    expect(result.allocations[0]?.quantity).toBe(300);
  });
});

describe("dubblettkandidater (spec §9)", () => {
  it("hittar kvitto+bild-dubblett inom fönstret", () => {
    const candidates = findDuplicateCandidates(
      {
        canonicalIngredientId: "milk_3",
        displayName: "Mjölk 3%",
        quantity: 1,
        source: "receipt",
        createdAt: "2026-08-02T10:00:00Z",
      },
      [
        {
          id: "existing",
          canonicalIngredientId: "milk_3",
          displayName: "mjölk",
          quantity: 1,
          source: "fridge_photo",
          createdAt: "2026-08-01T18:00:00Z",
        },
      ],
    );
    expect(candidates).toHaveLength(1);
    expect(candidates[0]?.score).toBeGreaterThanOrEqual(0.7);
  });
  it("matchar inte helt olika varor", () => {
    const candidates = findDuplicateCandidates(
      {
        canonicalIngredientId: "milk_3",
        displayName: "Mjölk",
        quantity: 1,
        source: "receipt",
        createdAt: "2026-08-02T10:00:00Z",
      },
      [
        {
          id: "x",
          canonicalIngredientId: "chicken_breast",
          displayName: "Kyckling",
          quantity: 500,
          source: "barcode",
          createdAt: "2026-08-02T09:00:00Z",
        },
      ],
    );
    expect(candidates).toHaveLength(0);
  });
});

describe("pantry forecast (spec §40)", () => {
  it("beräknar förbrukningstakt och påfyllnadsdatum", () => {
    const result = forecastDepletion(
      1,
      [
        { date: "2026-07-20", quantity: 1 },
        { date: "2026-07-26", quantity: 1 },
        { date: "2026-08-01", quantity: 1 },
      ],
      TODAY,
    );
    expect(result.dailyRate).toBeCloseTo(0.25, 2);
    expect(result.daysUntilEmpty).toBeCloseTo(4, 0);
    expect(result.suggestedRestockDate).toBe("2026-08-04");
  });
  it("låg konfidens vid för lite data", () => {
    expect(forecastDepletion(1, [{ date: "2026-08-01", quantity: 1 }], TODAY).confidence).toBe(
      "low",
    );
  });
});
