import Attendance from '../models/attendance';
import User from '../models/users';
import DesktopSession from '../models/desktopSession';
import { toKolkataTime, fromKolkataTime } from '../utils/timezone';
import { notifyAttendanceStreak } from './notificationService';
import { getDailyAttendanceReport } from './employeeTracker';

const err = (msg: string, code = 400): Error & { statusCode: number } =>
  Object.assign(new Error(msg), { statusCode: code });

const toDateStr = (d: Date): string =>
  `${d.getFullYear()}-${String(d.getMonth() + 1).padStart(2, '0')}-${String(d.getDate()).padStart(2, '0')}`;

const diffDays = (a: string, b: string): number => {
  const msA = new Date(a).getTime();
  const msB = new Date(b).getTime();
  return Math.round((msB - msA) / (1000 * 60 * 60 * 24));
};

// ─── Check In ────────────────────────────────────────────────────────────────

export const checkIn = async (userId: string) => {
  const now = new Date();
  const today = toDateStr(now);

  let attendance = await Attendance.findOne({ user: userId, date: today });

  if (attendance) {
    // Check if there's an open session (already checked in, not checked out)
    const openSession = attendance.sessions.find((s) => s.check_out === null);
    if (openSession) {
      throw err('Already checked in. Please check out first.');
    }
    // Add new session for the day
    attendance.sessions.push({ check_in: now, check_out: null });
    await attendance.save();
  } else {
    // First check-in of the day
    attendance = await Attendance.create({
      user: userId,
      date: today,
      sessions: [{ check_in: now, check_out: null }],
    });
  }

  // Update streak
  const user = await User.findById(userId).select('currentStreak longestStreak lastCheckInDate');
  if (!user) throw err('User not found', 404);

  const previousStreak = user.currentStreak;
  let streakWarning: string | null = null;

  if (user.lastCheckInDate === today) {
    // Already checked in today, streak already updated — no change
  } else if (user.lastCheckInDate) {
    const gap = diffDays(user.lastCheckInDate, today);

    if (gap === 1) {
      // Consecutive day
      user.currentStreak += 1;
    } else if (gap === 2) {
      // Missed exactly one day — warning only, streak continues
      streakWarning = `You missed a day! Check in tomorrow to keep your ${user.currentStreak} day streak.`;
      user.currentStreak += 1;
    } else {
      // Missed more than one day — streak resets
      user.currentStreak = 1;
    }
  } else {
    // First ever check-in
    user.currentStreak = 1;
  }

  if (user.currentStreak > user.longestStreak) {
    user.longestStreak = user.currentStreak;
  }

  user.lastCheckInDate = today;
  await user.save();

  if (user.currentStreak !== previousStreak || streakWarning) {
    await notifyAttendanceStreak(userId, user.currentStreak, streakWarning);
  }

  // Format session times to Asia/Kolkata
  const formattedSessions = attendance.sessions.map((s) => ({
    check_in: toKolkataTime(s.check_in),
    check_out: s.check_out ? toKolkataTime(s.check_out) : null,
  }));

  return {
    attendance: {
      date: attendance.date,
      sessions: formattedSessions,
      total_minutes: attendance.total_minutes,
    },
    streak: {
      current: user.currentStreak,
      longest: user.longestStreak,
      warning: streakWarning,
    },
  };
};

// ─── Check Out ───────────────────────────────────────────────────────────────

export const checkOut = async (userId: string) => {
  const now = new Date();
  const today = toDateStr(now);

  const attendance = await Attendance.findOne({ user: userId, date: today });
  if (!attendance) throw err('No check-in found for today');

  const openSession = attendance.sessions.find((s) => s.check_out === null);
  if (!openSession) throw err('No active session. Please check in first.');

  openSession.check_out = now;

  // Recalculate total minutes for the day
  attendance.total_minutes = attendance.sessions.reduce((sum, s) => {
    if (s.check_in && s.check_out) {
      return sum + (new Date(s.check_out).getTime() - new Date(s.check_in).getTime()) / 60000;
    }
    return sum;
  }, 0);
  attendance.total_minutes = Math.round(attendance.total_minutes);

  await attendance.save();

  // Format session times to Asia/Kolkata
  const formattedSessions = attendance.sessions.map((s) => ({
    check_in: toKolkataTime(s.check_in),
    check_out: s.check_out ? toKolkataTime(s.check_out) : null,
  }));
  return {
    date: attendance.date,
    sessions: formattedSessions,
    total_minutes: attendance.total_minutes,
  };
};

// ─── Get Today's Status ──────────────────────────────────────────────────────

export const getTodayStatus = async (userId: string) => {
  const today = toDateStr(new Date());
  const attendance = await Attendance.findOne({ user: userId, date: today }).lean();
  const user = await User.findById(userId).select('currentStreak longestStreak lastCheckInDate').lean();

  const isCheckedIn = attendance?.sessions.some((s) => s.check_out === null) || false;

  return {
    date: today,
    is_checked_in: isCheckedIn,
    sessions: attendance?.sessions || [],
    total_minutes: attendance?.total_minutes || 0,
    streak: {
      current: user?.currentStreak || 0,
      longest: user?.longestStreak || 0,
    },
  };
};

// ─── Get User Attendance History ─────────────────────────────────────────────

export const getMyAttendance = async (userId: string, month?: string) => {
  const filter: Record<string, unknown> = { user: userId };

  if (month) {
    // month format: "2026-04" → match dates starting with "2026-04"
    filter.date = { $regex: `^${month}` };
  }

  const records = await Attendance.find(filter)
    .select('date sessions total_minutes')
    .sort({ date: -1 })
    .lean();

  const total_days = records.length;
  const total_hours = Math.round(records.reduce((sum, r) => sum + r.total_minutes, 0) / 60 * 10) / 10;

  return { total_days, total_hours, records };
};

// ─── Admin: Get User Attendance ──────────────────────────────────────────────

export const getUserAttendance = async (userId: string, month?: string) => {
  const user = await User.findById(userId).select('name email currentStreak longestStreak').lean();
  if (!user) throw err('User not found', 404);

  const { total_days, total_hours, records } = await getMyAttendance(userId, month);

  return {
    user: { _id: user._id, name: user.name, email: user.email },
    streak: { current: user.currentStreak, longest: user.longestStreak },
    total_days,
    total_hours,
    records,
  };
};

// ─── Admin: Get All Attendance for a Date ────────────────────────────────────

// Same per-employee rows as the daily summary email (getDailyAttendanceReport,
// durations in seconds, absent employees included), plus each employee's
// CHECKIN/CHECKOUT pairs. Built from desktop tracker logs — the Attendance
// collection is only written by the unused web check-in.
export const getAttendanceByDate = async (date: string, page: number, limit: number) => {
  if (!/^\d{4}-\d{2}-\d{2}$/.test(date)) throw err('Invalid date, expected yyyy-MM-dd');

  const dayStart = fromKolkataTime(`${date} 00:00:00`);
  const dayEnd = new Date(dayStart.getTime() + 24 * 60 * 60 * 1000);

  const [logs, report] = await Promise.all([
    DesktopSession.find({
      log_type: { $in: ['CHECKIN', 'CHECKOUT'] },
      timestamp: { $gte: dayStart, $lt: dayEnd },
    })
      .select('user log_type timestamp')
      .sort({ timestamp: 1 })
      .lean(),
    getDailyAttendanceReport(date),
  ]);

  // Pair each CHECKIN with the next CHECKOUT; an unmatched CHECKIN stays open.
  const sessionsByUser = new Map<string, { check_in: Date; check_out: Date | null }[]>();
  for (const log of logs) {
    const userId = log.user.toString();
    const sessions = sessionsByUser.get(userId) ?? [];
    sessionsByUser.set(userId, sessions);
    const last = sessions[sessions.length - 1];
    if (log.log_type === 'CHECKIN') {
      if (!last || last.check_out) sessions.push({ check_in: log.timestamp, check_out: null });
    } else if (last && !last.check_out) {
      last.check_out = log.timestamp;
    }
  }

  const present = report.filter((row) => row.status !== 'Absent').length;
  const total = report.length;
  const totalPages = Math.ceil(total / limit);

  return {
    date,
    summary: {
      total,
      present,
      absent: total - present,
      autoCheckedOut: report.filter((row) => row.autoCheckedOut).length,
    },
    records: report
      .slice((page - 1) * limit, page * limit)
      .map((row) => ({ ...row, sessions: sessionsByUser.get(row.userId) ?? [] })),
    pagination: {
      total,
      page,
      limit,
      totalPages,
      hasNextPage: page < totalPages,
      hasPrevPage: page > 1,
    },
  };
};
