<?php
declare(strict_types=1);

namespace App\Modules\HR\Services;

use App\Core\App;
use App\Core\Logger;
use App\Modules\HR\Models\HrEmployeeProfile;

/**
 * Biometric Service
 *
 * Handles:
 *   - Processing biometric punches from devices
 *   - Matching fingerprint codes to employees
 *   - Determining punch type (in/out) based on attendance state
 *   - Bulk processing of pending punches
 */
final class BiometricService
{
    /**
     * Process a single punch received from a biometric device.
     *
     * @param array $data {device_serial, fingerprint_code, punch_time, punch_type}
     * @return array {success, punch_id?, error?}
     */
    public static function processPunch(array $data): array
    {
        $db = App::getInstance()->db();

        $deviceSerial = trim((string) ($data['device_serial'] ?? ''));
        $fingerprintCode = trim((string) ($data['fingerprint_code'] ?? ''));
        $punchTime = trim((string) ($data['punch_time'] ?? ''));
        $punchType = trim((string) ($data['punch_type'] ?? 'unknown'));

        if ($deviceSerial === '' || $fingerprintCode === '' || $punchTime === '') {
            return ['success' => false, 'error' => 'Missing required fields'];
        }

        // Validate device exists and is active
        $device = $db->selectOne(
            "SELECT id FROM hr_biometric_devices WHERE serial_number = ? AND is_active = 1",
            [$deviceSerial]
        );

        if (!$device) {
            return ['success' => false, 'error' => 'Device not found or inactive'];
        }

        $deviceId = (int) $device['id'];

        // Normalize punch_type
        if (!in_array($punchType, ['in', 'out', 'unknown'], true)) {
            $punchType = 'unknown';
        }

        // Insert punch record
        $db->insert('hr_biometric_punches', [
            'device_id'        => $deviceId,
            'fingerprint_code' => $fingerprintCode,
            'punch_time'       => $punchTime,
            'punch_type'       => $punchType,
            'processed'        => 0,
            'created_at'       => date('Y-m-d H:i:s'),
        ]);

        $punchId = (int) $db->lastInsertId();

        // Update device last_sync_at
        $db->update('hr_biometric_devices', [
            'last_sync_at' => date('Y-m-d H:i:s'),
        ], '`id` = ?', [$deviceId]);

        // Attempt to match employee and record attendance
        $profile = self::resolveEmployee($fingerprintCode);

        if ($profile) {
            $date = substr($punchTime, 0, 10);
            $time = substr($punchTime, 11, 8);

            // Determine type if unknown
            if ($punchType === 'unknown') {
                $punchType = self::determinePunchType((int) $profile['id'], $date);
            }

            try {
                if ($punchType === 'in') {
                    $result = AttendanceService::recordCheckIn(
                        (int) $profile['id'],
                        $date,
                        $time,
                        'biometric'
                    );
                } elseif ($punchType === 'out') {
                    $result = AttendanceService::recordCheckOut(
                        (int) $profile['id'],
                        $date,
                        $time,
                        'biometric'
                    );
                } else {
                    $result = ['success' => false, 'error' => 'Cannot determine punch type'];
                }

                if ($result['success']) {
                    // Get the attendance record id
                    $attendance = $db->selectOne(
                        "SELECT id FROM hr_attendance WHERE employee_profile_id = ? AND attendance_date = ? AND is_archived = 0",
                        [(int) $profile['id'], $date]
                    );

                    $db->update('hr_biometric_punches', [
                        'processed'     => 1,
                        'attendance_id' => $attendance ? (int) $attendance['id'] : null,
                        'punch_type'    => $punchType,
                    ], '`id` = ?', [$punchId]);
                } else {
                    $db->update('hr_biometric_punches', [
                        'processed'     => 1,
                        'error_message' => $result['error'] ?? 'Attendance recording failed',
                        'punch_type'    => $punchType,
                    ], '`id` = ?', [$punchId]);
                }
            } catch (\Throwable $e) {
                Logger::error('BiometricService::processPunch error', [
                    'punch_id' => $punchId,
                    'error'    => $e->getMessage(),
                ]);
                $db->update('hr_biometric_punches', [
                    'processed'     => 1,
                    'error_message' => $e->getMessage(),
                ], '`id` = ?', [$punchId]);
            }
        } else {
            // Employee not matched — leave as unprocessed for manual review
            $db->update('hr_biometric_punches', [
                'error_message' => 'Employee not found for fingerprint code: ' . $fingerprintCode,
            ], '`id` = ?', [$punchId]);
        }

        return ['success' => true, 'punch_id' => $punchId];
    }

    /**
     * Process all pending (unprocessed) punches.
     *
     * @return array {processed: int, failed: int, errors: array}
     */
    public static function processAllPending(): array
    {
        $db = App::getInstance()->db();

        $pending = $db->select(
            "SELECT p.*, d.serial_number as device_serial
             FROM hr_biometric_punches p
             JOIN hr_biometric_devices d ON d.id = p.device_id
             WHERE p.processed = 0
             ORDER BY p.punch_time ASC"
        );

        $processed = 0;
        $failed = 0;
        $errors = [];

        foreach ($pending as $punch) {
            $profile = self::resolveEmployee($punch['fingerprint_code']);

            if (!$profile) {
                $db->update('hr_biometric_punches', [
                    'processed'     => 1,
                    'error_message' => 'Employee not found for fingerprint code: ' . $punch['fingerprint_code'],
                ], '`id` = ?', [(int) $punch['id']]);
                $failed++;
                $errors[] = "Punch #{$punch['id']}: Employee not found for code {$punch['fingerprint_code']}";
                continue;
            }

            $date = substr($punch['punch_time'], 0, 10);
            $time = substr($punch['punch_time'], 11, 8);
            $punchType = $punch['punch_type'];

            if ($punchType === 'unknown') {
                $punchType = self::determinePunchType((int) $profile['id'], $date);
            }

            try {
                if ($punchType === 'in') {
                    $result = AttendanceService::recordCheckIn(
                        (int) $profile['id'],
                        $date,
                        $time,
                        'biometric'
                    );
                } elseif ($punchType === 'out') {
                    $result = AttendanceService::recordCheckOut(
                        (int) $profile['id'],
                        $date,
                        $time,
                        'biometric'
                    );
                } else {
                    $result = ['success' => false, 'error' => 'Cannot determine punch type'];
                }

                if ($result['success']) {
                    $attendance = $db->selectOne(
                        "SELECT id FROM hr_attendance WHERE employee_profile_id = ? AND attendance_date = ? AND is_archived = 0",
                        [(int) $profile['id'], $date]
                    );

                    $db->update('hr_biometric_punches', [
                        'processed'     => 1,
                        'attendance_id' => $attendance ? (int) $attendance['id'] : null,
                        'punch_type'    => $punchType,
                    ], '`id` = ?', [(int) $punch['id']]);
                    $processed++;
                } else {
                    $db->update('hr_biometric_punches', [
                        'processed'     => 1,
                        'error_message' => $result['error'] ?? 'Attendance recording failed',
                        'punch_type'    => $punchType,
                    ], '`id` = ?', [(int) $punch['id']]);
                    $failed++;
                    $errors[] = "Punch #{$punch['id']}: " . ($result['error'] ?? 'Unknown error');
                }
            } catch (\Throwable $e) {
                Logger::error('BiometricService::processAllPending error', [
                    'punch_id' => $punch['id'],
                    'error'    => $e->getMessage(),
                ]);
                $db->update('hr_biometric_punches', [
                    'processed'     => 1,
                    'error_message' => $e->getMessage(),
                ], '`id` = ?', [(int) $punch['id']]);
                $failed++;
                $errors[] = "Punch #{$punch['id']}: " . $e->getMessage();
            }
        }

        return [
            'processed' => $processed,
            'failed'    => $failed,
            'errors'    => $errors,
        ];
    }

    /**
     * Resolve an employee profile from a fingerprint code.
     *
     * @return array|null Profile row or null if not found
     */
    public static function resolveEmployee(string $fingerprintCode): ?array
    {
        $db = App::getInstance()->db();

        $profile = $db->selectOne(
            "SELECT id, employee_id, employee_number, first_name_ar, last_name_ar
             FROM hr_employee_profiles
             WHERE fingerprint_code = ? AND employment_status = 'active' AND is_archived = 0",
            [$fingerprintCode]
        );

        return $profile ?: null;
    }

    /**
     * Determine punch type based on current attendance state for the day.
     *
     * Logic:
     *   - No check_in today => 'in'
     *   - check_in exists, no check_out => 'out'
     *   - Both exist => 'unknown'
     */
    public static function determinePunchType(int $profileId, string $date): string
    {
        $db = App::getInstance()->db();

        $attendance = $db->selectOne(
            "SELECT check_in_time, check_out_time
             FROM hr_attendance
             WHERE employee_profile_id = ? AND attendance_date = ? AND is_archived = 0",
            [$profileId, $date]
        );

        if (!$attendance) {
            return 'in';
        }

        if ($attendance['check_in_time'] !== null && $attendance['check_out_time'] === null) {
            return 'out';
        }

        return 'unknown';
    }
}
