import { describe, expect, it } from "vitest";
import type { CoverageResult } from "@app/recipe-engine";
import {
  easterSunday,
  isEventActive,
  midsummerEve,
  parseCraving,
  rankAll,
  scoreCandidate,
  seasonForDate,
  type RecommendationCandidate,
  type RecommendationContext,
} from "../src/index.js";

const fullCoverage: CoverageResult = { coverage: 1, matches: [], missing: [], expiringUsed: [] };
const nutrition = {
  kcal: 550,
  proteinG: 45,
  carbsG: 50,
  fatG: 18,
  saturatedFatG: 6,
  fiberG: 6,
  sugarG: 4,
  saltG: 1.5,
};

function candidate(overrides: Partial<RecommendationCandidate> = {}): RecommendationCandidate {
  return {
    recipeId: "r1",
    titleSv: "Kycklinggryta",
    cuisine: "swedish",
    tags: [],
    totalTimeMinutes: 30,
    nutritionPerPortion: nutrition,
    estimatedCostMinorPerPortion: 2200,
    ratingAverage: null,
    ratingCount: 0,
    peakSeasons: [],
    holidayTags: [],
    spiceLevel: 1,
    coverage: fullCoverage,
    ...overrides,
  };
}

const ctx: RecommendationContext = {
  mealType: "dinner",
  persons: 4,
  currentSeason: "summer",
  activeHolidayTags: [],
  isWeekday: true,
  favoriteCuisines: ["swedish"],
  remainingProteinG: 60,
  remainingKcal: 800,
};

describe("poängsättning (spec §18)", () => {
  it("utgångsvaror lyfter rekommendationen", () => {
    const expiring = candidate({
      recipeId: "expiring",
      coverage: {
        coverage: 1,
        matches: [],
        missing: [],
        expiringUsed: [
          {
            canonicalIngredientId: "chicken",
            displayNameSv: "kycklingen",
            required: 500,
            unit: "GRAM",
            availableInUnit: 600,
            covered: true,
            optional: false,
            mostUrgentDaysLeft: 1,
            usesExpiringItem: true,
          },
        ],
      },
    });
    const fresh = candidate({ recipeId: "fresh" });
    const ranked = rankAll([fresh, expiring], ctx);
    expect(ranked[0]?.recipeId).toBe("expiring");
    expect(ranked[0]?.whySv).toContain("senast i morgon");
  });

  it("förklaringen innehåller täckning och protein (specens exempel)", () => {
    const scored = scoreCandidate(
      candidate({
        coverage: { ...fullCoverage, coverage: 0.92 },
        nutritionPerPortion: { ...nutrition, proteinG: 58 },
      }),
      ctx,
    );
    expect(scored.whySv).toContain("92 %");
    expect(scored.whySv).toContain("58 gram protein");
  });

  it("recept över tidsgränsen filtreras bort", () => {
    const slow = candidate({ recipeId: "slow", totalTimeMinutes: 90 });
    const ranked = rankAll([slow], { ...ctx, maxMinutes: 45 });
    expect(ranked).toHaveLength(0);
  });

  it("nyligen lagat straffas (variation)", () => {
    const recent = scoreCandidate(candidate({ daysSinceLastCooked: 2 }), ctx);
    const old = scoreCandidate(candidate({ daysSinceLastCooked: 30 }), ctx);
    expect(old.score).toBeGreaterThan(recent.score);
  });
});

describe("'jag är sugen på' (spec §19)", () => {
  it("tolkar kök, taggar och kcal-gräns", () => {
    const parsed = parseCraving("något krämigt och asiatiskt under 500 kcal");
    expect(parsed.cuisine).toBe("thai");
    expect(parsed.tags).toContain("creamy");
    expect(parsed.maxKcal).toBe(500);
  });
  it("tolkar svenska uttryck", () => {
    expect(parseCraving("snabb husmanskost").tags).toEqual(
      expect.arrayContaining(["quick", "comfort"]),
    );
    expect(parseCraving("barnvänliga tacos").cuisine).toBe("mexican");
  });
});

describe("säsongs- och eventmotor (spec §28)", () => {
  // Motorn räknar i UTC – testerna använder UTC-datum så de är sanna i alla tidszoner.
  it("årstider", () => {
    expect(seasonForDate(new Date("2026-07-15T00:00:00Z"))).toBe("summer");
    expect(seasonForDate(new Date("2026-01-15T00:00:00Z"))).toBe("winter");
  });
  it("midsommarafton är alltid en fredag 19–25 juni", () => {
    for (const year of [2025, 2026, 2027, 2028]) {
      const eve = midsummerEve(year);
      expect(eve.getUTCDay()).toBe(5);
      expect(eve.getUTCMonth()).toBe(5);
      expect(eve.getUTCDate()).toBeGreaterThanOrEqual(19);
      expect(eve.getUTCDate()).toBeLessThanOrEqual(25);
    }
  });
  it("påskdagen: kända referensår", () => {
    expect(easterSunday(2026).toISOString().slice(0, 10)).toBe("2026-04-05");
    expect(easterSunday(2027).toISOString().slice(0, 10)).toBe("2027-03-28");
  });
  it("event aktiveras inom leadDays", () => {
    const midsummer = {
      dateRule: { kind: "computed", algorithm: "midsummer" } as const,
      leadDays: 10,
    };
    expect(isEventActive(midsummer, new Date("2026-06-15T00:00:00Z"))).toBe(true);
    expect(isEventActive(midsummer, new Date("2026-03-01T00:00:00Z"))).toBe(false);
    const jul = {
      dateRule: { kind: "range", startMonthDay: "12-20", endMonthDay: "12-26" } as const,
      leadDays: 21,
    };
    expect(isEventActive(jul, new Date("2026-12-05T00:00:00Z"))).toBe(true);
    expect(isEventActive(jul, new Date("2026-08-02T00:00:00Z"))).toBe(false);
  });
});
