import {
  resolveVatRate,
  NoRateForDateError,
  type VatScheduleEntry as TaxRateEntry,
} from '@platform-modules/tax/rates-table';
import { toIsraelDateString } from '@/lib/datetime';
import type { DrizzleClient } from '../client.js';
import { getSystemConfigCached, setSystemConfig } from './system-config.js';

export { toIsraelDateString };

const VAT_SCHEDULE_KEY = 'il.vat_schedule' as const;

export interface VatScheduleEntry {
  effectiveDateIl: string; // 'YYYY-MM-DD' in Asia/Jerusalem timezone
  ratePercent: number; // integer, e.g. 18
  addedBy: string; // admin user ID or 'system'
  addedAt: string; // ISO 8601 UTC timestamp
}

export class DuplicateVatDateError extends Error {
  readonly effectiveDateIl: string;
  constructor(effectiveDateIl: string) {
    super(`VAT entry for ${effectiveDateIl} already exists`);
    this.effectiveDateIl = effectiveDateIl;
  }
}

/**
 * No VAT rate could be resolved for a date. A VAT rate is a legal fact, never
 * guessed — so an unresolved rate fails loud rather than returning a default.
 * The schedule must be seeded complete (see db/seeds/seed-vat-schedule.ts).
 */
export class NoVatRateError extends Error {
  readonly dateIl: string;
  constructor(dateIl: string) {
    super(`No VAT rate in schedule for date ${dateIl}`);
    this.name = 'NoVatRateError';
    this.dateIl = dateIl;
  }
}

/** The stored il.vat_schedule value exists but is not valid JSON / not an array. */
export class VatScheduleCorruptError extends Error {
  constructor(cause?: unknown) {
    super('il.vat_schedule is corrupt — not a valid JSON array');
    this.name = 'VatScheduleCorruptError';
    if (cause !== undefined) this.cause = cause;
  }
}

/** Map host DB-stored rows to the tax module's date-effective entries (integer percent). */
function toRateSchedule(schedule: VatScheduleEntry[]): TaxRateEntry[] {
  return schedule.map((e) => ({
    effectiveFrom: e.effectiveDateIl,
    value: e.ratePercent,
    unit: 'percent',
  }));
}

/**
 * Look up rate (integer %) from a pre-fetched schedule — no DB call. Resolution
 * delegated to @platform-modules/tax/rates-table. Throws {@link NoVatRateError}
 * when no entry qualifies — no fallback (a wrong VAT rate is wrong money).
 */
export function findRateInSchedule(schedule: VatScheduleEntry[], dateIl: string): number {
  try {
    const rate = resolveVatRate(toRateSchedule(schedule), dateIl);
    return Number(rate) / 100; // basis points → integer percent (host stores integer %)
  } catch (err) {
    if (err instanceof NoRateForDateError) throw new NoVatRateError(dateIl);
    throw err;
  }
}

/**
 * Parse the stored schedule. A missing key yields an empty schedule (callers then
 * fail loud via {@link NoVatRateError}); a present-but-corrupt value throws
 * {@link VatScheduleCorruptError} rather than being silently swallowed to a default.
 */
export function parseSchedule(raw: string | null): VatScheduleEntry[] {
  if (!raw) return [];
  let parsed: unknown;
  try {
    parsed = JSON.parse(raw);
  } catch (err) {
    throw new VatScheduleCorruptError(err);
  }
  if (!Array.isArray(parsed)) throw new VatScheduleCorruptError();
  return parsed as VatScheduleEntry[];
}

/** Full schedule, sorted ascending by effectiveDateIl. */
export async function getVatSchedule(db: DrizzleClient): Promise<VatScheduleEntry[]> {
  const raw = await getSystemConfigCached(db, VAT_SCHEDULE_KEY);
  return parseSchedule(raw);
}

/**
 * Rate (integer %) applicable on a given Israel date string ('YYYY-MM-DD').
 * Finds the last entry whose effectiveDateIl <= dateIl.
 * Throws {@link NoVatRateError} if no entry qualifies — no fallback.
 */
export async function getVatRateForDate(db: DrizzleClient, dateIl: string): Promise<number> {
  return findRateInSchedule(await getVatSchedule(db), dateIl);
}

/** Convenience: rate for right now in Israel time. */
export async function getCurrentVatRate(db: DrizzleClient): Promise<number> {
  return getVatRateForDate(db, toIsraelDateString(new Date()));
}

/**
 * Append a new entry. Throws if effectiveDateIl already exists.
 * Returns the updated sorted schedule.
 */
export async function addVatEntry(
  db: DrizzleClient,
  entry: Omit<VatScheduleEntry, 'addedAt'>,
): Promise<VatScheduleEntry[]> {
  const schedule = await getVatSchedule(db);
  if (schedule.some((e) => e.effectiveDateIl === entry.effectiveDateIl)) {
    throw new DuplicateVatDateError(entry.effectiveDateIl);
  }
  const newEntry: VatScheduleEntry = { ...entry, addedAt: new Date().toISOString() };
  const updated = [...schedule, newEntry].sort((a, b) =>
    a.effectiveDateIl.localeCompare(b.effectiveDateIl),
  );
  await setSystemConfig(db, VAT_SCHEDULE_KEY, JSON.stringify(updated));
  return updated;
}
