import { getPublicHolidays } from "@capakraken/shared";

export type HolidayCalendarSeedScope = "COUNTRY" | "STATE" | "CITY";

export type HolidayCalendarSeedEntry = {
  date: string;
  name: string;
  isRecurringAnnual: boolean;
};

export type HolidayCalendarSeedDefinition = {
  name: string;
  scopeType: HolidayCalendarSeedScope;
  countryCode: string;
  stateCode?: string;
  cityName?: string;
  priority: number;
  entries: HolidayCalendarSeedEntry[];
};

type SeedContext = {
  availableCountryCodes: string[];
  availableCitiesByCountry: Record<string, string[]>;
  activeGermanStates?: string[];
  years: number[];
};

type SimpleHolidayDefinition = {
  name: string;
  resolveDate: (year: number) => string;
};

const COUNTRY_PRIORITY = 10;
const STATE_PRIORITY = 20;
const CITY_PRIORITY = 30;

function toIsoDate(date: Date): string {
  return date.toISOString().slice(0, 10);
}

function dateUtc(year: number, month: number, day: number): Date {
  return new Date(Date.UTC(year, month - 1, day));
}

function addDays(date: Date, amount: number): Date {
  const next = new Date(date);
  next.setUTCDate(next.getUTCDate() + amount);
  return next;
}

function computeEasterSunday(year: number): Date {
  const a = year % 19;
  const b = Math.floor(year / 100);
  const c = year % 100;
  const d = Math.floor(b / 4);
  const e = b % 4;
  const f = Math.floor((b + 8) / 25);
  const g = Math.floor((b - f + 1) / 3);
  const h = (19 * a + b - d - g + 15) % 30;
  const i = Math.floor(c / 4);
  const k = c % 4;
  const l = (32 + 2 * e + 2 * i - h - k) % 7;
  const m = Math.floor((a + 11 * h + 22 * l) / 451);
  const month = Math.floor((h + l - 7 * m + 114) / 31);
  const day = ((h + l - 7 * m + 114) % 31) + 1;
  return dateUtc(year, month, day);
}

function nthWeekdayOfMonth(year: number, month: number, weekday: number, occurrence: number): string {
  const firstDay = dateUtc(year, month, 1);
  const delta = (weekday - firstDay.getUTCDay() + 7) % 7;
  const day = 1 + delta + ((occurrence - 1) * 7);
  return toIsoDate(dateUtc(year, month, day));
}

function lastWeekdayOfMonth(year: number, month: number, weekday: number): string {
  const lastDay = dateUtc(year, month + 1, 0);
  const delta = (lastDay.getUTCDay() - weekday + 7) % 7;
  lastDay.setUTCDate(lastDay.getUTCDate() - delta);
  return toIsoDate(lastDay);
}

function observedUsFixedHoliday(year: number, month: number, day: number): string {
  const holiday = dateUtc(year, month, day);
  const weekday = holiday.getUTCDay();

  if (weekday === 6) {
    return toIsoDate(addDays(holiday, -1));
  }
  if (weekday === 0) {
    return toIsoDate(addDays(holiday, 1));
  }

  return toIsoDate(holiday);
}

function buildEntries(years: number[], definitions: SimpleHolidayDefinition[]): HolidayCalendarSeedEntry[] {
  const entries = new Map<string, HolidayCalendarSeedEntry>();

  for (const year of years) {
    for (const definition of definitions) {
      const date = definition.resolveDate(year);
      entries.set(date, {
        date,
        name: definition.name,
        isRecurringAnnual: false,
      });
    }
  }

  return [...entries.values()].sort((left, right) => left.date.localeCompare(right.date));
}

function buildGermanCountryEntries(years: number[]): HolidayCalendarSeedEntry[] {
  return buildEntries(
    years,
    years.flatMap((year) =>
      getPublicHolidays(year)
        .filter((holiday) => holiday.federal)
        .map((holiday) => ({
          name: holiday.name,
          resolveDate: () => holiday.date,
        })),
    ),
  );
}

function buildGermanStateEntries(years: number[], stateCode: string): HolidayCalendarSeedEntry[] {
  return buildEntries(
    years,
    years.flatMap((year) =>
      getPublicHolidays(year, stateCode)
        .filter((holiday) => !holiday.federal)
        .map((holiday) => ({
          name: holiday.name,
          resolveDate: () => holiday.date,
        })),
    ),
  );
}

function buildSpanishCountryEntries(years: number[]): HolidayCalendarSeedEntry[] {
  return buildEntries(years, [
    { name: "Ano Nuevo", resolveDate: (year) => toIsoDate(dateUtc(year, 1, 1)) },
    { name: "Epifania del Senor", resolveDate: (year) => toIsoDate(dateUtc(year, 1, 6)) },
    { name: "Viernes Santo", resolveDate: (year) => toIsoDate(addDays(computeEasterSunday(year), -2)) },
    { name: "Fiesta del Trabajo", resolveDate: (year) => toIsoDate(dateUtc(year, 5, 1)) },
    { name: "Asuncion de la Virgen", resolveDate: (year) => toIsoDate(dateUtc(year, 8, 15)) },
    { name: "Fiesta Nacional de Espana", resolveDate: (year) => toIsoDate(dateUtc(year, 10, 12)) },
    { name: "Todos los Santos", resolveDate: (year) => toIsoDate(dateUtc(year, 11, 1)) },
    { name: "Dia de la Constitucion", resolveDate: (year) => toIsoDate(dateUtc(year, 12, 6)) },
    { name: "Inmaculada Concepcion", resolveDate: (year) => toIsoDate(dateUtc(year, 12, 8)) },
    { name: "Navidad", resolveDate: (year) => toIsoDate(dateUtc(year, 12, 25)) },
  ]);
}

function buildIndianCountryEntries(years: number[]): HolidayCalendarSeedEntry[] {
  return buildEntries(years, [
    { name: "Republic Day", resolveDate: (year) => toIsoDate(dateUtc(year, 1, 26)) },
    { name: "Good Friday", resolveDate: (year) => toIsoDate(addDays(computeEasterSunday(year), -2)) },
    { name: "Independence Day", resolveDate: (year) => toIsoDate(dateUtc(year, 8, 15)) },
    { name: "Gandhi Jayanti", resolveDate: (year) => toIsoDate(dateUtc(year, 10, 2)) },
  ]);
}

function buildUsCountryEntries(years: number[]): HolidayCalendarSeedEntry[] {
  return buildEntries(years, [
    { name: "New Year's Day", resolveDate: (year) => observedUsFixedHoliday(year, 1, 1) },
    { name: "Martin Luther King Jr. Day", resolveDate: (year) => nthWeekdayOfMonth(year, 1, 1, 3) },
    { name: "Memorial Day", resolveDate: (year) => lastWeekdayOfMonth(year, 5, 1) },
    { name: "Independence Day", resolveDate: (year) => observedUsFixedHoliday(year, 7, 4) },
    { name: "Labor Day", resolveDate: (year) => nthWeekdayOfMonth(year, 9, 1, 1) },
    { name: "Thanksgiving Day", resolveDate: (year) => nthWeekdayOfMonth(year, 11, 4, 4) },
    { name: "Christmas Day", resolveDate: (year) => observedUsFixedHoliday(year, 12, 25) },
  ]);
}

function normalizeCountryCodes(countryCodes: string[]): Set<string> {
  return new Set(countryCodes.map((countryCode) => countryCode.trim().toUpperCase()));
}

function normalizeCityLookup(availableCitiesByCountry: Record<string, string[]>): Map<string, Set<string>> {
  const lookup = new Map<string, Set<string>>();

  for (const [countryCode, cityNames] of Object.entries(availableCitiesByCountry)) {
    lookup.set(countryCode.trim().toUpperCase(), new Set(cityNames));
  }

  return lookup;
}

function hasCity(cityLookup: Map<string, Set<string>>, countryCode: string, cityName: string): boolean {
  return cityLookup.get(countryCode)?.has(cityName) ?? false;
}

function germanStateDisplayName(stateCode: string): string {
  switch (stateCode) {
    case "BW":
      return "Baden-Wuerttemberg";
    case "BY":
      return "Bayern";
    case "HH":
      return "Hamburg";
    case "NW":
      return "Nordrhein-Westfalen";
    default:
      return stateCode;
  }
}

function buildCityEntries(years: number[], definitions: SimpleHolidayDefinition[]): HolidayCalendarSeedEntry[] {
  return buildEntries(years, definitions);
}

export function buildHolidayCalendarSeedDefinitions(
  context: SeedContext,
): HolidayCalendarSeedDefinition[] {
  const availableCountries = normalizeCountryCodes(context.availableCountryCodes);
  const cityLookup = normalizeCityLookup(context.availableCitiesByCountry);
  const definitions: HolidayCalendarSeedDefinition[] = [];

  if (availableCountries.has("DE")) {
    definitions.push({
      name: "Referenzfeiertage Deutschland 2026-2027",
      scopeType: "COUNTRY",
      countryCode: "DE",
      priority: COUNTRY_PRIORITY,
      entries: buildGermanCountryEntries(context.years),
    });

    const germanStates = new Set(
      [...(context.activeGermanStates ?? []), "BY", "NW"]
        .map((stateCode) => stateCode.trim().toUpperCase())
        .filter((stateCode) => ["BW", "BY", "HH", "NW"].includes(stateCode)),
    );

    for (const stateCode of [...germanStates].sort()) {
      const entries = buildGermanStateEntries(context.years, stateCode);
      if (entries.length === 0) {
        continue;
      }
      definitions.push({
        name: `Referenzfeiertage Deutschland - ${germanStateDisplayName(stateCode)} 2026-2027`,
        scopeType: "STATE",
        countryCode: "DE",
        stateCode,
        priority: STATE_PRIORITY,
        entries,
      });
    }

    if (hasCity(cityLookup, "DE", "Augsburg")) {
      definitions.push({
        name: "Referenzfeiertage Deutschland - Augsburg 2026-2027",
        scopeType: "CITY",
        countryCode: "DE",
        cityName: "Augsburg",
        priority: CITY_PRIORITY,
        entries: buildCityEntries(context.years, [
          { name: "Augsburger Friedensfest", resolveDate: (year) => toIsoDate(dateUtc(year, 8, 8)) },
        ]),
      });
    }
  }

  if (availableCountries.has("ES")) {
    definitions.push({
      name: "Referenzfeiertage Spanien 2026-2027",
      scopeType: "COUNTRY",
      countryCode: "ES",
      priority: COUNTRY_PRIORITY,
      entries: buildSpanishCountryEntries(context.years),
    });

    if (hasCity(cityLookup, "ES", "Madrid")) {
      definitions.push({
        name: "Referenzfeiertage Spanien - Madrid 2026-2027",
        scopeType: "CITY",
        countryCode: "ES",
        cityName: "Madrid",
        priority: CITY_PRIORITY,
        entries: buildEntries(context.years, [
          { name: "San Isidro", resolveDate: (year) => toIsoDate(dateUtc(year, 5, 15)) },
          { name: "Nuestra Senora de la Almudena", resolveDate: (year) => toIsoDate(dateUtc(year, 11, 9)) },
        ]),
      });
    }

    if (hasCity(cityLookup, "ES", "Barcelona")) {
      definitions.push({
        name: "Referenzfeiertage Spanien - Barcelona 2026-2027",
        scopeType: "CITY",
        countryCode: "ES",
        cityName: "Barcelona",
        priority: CITY_PRIORITY,
        entries: buildEntries(context.years, [
          { name: "La Merce", resolveDate: (year) => toIsoDate(dateUtc(year, 9, 24)) },
          { name: "Santa Eulalia", resolveDate: (year) => toIsoDate(dateUtc(year, 2, 12)) },
        ]),
      });
    }
  }

  if (availableCountries.has("IN")) {
    definitions.push({
      name: "Referenzfeiertage Indien 2026-2027",
      scopeType: "COUNTRY",
      countryCode: "IN",
      priority: COUNTRY_PRIORITY,
      entries: buildIndianCountryEntries(context.years),
    });
  }

  if (availableCountries.has("US")) {
    definitions.push({
      name: "Referenzfeiertage USA 2026-2027",
      scopeType: "COUNTRY",
      countryCode: "US",
      priority: COUNTRY_PRIORITY,
      entries: buildUsCountryEntries(context.years),
    });
  }

  return definitions;
}
