import { EXPIRY_THRESHOLDS, type ExpiryStatus, type StorageLocationType } from "@app/shared-types";

export interface ExpiryInput {
  bestBeforeDate?: string | null;
  useByDate?: string | null;
  openedAt?: string | null;
  frozenAt?: string | null;
  thawedAt?: string | null;
  storageLocationType?: StorageLocationType | null;
  /** Riktvärde i dagar efter öppning/inköp per plats (från canonical ingredient). */
  shelfLifeGuidance?: Partial<Record<StorageLocationType, number>> | null;
  purchasedAt?: string | null;
}

export interface ExpiryResult {
  status: ExpiryStatus;
  /** Dagar kvar till den mest bindande gränsen. Negativt = passerad. */
  daysLeft: number | null;
  /** Vilken gräns som styr klassningen. */
  limitingFactor: "use_by" | "best_before" | "opened_guidance" | "purchased_guidance" | "none";
  /** Bäst före passerad men troligen ätbar – kommunicera skillnaden (spec §13). */
  pastBestBefore: boolean;
}

const DAY_MS = 86_400_000;

function daysBetween(from: Date, to: Date): number {
  return Math.floor((startOfDay(to).getTime() - startOfDay(from).getTime()) / DAY_MS);
}

/**
 * All kalenderaritmetik sker i UTC (deterministiskt oavsett serverns tidszon).
 * Datumsträngar ("2026-08-05") är kalenderdatum och tolkas som UTC-midnatt;
 * resultatet blir identiskt på en server i Stockholm, Auckland eller UTC.
 */
function startOfDay(d: Date): Date {
  return new Date(Date.UTC(d.getUTCFullYear(), d.getUTCMonth(), d.getUTCDate()));
}

function parse(dateStr: string | null | undefined): Date | null {
  if (!dateStr) return null;
  const d = new Date(`${dateStr}T00:00:00Z`);
  return Number.isNaN(d.getTime()) ? null : d;
}

/**
 * Deterministisk bäst före-klassning (spec §13).
 *
 * Regler:
 * - Fryst vara ("frozenAt" satt, ej upptinad): datumklockan pausad → "fresh"
 *   (kvalitetsförsämring hanteras som vägledning i UI, inte som utgång).
 * - "Sista förbrukningsdag" är hård gräns → efter den: "expired".
 * - "Bäst före" passerad: "expired"-klass används INTE automatiskt – varan
 *   flaggas "use_soon/expiring" med pastBestBefore=true. Appen får aldrig
 *   garantera säkerhet (spec §61.3) – användaren avgör.
 * - Öppnad vara: öppningsdatum + riktvärde per förvaringsplats kan vara mer
 *   bindande än tryckt datum.
 */
export function classifyExpiry(input: ExpiryInput, today: Date = new Date()): ExpiryResult {
  const isFrozen = Boolean(input.frozenAt) && !input.thawedAt;
  if (isFrozen) {
    return { status: "fresh", daysLeft: null, limitingFactor: "none", pastBestBefore: false };
  }

  const candidates: Array<{ factor: ExpiryResult["limitingFactor"]; date: Date }> = [];

  const useBy = parse(input.useByDate);
  if (useBy) candidates.push({ factor: "use_by", date: useBy });

  const bestBefore = parse(input.bestBeforeDate);
  if (bestBefore) candidates.push({ factor: "best_before", date: bestBefore });

  const guidanceDays = input.storageLocationType
    ? input.shelfLifeGuidance?.[input.storageLocationType]
    : undefined;
  const opened = parse(input.openedAt);
  if (opened && guidanceDays != null) {
    candidates.push({
      factor: "opened_guidance",
      date: new Date(opened.getTime() + guidanceDays * DAY_MS),
    });
  } else if (!useBy && !bestBefore && guidanceDays != null) {
    const purchased = parse(input.purchasedAt);
    if (purchased) {
      candidates.push({
        factor: "purchased_guidance",
        date: new Date(purchased.getTime() + guidanceDays * DAY_MS),
      });
    }
  }

  if (candidates.length === 0) {
    return { status: "unknown", daysLeft: null, limitingFactor: "none", pastBestBefore: false };
  }

  // Mest bindande = tidigaste datum.
  candidates.sort((a, b) => a.date.getTime() - b.date.getTime());
  const limiting = candidates[0]!;
  const daysLeft = daysBetween(today, limiting.date);
  const pastBestBefore = bestBefore ? daysBetween(today, bestBefore) < 0 : false;

  let status: ExpiryStatus;
  if (daysLeft < 0) {
    // Hård gräns endast för sista förbrukningsdag; annars "expiring" + flagga.
    status = limiting.factor === "use_by" ? "expired" : "expiring";
  } else if (daysLeft <= EXPIRY_THRESHOLDS.EXPIRING_DAYS) {
    status = "expiring";
  } else if (daysLeft <= EXPIRY_THRESHOLDS.USE_SOON_DAYS) {
    status = "use_soon";
  } else {
    status = "fresh";
  }

  return { status, daysLeft, limitingFactor: limiting.factor, pastBestBefore };
}

/** Sorteringsnyckel: mest brådskande först, okända sist. */
export function urgencyRank(result: ExpiryResult): number {
  const order: Record<ExpiryStatus, number> = {
    expired: 0,
    expiring: 1,
    use_soon: 2,
    fresh: 3,
    unknown: 4,
  };
  return order[result.status] * 10_000 + (result.daysLeft ?? 9_000);
}
