import { Injectable } from '@nestjs/common';
import { PrismaService } from '../prisma/prisma.service';
import { Cron } from '@nestjs/schedule';

@Injectable()
export class IotService {
  constructor(private prisma: PrismaService) {}

  getHello(): string {
    return 'Hello World!';
  }

  getTime() {
    // Get current time in Berlin timezone
    const now = new Date();
    const berlinTime = new Date(now.toLocaleString('en-US', { timeZone: 'Europe/Berlin' }));
    const unixTimestamp = Math.floor(berlinTime.getTime() / 1000);
    
    return {
      unix: unixTimestamp,
      readable: berlinTime.toLocaleString('de-DE', { timeZone: 'Europe/Berlin' })
    };
  }

  async receiveScan(uid: string, timestamp: number) {
    const scanTime = new Date(timestamp * 1000);
    
    const berlinTime = scanTime.toLocaleString('de-DE', { 
      timeZone: 'Europe/Berlin',
      year: 'numeric',
      month: '2-digit',
      day: '2-digit',
      hour: '2-digit',
      minute: '2-digit',
      second: '2-digit'
    });
    
    // Find student by RFID UID
    const student = await this.prisma.student.findUnique({
      where: { studentRfidCardUid: uid }
    });
    
    // Check if card is unregistered or disabled
    if (!student) {
      console.log(`[RFID Scan] UID: ${uid} | Time: ${berlinTime} | WARNING: Unregistered card`);
      return 'BLOCKED';
    }
    
    if (!student.studentCardIsActive) {
      console.log(`[RFID Scan] ${student.studentFirstName} ${student.studentLastName} | WARNING: Card disabled`);
      return 'BLOCKED';
    }
    
    // Use transaction to prevent race conditions during rapid cached uploads
    const result = await this.prisma.$transaction(async (tx) => {
      // Get last scan (any day) with row lock
      const lastScan = await tx.attendanceLog.findFirst({
        where: { studentId: student.studentId },
        orderBy: { attendanceScannedAt: 'desc' }
      });
      
      let eventType: string;
      
      if (!lastScan) {
        // First scan ever = IN
        eventType = 'IN';
      } else {
        // Convert both times to Berlin timezone for comparison
        const lastScanBerlin = new Date(lastScan.attendanceScannedAt.toLocaleString('en-US', { timeZone: 'Europe/Berlin' }));
        const currentBerlin = new Date(scanTime.toLocaleString('en-US', { timeZone: 'Europe/Berlin' }));
        
        console.log(`[Debug] Last scan: ${lastScanBerlin.toISOString()} | Current: ${currentBerlin.toISOString()}`);
        console.log(`[Debug] Last event type: ${lastScan.attendanceEventType}`);
        
        // Check if last scan was on a different day (in Berlin timezone)
        const isDifferentDay = 
          lastScanBerlin.getDate() !== currentBerlin.getDate() ||
          lastScanBerlin.getMonth() !== currentBerlin.getMonth() ||
          lastScanBerlin.getFullYear() !== currentBerlin.getFullYear();
        
        console.log(`[Debug] Is different day? ${isDifferentDay}`);
        
        if (isDifferentDay) {
          // Different day = always start with IN
          eventType = 'IN';
          
          // Auto-close previous day if still IN
          if (lastScan.attendanceEventType === 'IN') {
            const endOfLastDay = new Date(lastScanBerlin);
            endOfLastDay.setHours(23, 59, 59, 999);
            
            await tx.attendanceLog.create({
              data: {
                studentId: student.studentId,
                attendanceScannedAt: endOfLastDay,
                attendanceEventType: 'OUT',
                wasManualEntry: true,
                manualEntryReason: 'Auto-closed: forgot to sign out'
              }
            });
            
            console.log(`[Auto Close] ${student.studentFirstName} ${student.studentLastName} at 23:59:59`);
          }
        } else {
          // Same day = toggle
          eventType = lastScan.attendanceEventType === 'IN' ? 'OUT' : 'IN';
        }
      }
      
      // Save scan within transaction
      await tx.attendanceLog.create({
        data: {
          studentId: student.studentId,
          attendanceScannedAt: scanTime,
          attendanceEventType: eventType,
          wasManualEntry: false
        }
      });
      
      return eventType;
    });
    
    const eventType = result;
    
    const action = eventType === 'IN' ? 'SIGN IN' : 'SIGN OUT';
    const studentName = `${student.studentFirstName} ${student.studentLastName}`;
    
    console.log(`[Attendance] ${studentName} | ${action} | Time: ${berlinTime} | Saved to DB`);
    
    // Return specific response for ESP32
    return eventType === 'IN' ? 'SIGNIN' : 'SIGNOUT';
  }

  // Register new student
  async registerStudent(firstName: string, lastName: string, className: string, rfidUid: string) {
    const student = await this.prisma.student.create({
      data: {
        studentFirstName: firstName,
        studentLastName: lastName,
        studentClassName: className,
        studentRfidCardUid: rfidUid,
        studentCardIsActive: true
      }
    });
    
    console.log(`[Student] Registered: ${firstName} ${lastName} (${className}) with card ${rfidUid}`);
    
    return student;
  }

  // Get all students
  async getAllStudents() {
    return await this.prisma.student.findMany({
      orderBy: { studentLastName: 'asc' }
    });
  }

  // Get student by ID
  async getStudent(id: number) {
    return await this.prisma.student.findUnique({
      where: { studentId: id },
      include: {
        attendanceLogs: {
          orderBy: { attendanceScannedAt: 'desc' },
          take: 20
        }
      }
    });
  }

  // Update student RFID card
  async assignCard(id: number, rfidUid: string) {
    const student = await this.prisma.student.update({
      where: { studentId: id },
      data: { studentRfidCardUid: rfidUid }
    });
    
    console.log(`[Student] Assigned card ${rfidUid} to ${student.studentFirstName} ${student.studentLastName}`);
    
    return student;
  }

  // Update student information
  async updateStudent(id: number, firstName?: string, lastName?: string, className?: string, rfidUid?: string) {
    const data: any = {};
    if (firstName) data.studentFirstName = firstName;
    if (lastName) data.studentLastName = lastName;
    if (className) data.studentClassName = className;
    if (rfidUid) data.studentRfidCardUid = rfidUid;
    
    const student = await this.prisma.student.update({
      where: { studentId: id },
      data
    });
    
    console.log(`[Student] Updated: ${student.studentFirstName} ${student.studentLastName}`);
    
    return student;
  }

  // Enable/Disable student card
  async setCardActive(id: number, isActive: boolean) {
    const student = await this.prisma.student.update({
      where: { studentId: id },
      data: { studentCardIsActive: isActive }
    });
    
    const status = isActive ? 'enabled' : 'disabled';
    console.log(`[Student] Card ${status} for ${student.studentFirstName} ${student.studentLastName}`);
    
    return student;
  }

  // Delete student and all attendance logs
  async deleteStudent(id: number) {
    const student = await this.prisma.student.delete({
      where: { studentId: id }
    });
    
    console.log(`[Student] Deleted: ${student.studentFirstName} ${student.studentLastName} and all attendance logs`);
    
    return student;
  }

  // Get currently present students
  async getCurrentlyPresent() {
    const today = new Date();
    today.setHours(0, 0, 0, 0);
    
    // Get all active students with their last scan of the day
    const students = await this.prisma.student.findMany({
      where: { studentCardIsActive: true },
      include: {
        attendanceLogs: {
          where: {
            attendanceScannedAt: { gte: today }
          },
          orderBy: { attendanceScannedAt: 'desc' },
          take: 1
        }
      }
    });
    
    // Filter to only those whose last scan was IN
    const present = students
      .filter(s => s.attendanceLogs.length > 0 && s.attendanceLogs[0].attendanceEventType === 'IN')
      .map(s => ({
        studentId: s.studentId,
        name: `${s.studentFirstName} ${s.studentLastName}`,
        className: s.studentClassName,
        checkInTime: s.attendanceLogs[0].attendanceScannedAt,
        minutesPresent: Math.floor((Date.now() - s.attendanceLogs[0].attendanceScannedAt.getTime()) / 60000)
      }));
    
    // Get absent students (no scans today)
    const absent = students
      .filter(s => s.attendanceLogs.length === 0)
      .map(s => ({
        studentId: s.studentId,
        name: `${s.studentFirstName} ${s.studentLastName}`,
        className: s.studentClassName
      }));
    
    return {
      date: new Date().toISOString().split('T')[0],
      time: new Date().toLocaleTimeString('de-DE'),
      presentStudents: present,
      absentStudents: absent,
      totalPresent: present.length,
      totalAbsent: absent.length
    };
  }

  // Get all scans for a specific date
  async getScansForDate(dateString: string) {
    const date = new Date(dateString);
    date.setHours(0, 0, 0, 0);
    
    const nextDay = new Date(date);
    nextDay.setDate(nextDay.getDate() + 1);
    
    const logs = await this.prisma.attendanceLog.findMany({
      where: {
        attendanceScannedAt: {
          gte: date,
          lt: nextDay
        }
      },
      include: {
        student: true
      },
      orderBy: { attendanceScannedAt: 'asc' }
    });
    
    // Group by student
    const studentLogs = new Map();
    
    logs.forEach(log => {
      const studentId = log.studentId;
      if (!studentLogs.has(studentId)) {
        studentLogs.set(studentId, {
          studentId: studentId,
          name: `${log.student.studentFirstName} ${log.student.studentLastName}`,
          className: log.student.studentClassName,
          logs: []
        });
      }
      studentLogs.get(studentId).logs.push({
        time: log.attendanceScannedAt,
        action: log.attendanceEventType,
        wasManual: log.wasManualEntry,
        reason: log.manualEntryReason
      });
    });
    
    return {
      date: dateString,
      students: Array.from(studentLogs.values())
    };
  }

  // Get all attendance logs for a specific student
  async getStudentAttendance(id: number) {
    const student = await this.prisma.student.findUnique({
      where: { studentId: id },
      include: {
        attendanceLogs: {
          orderBy: { attendanceScannedAt: 'desc' }
        }
      }
    });

    if (!student) {
      throw new Error('Student not found');
    }

    return {
      studentId: student.studentId,
      name: `${student.studentFirstName} ${student.studentLastName}`,
      className: student.studentClassName,
      logs: student.attendanceLogs.map(log => ({
        id: log.attendanceLogId,
        time: log.attendanceScannedAt,
        action: log.attendanceEventType,
        wasManual: log.wasManualEntry,
        reason: log.manualEntryReason
      }))
    };
  }

  // Get all attendance logs (for sorting/filtering)
  async getAllAttendanceLogs() {
    const logs = await this.prisma.attendanceLog.findMany({
      include: {
        student: true
      },
      orderBy: { attendanceScannedAt: 'desc' }
    });

    return logs.map(log => ({
      id: log.attendanceLogId,
      studentId: log.studentId,
      studentName: `${log.student.studentFirstName} ${log.student.studentLastName}`,
      className: log.student.studentClassName,
      time: log.attendanceScannedAt,
      action: log.attendanceEventType,
      wasManual: log.wasManualEntry,
      reason: log.manualEntryReason
    }));
  }

  // Admin: Manual attendance entry
  async addManualEntry(studentId: number, scannedAt: Date, eventType: string, reason: string) {
    const student = await this.prisma.student.findUnique({
      where: { studentId }
    });
    
    if (!student) {
      throw new Error('Student not found');
    }
    
    const log = await this.prisma.attendanceLog.create({
      data: {
        studentId,
        attendanceScannedAt: scannedAt,
        attendanceEventType: eventType,
        wasManualEntry: true,
        manualEntryReason: reason
      }
    });
    
    console.log(`[Manual Entry] ${student.studentFirstName} ${student.studentLastName} | ${eventType} | ${reason}`);
    return log;
  }

  // Admin: Delete attendance log
  async deleteAttendanceLog(logId: number) {
    const log = await this.prisma.attendanceLog.delete({
      where: { attendanceLogId: logId }
    });
    
    console.log(`[Delete Log] ID ${logId} deleted`);
    
    return log;
  }

  // Cron job: Auto sign-out at midnight
  @Cron('0 0 * * *') // Runs at 00:00 every day
  async autoSignOutAtMidnight() {
    console.log('[Cron] Running auto sign-out at midnight...');
    
    const yesterday = new Date();
    yesterday.setDate(yesterday.getDate() - 1);
    yesterday.setHours(0, 0, 0, 0);
    
    const yesterdayEnd = new Date(yesterday);
    yesterdayEnd.setHours(23, 59, 59, 999);
    
    const midnight = new Date();
    midnight.setHours(0, 0, 0, 0);
    
    // Find all students who signed in yesterday but never signed out
    const students = await this.prisma.student.findMany({
      include: {
        attendanceLogs: {
          where: {
            attendanceScannedAt: { gte: yesterday, lt: midnight }
          },
          orderBy: { attendanceScannedAt: 'desc' }
        }
      }
    });
    
    let autoSignedOut = 0;
    
    for (const student of students) {
      if (student.attendanceLogs.length > 0) {
        const lastLog = student.attendanceLogs[0];
        
        // If last log was IN (not signed out)
        if (lastLog.attendanceEventType === 'IN') {
          // Create auto sign-out at 23:59:59
          await this.prisma.attendanceLog.create({
            data: {
              studentId: student.studentId,
              attendanceScannedAt: yesterdayEnd,
              attendanceEventType: 'OUT',
              wasManualEntry: true,
              manualEntryReason: 'Auto-closed at midnight'
            }
          });
          
          autoSignedOut++;
          console.log(`[Auto Sign-Out] ${student.studentFirstName} ${student.studentLastName} at 23:59:59`);
        }
      }
    }
    
    console.log(`[Cron] Auto signed out ${autoSignedOut} students`);
  }
}
