<?php

namespace App\Modules\HR\Attendance\Services;

use App\Modules\HR\Attendance\Models\AttendanceImport;
use App\Modules\HR\Attendance\Repositories\AttendanceImportRepository;
use App\Modules\HR\Attendance\Repositories\AttendanceRecordRepository;
use App\Modules\HR\Attendance\Support\AttendanceRecordStatus;
use App\Modules\HR\Attendance\Support\AttendanceSource;
use App\Modules\Payroll\Services\PayrollProtectionService;
use Carbon\CarbonImmutable;
use Illuminate\Http\UploadedFile;
use Illuminate\Support\Facades\DB;
use Illuminate\Validation\ValidationException;
use Throwable;

class AttendanceImportService
{
    public function __construct(
        private readonly AttendanceImportRepository $imports,
        private readonly AttendanceRecordRepository $records,
        private readonly PayrollProtectionService $payroll,
        private readonly AttendanceRecordActivityLogger $activity,
    ) {}

    public function preview(int $tenantId, int $userId, UploadedFile $file): AttendanceImport
    {
        $import = $this->imports->create([
            'tenant_id' => $tenantId,
            'uploaded_by' => $userId,
            'original_name' => $file->getClientOriginalName(),
        ]);
        $handle = fopen($file->getRealPath(), 'rb');
        $headers = array_map(fn ($value) => strtolower(trim((string) $value)), fgetcsv($handle) ?: []);
        $required = ['employee_code', 'date', 'check_in', 'check_out'];
        if (array_diff($required, $headers) !== []) {
            throw ValidationException::withMessages(['file' => [__('attendance.import.invalid_headers')]]);
        }
        $seen = [];
        $stats = ['total_rows' => 0, 'valid_rows' => 0, 'error_rows' => 0];
        while (($values = fgetcsv($handle)) !== false) {
            $stats['total_rows']++;
            $values = array_slice(array_pad($values, count($headers), null), 0, count($headers));
            $row = array_combine($headers, $values);
            $payload = $this->validateRow($tenantId, $row, $seen);
            $this->imports->addRow($import, ['row_number' => $stats['total_rows'] + 1] + $payload);
            $stats[$payload['is_valid'] ? 'valid_rows' : 'error_rows']++;
        }
        fclose($handle);

        return $this->imports->update($import, $stats);
    }

    public function commit(AttendanceImport $import, int $userId): AttendanceImport
    {
        if ($import->status !== 'preview' || $import->error_rows > 0) {
            throw ValidationException::withMessages(['import' => [__('attendance.import.not_committable')]]);
        }

        DB::transaction(function () use ($import, $userId): void {
            foreach ($this->imports->validRows($import) as $row) {
                $employee = $this->imports->employeeByCode((int) $import->tenant_id, $row->employee_code);
                if ($employee === null) {
                    throw ValidationException::withMessages(['import' => [__('attendance.import.unknown_employee')]]);
                }
                $date = $row->attendance_date->toDateString();
                $this->payroll->ensureDateWritable((int) $import->tenant_id, $date, $userId);
                if ($this->records->findForEmployeeDateInTenant((int) $import->tenant_id, (int) $employee->id, $date)) {
                    throw ValidationException::withMessages(['import' => [__('attendance.import.duplicate_on_commit')]]);
                }
                $record = $this->records->updateOrCreateDay((int) $import->tenant_id, (int) $employee->id, $date, [
                    'check_in_at' => $date.' '.$row->check_in,
                    'check_out_at' => $row->check_out ? $date.' '.$row->check_out : null,
                    'overtime_minutes' => 0,
                    'missing_checkout' => ! $row->check_out,
                    'source' => AttendanceSource::IMPORT,
                    'status' => AttendanceRecordStatus::PRESENT,
                    'created_by' => $userId,
                    'updated_by' => $userId,
                ]);
                $this->activity->log($record, 'attendance_import_commit', $userId, "Import #{$import->id}");
            }
            $this->imports->update($import, ['status' => 'committed', 'committed_at' => now()]);
        });

        return $import->fresh() ?? $import;
    }

    private function validateRow(int $tenantId, array $row, array &$seen): array
    {
        $code = trim((string) ($row['employee_code'] ?? ''));
        $date = $this->date((string) ($row['date'] ?? ''));
        $errors = [];
        $employee = $code === '' ? null : $this->imports->employeeByCode($tenantId, $code);
        if ($employee === null) {
            $errors[] = __('attendance.import.unknown_employee');
        }
        if ($date === null) {
            $errors[] = __('attendance.import.invalid_date');
        }
        $checkIn = $this->time($row['check_in'] ?? null);
        $checkOut = $this->time($row['check_out'] ?? null);
        if ($checkIn === null || (($row['check_out'] ?? '') !== '' && $checkOut === null)) {
            $errors[] = __('attendance.import.invalid_time');
        }
        $key = $code.'|'.$date;
        if (isset($seen[$key]) || ($employee && $date && $this->records->findForEmployeeDateInTenant($tenantId, (int) $employee->id, $date))) {
            $errors[] = __('attendance.import.duplicate');
        }
        $seen[$key] = true;

        return [
            'employee_code' => $code,
            'attendance_date' => $date,
            'check_in' => $checkIn,
            'check_out' => $checkOut,
            'errors' => $errors ?: null,
            'is_valid' => $errors === [],
        ];
    }

    private function date(string $value): ?string
    {
        try {
            return CarbonImmutable::parse($value)->toDateString();
        } catch (Throwable) {
            return null;
        }
    }

    private function time(mixed $value): ?string
    {
        $value = trim((string) $value);
        if ($value === '') {
            return null;
        }
        foreach (['H:i', 'H:i:s'] as $format) {
            try {
                return CarbonImmutable::createFromFormat($format, $value)->format('H:i:s');
            } catch (Throwable) {
                continue;
            }
        }

        return null;
    }
}
