import { getHolidays } from "../../utils/czech-holidays";

interface AttendanceRecord {
  user_id?: number;
  arrival_time?: string | null;
  departure_time?: string | null;
  break_start?: string | null;
  break_end?: string | null;
  leave_type?: string;
  leave_hours?: number;
  shift_date?: string;
  notes?: string | null;
  project_logs?: Array<{
    id?: number;
    project_id?: number;
    project_name?: string;
    started_at?: string;
    ended_at?: string | null;
    hours?: string | number | null;
    minutes?: string | number | null;
  }>;
}

// formatDate is defined once in ./formatters and re-exported here so existing
// importers of attendanceHelpers keep working without a duplicate definition.
export { formatDate } from "./formatters";

/** Extract time as HH:MM from a datetime string without timezone conversion */
const extractTime = (datetime: string): string => {
  // Try ISO format: "2026-03-23T08:00:00.000Z" or "2026-03-23T08:00:00"
  const tMatch = datetime.match(/T(\d{2}):(\d{2})/);
  if (tMatch) return `${tMatch[1]}:${tMatch[2]}`;
  // Try space format: "2026-03-23 08:00:00"
  const sMatch = datetime.match(/\s(\d{2}):(\d{2})/);
  if (sMatch) return `${sMatch[1]}:${sMatch[2]}`;
  // Fallback: try parsing time-only "08:00"
  const hMatch = datetime.match(/^(\d{2}):(\d{2})/);
  if (hMatch) return `${hMatch[1]}:${hMatch[2]}`;
  return datetime;
};

export const formatDatetime = (datetime: string | null | undefined): string => {
  if (!datetime) return "—";
  // Extract date part without timezone conversion
  const dMatch = datetime.match(/(\d{4})-(\d{2})-(\d{2})/);
  const datePart = dMatch
    ? `${parseInt(dMatch[3])}.${parseInt(dMatch[2])}.`
    : "";
  return `${datePart} ${extractTime(datetime)}`;
};

export const formatTime = (datetime: string | null | undefined): string => {
  if (!datetime) return "—";
  return extractTime(datetime);
};

export const calculateWorkMinutes = (record: AttendanceRecord): number => {
  if (!record.arrival_time || !record.departure_time) return 0;
  const arrival = new Date(record.arrival_time).getTime();
  const departure = new Date(record.departure_time).getTime();
  let minutes = (departure - arrival) / 60000;

  if (record.break_start && record.break_end) {
    const breakStart = new Date(record.break_start).getTime();
    const breakEnd = new Date(record.break_end).getTime();
    minutes -= (breakEnd - breakStart) / 60000;
  }

  return Math.max(0, Math.floor(minutes));
};

export const formatMinutes = (minutes: number, withUnit = false): string => {
  const h = Math.floor(minutes / 60);
  const m = minutes % 60;
  return `${h}:${String(m).padStart(2, "0")}${withUnit ? " h" : ""}`;
};

export const getLeaveTypeName = (type: string): string => {
  const types: Record<string, string> = {
    work: "Práce",
    vacation: "Dovolená",
    sick: "Nemoc",
    unpaid: "Neplacené volno",
  };
  return types[type] || "Práce";
};

export const getLeaveTypeBadgeClass = (type: string): string => {
  const classes: Record<string, string> = {
    vacation: "badge-vacation",
    sick: "badge-sick",
    unpaid: "badge-unpaid",
  };
  return classes[type] || "";
};

export const getDatePart = (datetime: string | null | undefined): string => {
  if (!datetime) return "";
  if (datetime.includes("T")) {
    return datetime.split("T")[0];
  }
  return datetime.split(" ")[0];
};

export const getTimePart = (datetime: string | null | undefined): string => {
  if (!datetime) return "";
  // Parse HH:MM directly from the string (no `new Date(...).getHours()`), so
  // the displayed time matches the stored wall-clock time regardless of the
  // browser timezone — same ethos as the sibling `extractTime` helper.
  return extractTime(datetime);
};

/**
 * Join a date input ("YYYY-MM-DD") and a time input ("HH:MM") into the
 * combined local-datetime wire format the attendance API expects
 * ("YYYY-MM-DDTHH:MM:00", validated server-side by `nullableDateTimeString`
 * and parsed with `new Date(...)` as local time). Returns null when either
 * part is unset — "no value" for the optional datetime fields.
 */
export const combineDatetime = (date: string, time: string): string | null =>
  date && time ? `${date}T${time}:00` : null;

export const calcProjectMinutesTotal = (
  logs: Array<{
    project_id?: number;
    hours?: string | number;
    minutes?: string | number;
  }>,
): number => {
  return logs
    .filter((l) => l.project_id)
    .reduce((sum, l) => {
      return (
        sum +
        (parseInt(String(l.hours)) || 0) * 60 +
        (parseInt(String(l.minutes)) || 0)
      );
    }, 0);
};

interface ShiftForm {
  arrival_time?: string;
  departure_time?: string;
  arrival_date?: string;
  departure_date?: string;
  break_start_time?: string;
  break_end_time?: string;
  break_start_date?: string;
  break_end_date?: string;
}

export const calcFormWorkMinutes = (form: ShiftForm): number => {
  if (!form.arrival_time || !form.departure_time) return 0;
  const arrivalStr = `${form.arrival_date}T${form.arrival_time}`;
  const departureStr = `${form.departure_date}T${form.departure_time}`;
  let mins =
    (new Date(departureStr).getTime() - new Date(arrivalStr).getTime()) / 60000;
  if (form.break_start_time && form.break_end_time) {
    const bsStr = `${form.break_start_date}T${form.break_start_time}`;
    const beStr = `${form.break_end_date}T${form.break_end_time}`;
    mins -= (new Date(beStr).getTime() - new Date(bsStr).getTime()) / 60000;
  }
  return Math.max(0, Math.floor(mins));
};

export const formatTimeOrDatetimePrint = (
  datetime: string | null | undefined,
  shiftDate: string,
): string => {
  if (!datetime) return "—";
  // Extract date from the datetime string directly (no timezone conversion)
  const dateMatch = datetime.match(/(\d{4})-(\d{2})-(\d{2})/);
  const timeDate = dateMatch
    ? `${dateMatch[1]}-${dateMatch[2]}-${dateMatch[3]}`
    : "";
  if (timeDate && timeDate !== shiftDate) {
    const datePart = `${parseInt(dateMatch![3])}.${parseInt(dateMatch![2])}.`;
    return `${datePart} ${extractTime(datetime)}`;
  }
  return extractTime(datetime);
};

export const calculateWorkMinutesPrint = (record: AttendanceRecord): number => {
  const leaveType = record.leave_type || "work";
  if (leaveType !== "work") {
    return (Number(record.leave_hours) || 8) * 60;
  }
  if (!record.arrival_time || !record.departure_time) return 0;
  const arrival = new Date(record.arrival_time).getTime();
  const departure = new Date(record.departure_time).getTime();
  let minutes = (departure - arrival) / 60000;

  if (record.break_start && record.break_end) {
    const breakStart = new Date(record.break_start).getTime();
    const breakEnd = new Date(record.break_end).getTime();
    minutes -= (breakEnd - breakStart) / 60000;
  }

  return Math.max(0, Math.floor(minutes));
};

// ---------------------------------------------------------------------------
// Print template helpers (mzda-style counting)
// ---------------------------------------------------------------------------

/** Returns the Czech weekday name with the first letter capitalized. */
export const getCzechWeekday = (dateStr: string): string => {
  const wd = new Date(dateStr).toLocaleDateString("cs-CZ", {
    weekday: "long",
  });
  return wd.charAt(0).toUpperCase() + wd.slice(1);
};

/** True if the date is a Saturday or Sunday. */
export const isWeekendDate = (dateStr: string): boolean => {
  const dow = new Date(dateStr).getDay();
  return dow === 0 || dow === 6;
};

/** Number of days in a month (1-12). e.g. May 2026 → 31. */
export const getDaysInMonth = (year: number, month1to12: number): number => {
  return new Date(year, month1to12, 0).getDate();
};

/** Build a "YYYY-MM-DD" string for a given year/month/day. */
export const shiftDateForMonth = (
  year: number,
  month1to12: number,
  day: number,
): string => {
  return `${year}-${String(month1to12).padStart(2, "0")}-${String(day).padStart(2, "0")}`;
};

/** Format minutes as decimal hours, comma separator, always 2 decimals. */
export const formatHoursDecimal = (minutes: number): string => {
  return ((minutes / 60).toFixed(2) as string).replace(".", ",");
};

/** Format minutes as decimal hours, comma separator, 1 decimal. */
export const formatHoursDecimal1 = (minutes: number): string => {
  return ((minutes / 60).toFixed(1) as string).replace(".", ",");
};

/**
 * Compute minutes that fall within the 22:00-06:00 night window (Czech law §94).
 *
 * Algorithm (per Czech spec):
 *  1. Subtract all break intervals from the shift interval → list of worked
 *     sub-intervals.
 *  2. For each worked sub-interval, compute its intersection with the
 *     22:00-06:00 night window. Iterate from (sub-interval start day - 1)
 *     to (sub-interval end day + 1) to be safe with cross-midnight shifts.
 *  3. Sum all overlaps in minutes.
 */
export const calculateNightMinutes = (record: AttendanceRecord): number => {
  if (!record.arrival_time || !record.departure_time) return 0;

  const startMs = new Date(record.arrival_time).getTime();
  let endMs = new Date(record.departure_time).getTime();
  if (!Number.isFinite(startMs) || !Number.isFinite(endMs)) return 0;
  if (endMs <= startMs) endMs += 24 * 60 * 60 * 1000;

  // STEP 1: subtract breaks from the shift interval.
  let intervals: Array<[number, number]> = [[startMs, endMs]];
  if (record.break_start && record.break_end) {
    const bStart = new Date(record.break_start).getTime();
    const bEnd = new Date(record.break_end).getTime();
    if (Number.isFinite(bStart) && Number.isFinite(bEnd) && bEnd > bStart) {
      const next: Array<[number, number]> = [];
      for (const [s, e] of intervals) {
        if (bEnd <= s || bStart >= e) {
          next.push([s, e]);
        } else {
          if (bStart > s) next.push([s, bStart]);
          if (bEnd < e) next.push([bEnd, e]);
        }
      }
      intervals = next;
    }
  }

  // Helper: midnight of the local day for a timestamp.
  const dayMidnight = (ms: number): number => {
    const d = new Date(ms);
    return new Date(
      d.getFullYear(),
      d.getMonth(),
      d.getDate(),
      0,
      0,
      0,
      0,
    ).getTime();
  };

  // STEP 2: for each worked sub-interval, intersect with every relevant
  // 22:00-06:00 night window. A 24h shift touches at most 2 windows, but
  // we iterate (startDay − 1) to (endDay + 1) to be defensive.
  let totalMs = 0;
  for (const [s, e] of intervals) {
    const fromDay = dayMidnight(s) - 24 * 60 * 60 * 1000;
    const toDay = dayMidnight(e) + 24 * 60 * 60 * 1000;
    for (let day = fromDay; day <= toDay; day += 24 * 60 * 60 * 1000) {
      const winStart = day + 22 * 60 * 60 * 1000;
      const winEnd = day + 30 * 60 * 60 * 1000; // 06:00 next day
      const ov = Math.max(0, Math.min(e, winEnd) - Math.max(s, winStart));
      totalMs += ov;
    }
  }

  return Math.floor(totalMs / 60000);
};

/**
 * Normalize a date string to "YYYY-MM-DD".
 * Accepts both "2026-05-15" and "2026-05-15T00:00:00" / "2026-05-15T12:00:00"
 * and returns just the date part.
 */
export const normalizeDateStr = (
  dateStr: string | null | undefined,
): string => {
  if (!dateStr) return "";
  const m = String(dateStr).match(/^(\d{4})-(\d{2})-(\d{2})/);
  return m ? `${m[1]}-${m[2]}-${m[3]}` : "";
};

/** All Czech public holidays in a given month (1-12) as YYYY-MM-DD. */
export const holidaysInMonth = (year: number, month1to12: number): string[] => {
  const prefix = `${year}-${String(month1to12).padStart(2, "0")}-`;
  return getHolidays(year).filter((d) => d.startsWith(prefix));
};

/**
 * Hours the user "got for free" because they didn't work on a holiday.
 * Returns 8h for each holiday in the month that has no work record.
 * Leave records (vacation, sick, holiday, unpaid) on a holiday date do NOT
 * count as work — the user must have an actual work shift to forfeit the
 * 100% bonus.
 */
export const calculateFreeHolidayHours = (
  records: AttendanceRecord[],
  holidayDates: string[],
): number => {
  const workedDates = new Set(
    records
      .filter((r) => (r.leave_type || "work") === "work")
      .map((r) => normalizeDateStr(r.shift_date))
      .filter(Boolean),
  );
  let free = 0;
  for (const hd of holidayDates) {
    if (!workedDates.has(hd)) free += 8;
  }
  return free;
};
