import { AttendanceService } from './attendance.service';
import { HttpStatus } from '@nestjs/common';

describe('AttendanceService QR Code Attendance Scanning', () => {
  let service: AttendanceService;
  let mockDb: any;
  let mockPermissionResolver: any;
  let mockOrgTimezone: any;
  let mockIpWhitelistService: any;
  let mockDevicesService: any;

  beforeEach(() => {
    mockDb = {
      qr_attendance_points: {
        findFirst: jest.fn(),
        findMany: jest.fn(),
        create: jest.fn(),
        update: jest.fn(),
        delete: jest.fn(),
      },
      work_day_schedules: {
        findFirst: jest.fn(),
      },
      attendances: {
        findFirst: jest.fn(),
        create: jest.fn(),
        update: jest.fn(),
      },
    };

    mockPermissionResolver = {
      resolveForUser: jest.fn(),
    };

    mockOrgTimezone = {
      getTimezone: jest.fn().mockResolvedValue('Asia/Dhaka'),
    };

    mockIpWhitelistService = {
      isIpAllowed: jest.fn().mockResolvedValue(true),
    };

    mockDevicesService = {
      validateOrBindDevice: jest.fn().mockResolvedValue({ valid: true }),
    };

    service = new AttendanceService(
      mockDb,
      mockPermissionResolver,
      mockOrgTimezone,
      mockIpWhitelistService,
      mockDevicesService,
    );
  });

  it('should block scan if network/IP is not authorized', async () => {
    mockIpWhitelistService.isIpAllowed.mockResolvedValue(false);

    await expect(
      service.scanQr(
        'teacher-1',
        'owner-1',
        { qr_code: 'QR-ATT-12345', device_id: 'dev-1' },
        '203.0.113.1',
      ),
    ).rejects.toThrow('Network Restricted');
  });

  it('should block scan if device verification fails', async () => {
    mockDevicesService.validateOrBindDevice.mockResolvedValue({
      valid: false,
      message: 'Device mismatch: Please submit a change request.',
    });

    await expect(
      service.scanQr(
        'teacher-1',
        'owner-1',
        { qr_code: 'QR-ATT-12345', device_id: 'dev-unknown' },
        '192.168.1.5',
      ),
    ).rejects.toThrow('Device mismatch');
  });

  it('should block scan if QR code is invalid or inactive', async () => {
    mockDb.qr_attendance_points.findFirst.mockResolvedValue(null);

    await expect(
      service.scanQr(
        'teacher-1',
        'owner-1',
        { qr_code: 'INVALID-QR', device_id: 'dev-1' },
        '192.168.1.5',
      ),
    ).rejects.toThrow('Invalid or inactive QR code');
  });

  it('should automatically perform CHECK-IN on first scan of the day', async () => {
    mockDb.qr_attendance_points.findFirst.mockResolvedValue({
      id: 'qr-point-1',
      owner_id: 'owner-1',
      code: 'QR-ATT-VALID',
      is_active: true,
    });

    mockDb.work_day_schedules.findFirst.mockResolvedValue({
      is_active: true,
      start_time: '09:00',
      end_time: '23:59', // day not over
      grace_period_minutes: 15,
    });

    mockDb.attendances.findFirst.mockResolvedValue(null); // No attendance yet today
    mockDb.attendances.create.mockImplementation(({ data }: any) => ({
      id: 'att-1',
      ...data,
      created_at: new Date(),
      updated_at: new Date(),
    }));

    const result = await service.scanQr(
      'teacher-1',
      'owner-1',
      { qr_code: 'QR-ATT-VALID', device_id: 'dev-1' },
      '192.168.1.5',
    );

    expect(result.status).toBe(HttpStatus.OK);
    expect(result.data?.action).toBe('CHECK_IN');
    expect(mockDb.attendances.create).toHaveBeenCalledWith(
      expect.objectContaining({
        data: expect.objectContaining({
          member_id: 'teacher-1',
          attendance_method: 'qr',
        }),
      }),
    );
  });

  it('should automatically perform CHECK-OUT on second scan of the day', async () => {
    mockDb.qr_attendance_points.findFirst.mockResolvedValue({
      id: 'qr-point-1',
      owner_id: 'owner-1',
      code: 'QR-ATT-VALID',
      is_active: true,
    });

    mockDb.work_day_schedules.findFirst.mockResolvedValue({
      is_active: true,
      start_time: '09:00',
      end_time: '17:00',
      break_start: '13:00',
      break_end: '14:00',
    });

    const checkInTime = new Date(Date.now() - 4 * 60 * 60 * 1000); // 4 hours ago
    mockDb.attendances.findFirst.mockResolvedValue({
      id: 'att-1',
      member_id: 'teacher-1',
      owner_id: 'owner-1',
      check_in: checkInTime,
      check_out: null,
      status: 'present',
    });

    mockDb.attendances.update.mockImplementation(({ data }: any) => ({
      id: 'att-1',
      member_id: 'teacher-1',
      owner_id: 'owner-1',
      check_in: checkInTime,
      ...data,
      created_at: new Date(),
      updated_at: new Date(),
    }));

    const result = await service.scanQr(
      'teacher-1',
      'owner-1',
      { qr_code: 'QR-ATT-VALID', device_id: 'dev-1' },
      '192.168.1.5',
    );

    expect(result.status).toBe(HttpStatus.OK);
    expect(result.data?.action).toBe('CHECK_OUT');
    expect(mockDb.attendances.update).toHaveBeenCalledWith(
      expect.objectContaining({
        data: expect.objectContaining({
          check_out: expect.any(Date),
          attendance_method: 'qr',
        }),
      }),
    );
  });

  it('should return ALREADY_COMPLETED if teacher already checked in and checked out', async () => {
    mockDb.qr_attendance_points.findFirst.mockResolvedValue({
      id: 'qr-point-1',
      owner_id: 'owner-1',
      code: 'QR-ATT-VALID',
      is_active: true,
    });

    mockDb.work_day_schedules.findFirst.mockResolvedValue({
      is_active: true,
      start_time: '09:00',
      end_time: '17:00',
    });

    mockDb.attendances.findFirst.mockResolvedValue({
      id: 'att-1',
      member_id: 'teacher-1',
      owner_id: 'owner-1',
      check_in: new Date(Date.now() - 8 * 60 * 60 * 1000),
      check_out: new Date(Date.now() - 1000),
      work_hours: 8,
      net_hours: 7,
      status: 'present',
      created_at: new Date(),
      updated_at: new Date(),
    });

    const result = await service.scanQr(
      'teacher-1',
      'owner-1',
      { qr_code: 'QR-ATT-VALID', device_id: 'dev-1' },
      '192.168.1.5',
    );

    expect(result.status).toBe(HttpStatus.OK);
    expect(result.data?.action).toBe('ALREADY_COMPLETED');
    expect(result.message).toContain('already completed');
  });
});
