import { toZonedTime } from 'date-fns-tz';
import { getDailyAttendanceReport } from '../services/employeeTracker';
import { sendDailyAttendanceSummaryEmail } from '../utils/emailService';
import { toKolkataTime } from '../utils/timezone';
import CronRun, { CronRunStatus } from '../models/cronRun';
import logger from '../utils/logger';

const INTERVAL = 60 * 1000; // check every minute
const KOLKATA_TZ = 'Asia/Kolkata';
const JOB_NAME = 'daily_attendance_summary';
const DAY_MS = 24 * 60 * 60 * 1000;

let lastRunDateKey: string | null = null;

// Runs at 08:00 so the previous day is fully closed (auto-checkout runs at
// 23:59 the night before).
const isRunTimeInKolkata = (now: Date): boolean => {
  const zoned = toZonedTime(now, KOLKATA_TZ);
  return zoned.getHours() === 8 && zoned.getMinutes() === 0;
};

// IST has no DST, so "24h ago" is always the previous calendar day.
const previousKolkataDateKey = (now: Date): string =>
  toKolkataTime(new Date(now.getTime() - DAY_MS), 'yyyy-MM-dd');

const EMAIL_RE = /^[^\s@,;]+@[^\s@,;]+\.[^\s@,;]+$/;

// ADMIN_EMAIL holds one or more addresses separated by commas or semicolons,
// e.g. "a@x.com, b@x.com". Duplicates (case-insensitive) are dropped and
// malformed entries are reported instead of breaking the whole send.
const parseAdminRecipients = (): { valid: string[]; invalid: string[] } => {
  const seen = new Set<string>();
  const valid: string[] = [];
  const invalid: string[] = [];

  for (const raw of (process.env.ADMIN_EMAIL ?? '').split(/[,;]/)) {
    const email = raw.trim();
    if (!email) continue;
    if (!EMAIL_RE.test(email)) {
      invalid.push(email);
      continue;
    }
    const key = email.toLowerCase();
    if (seen.has(key)) continue;
    seen.add(key);
    valid.push(email);
  }

  return { valid, invalid };
};

export const runAttendanceSummaryCron = async (): Promise<void> => {
  const now = new Date();
  if (!isRunTimeInKolkata(now)) return;

  // The interval polls every minute, so guard against firing twice inside
  // the same run minute (e.g. on a slow tick).
  const reportDate = previousKolkataDateKey(now);
  if (lastRunDateKey === reportDate) return;
  lastRunDateKey = reportDate;

  // A restart inside the run minute resets the in-memory guard; don't email
  // the admin a second time if this date was already sent.
  try {
    const alreadySent = await CronRun.exists({
      job_name: JOB_NAME,
      status: 'SUCCESS',
      'result.reportDate': reportDate,
    });
    if (alreadySent) {
      logger.info('[Cron] Attendance summary already sent, skipping', { reportDate });
      return;
    }
  } catch (e) {
    // Better to risk a duplicate than to skip the report entirely.
    logger.warn('[Cron] Could not check previous attendance summary runs', {
      reportDate,
      error: (e as Error).message,
    });
  }

  const startedAt = new Date();
  logger.info('[Cron] Attendance summary run started', { reportDate });

  let status: CronRunStatus = 'SUCCESS';
  let result: Record<string, unknown> = { reportDate };
  let errorMessage: string | undefined;

  try {
    const { valid: recipients, invalid } = parseAdminRecipients();
    if (invalid.length > 0) {
      logger.warn('[Cron] Ignoring invalid ADMIN_EMAIL entries', { reportDate, invalid });
    }
    if (recipients.length === 0) {
      throw new Error('ADMIN_EMAIL has no valid email addresses configured');
    }

    const rows = await getDailyAttendanceReport(reportDate);
    const present = rows.filter((r) => r.status !== 'Absent').length;

    // One email per admin: recipients don't see each other's addresses, and
    // a rejected address doesn't stop the others from getting the report.
    const outcomes = await Promise.allSettled(
      recipients.map((to) => sendDailyAttendanceSummaryEmail(to, reportDate, rows)),
    );

    const sentTo: string[] = [];
    const failedTo: { email: string; error: string }[] = [];
    outcomes.forEach((outcome, i) => {
      const email = recipients[i];
      if (outcome.status === 'fulfilled') {
        sentTo.push(email);
      } else {
        const error = (outcome.reason as Error)?.message ?? String(outcome.reason);
        failedTo.push({ email, error });
        logger.error('[Cron] Attendance summary email failed for recipient', {
          reportDate,
          email,
          error,
        });
      }
    });

    result = {
      reportDate,
      sentTo,
      failedTo,
      invalidRecipients: invalid,
      employees: rows.length,
      present,
      absent: rows.length - present,
      autoCheckedOut: rows.filter((r) => r.autoCheckedOut).length,
    };

    if (sentTo.length === 0) {
      throw new Error(`Email send failed for all ${recipients.length} recipient(s)`);
    }
    if (failedTo.length > 0) {
      status = 'PARTIAL';
      errorMessage = `Email send failed for ${failedTo.length} of ${recipients.length} recipient(s)`;
    }

    logger.info(
      `[Cron] Attendance summary sent to ${sentTo.length}/${recipients.length} recipient(s)`,
      { ...result, status },
    );
  } catch (e) {
    status = 'FAILED';
    errorMessage = (e as Error).message;
    logger.error('[Cron] Attendance summary run failed', {
      reportDate,
      error: errorMessage,
      stack: (e as Error).stack,
    });
  }

  const finishedAt = new Date();
  try {
    await CronRun.create({
      job_name: JOB_NAME,
      status,
      started_at: startedAt,
      finished_at: finishedAt,
      duration_ms: finishedAt.getTime() - startedAt.getTime(),
      result,
      error: errorMessage,
    });
  } catch (e) {
    logger.error('[Cron] Failed to save attendance summary run record', {
      reportDate,
      error: (e as Error).message,
    });
  }
};

export const startAttendanceSummaryCron = (): void => {
  const { valid, invalid } = parseAdminRecipients();
  if (invalid.length > 0) {
    logger.warn('[Cron] ADMIN_EMAIL contains invalid entries that will be skipped', { invalid });
  }
  if (valid.length === 0) {
    logger.warn('[Cron] ADMIN_EMAIL has no valid addresses — daily attendance summary will fail until it does');
  } else {
    logger.info(`[Cron] Attendance summary recipients: ${valid.length}`, { recipients: valid });
  }

  setInterval(() => {
    runAttendanceSummaryCron().catch((e) =>
      logger.error('[Cron] Attendance summary failed', { error: (e as Error).message }),
    );
  }, INTERVAL);

  logger.info('[Cron] Attendance summary scheduler started (daily at 08:00 Asia/Kolkata)');
};
