<?php

namespace App\Http\Controllers\Tenant;

use App\Http\Controllers\Controller;
use App\Http\Requests\Hr\ReviewAttendanceCorrectionRequest;
use App\Http\Requests\Hr\StoreAttendanceCorrectionRequest;
use App\Http\Requests\Hr\StoreAttendanceRequest;
use App\Models\AttendanceCorrection;
use App\Models\AttendanceRecord;
use App\Models\AttendanceSetting;
use App\Models\Employee;
use App\Models\Shift;
use App\Services\AttendanceService;
use Carbon\Carbon;
use Illuminate\Http\Request;
use Illuminate\Validation\ValidationException;

class AttendanceController extends Controller
{
    public function __construct(protected AttendanceService $attendanceService) {}

    public function index(Request $request)
    {
        $date = Carbon::parse($request->query('date', today()->toDateString()));
        $view = $request->query('view', 'daily');
        $query = AttendanceRecord::with(['employee.branch', 'employee.department', 'shift'])
            ->when($request->filled('employee_id'), fn ($q) => $q->where('employee_id', $request->integer('employee_id')))
            ->when($request->filled('status'), fn ($q) => $q->where('status', $request->query('status')));
        if (in_array($view, ['monthly', 'calendar'], true)) {
            $query->whereBetween('attendance_date', [$date->copy()->startOfMonth(), $date->copy()->endOfMonth()]);
        } elseif ($view === 'weekly') {
            $query->whereBetween('attendance_date', [$date->copy()->startOfWeek(), $date->copy()->endOfWeek()]);
        } else {
            $query->whereDate('attendance_date', $date);
        }
        $records = $query->orderByDesc('attendance_date')->orderBy('employee_id')->paginate(30)->withQueryString();
        $todayRecords = AttendanceRecord::whereDate('attendance_date', today())->get();
        $activeEmployees = Employee::where('status', 'active')->count();
        $stats = [
            'total' => $activeEmployees,
            'present' => $todayRecords->whereIn('status', ['present', 'late', 'early_leave', 'half_day', 'work_from_home', 'business_visit'])->count(),
            'absent' => $todayRecords->where('status', 'absent')->count(),
            'late' => $todayRecords->where('late_minutes', '>', 0)->count(),
            'on_leave' => $todayRecords->where('status', 'on_leave')->count(),
            'not_checked_in' => max(0, $activeEmployees - $todayRecords->count()),
            'overtime_minutes' => $todayRecords->sum('overtime_minutes'),
        ];
        $employees = Employee::where('status', 'active')->orderBy('full_name')->get();
        $shifts = Shift::where('is_active', true)->orderBy('name')->get();
        $corrections = AttendanceCorrection::with(['employee', 'attendanceRecord', 'requester'])->where('status', 'requested')->latest()->get();
        $calendarDays = collect();
        $calendarSummary = collect();
        if ($view === 'calendar') {
            $calendarStart = $date->copy()->startOfMonth();
            $calendarEnd = $date->copy()->endOfMonth();
            $calendarDays = collect(range(0, $calendarStart->diffInDays($calendarEnd)))
                ->map(fn (int $offset) => $calendarStart->copy()->addDays($offset));
            $calendarSummary = AttendanceRecord::whereBetween('attendance_date', [$calendarStart, $calendarEnd])
                ->when($request->filled('employee_id'), fn ($q) => $q->where('employee_id', $request->integer('employee_id')))
                ->when($request->filled('status'), fn ($q) => $q->where('status', $request->query('status')))
                ->get(['attendance_date', 'status', 'overtime_minutes'])
                ->groupBy(fn ($record) => $record->attendance_date->toDateString());
        }

        return view('tenant.hr.attendance.index', compact('records', 'stats', 'employees', 'shifts', 'corrections', 'date', 'view', 'calendarDays', 'calendarSummary'));
    }

    public function store(StoreAttendanceRequest $request)
    {
        $employee = Employee::findOrFail($request->integer('employee_id'));
        $this->attendanceService->record($employee, $request->validated(), $request->user());

        return back()->with('status', 'Attendance saved and work-time metrics calculated.');
    }

    public function punch(Request $request)
    {
        $validated = $request->validate(['action' => ['required', 'in:check_in,check_out']]);
        $punchSettings = AttendanceSetting::first()?->settings ?? [];
        abort_unless($punchSettings['allow_web_punch'] ?? true, 403, 'Web attendance punches are disabled by your administrator.');
        $employee = $request->user()->employee;
        abort_unless($employee && $employee->status === 'active', 403);

        $record = AttendanceRecord::where('employee_id', $employee->id)->whereDate('attendance_date', today())->first();
        if ($validated['action'] === 'check_in' && $record?->actual_check_in) {
            throw ValidationException::withMessages(['attendance' => 'You have already checked in today.']);
        }
        if ($validated['action'] === 'check_out' && ! $record?->actual_check_in) {
            throw ValidationException::withMessages(['attendance' => 'Check in before recording check-out.']);
        }
        if ($validated['action'] === 'check_out' && $record?->actual_check_out) {
            throw ValidationException::withMessages(['attendance' => 'You have already checked out today.']);
        }

        $this->attendanceService->record($employee, [
            'attendance_date' => today()->toDateString(),
            'actual_check_in' => $record?->actual_check_in ?? now(),
            'actual_check_out' => $validated['action'] === 'check_out' ? now() : null,
            'status' => $record?->status ?? 'present',
            'source' => 'web',
            'notes' => $record?->notes,
        ], $request->user());

        return back()->with('status', $validated['action'] === 'check_in' ? 'Web check-in recorded.' : 'Web check-out recorded.');
    }

    public function import(Request $request)
    {
        $request->validate(['attendance_file' => ['required', 'file', 'mimes:csv,txt', 'max:2048']]);
        $handle = fopen($request->file('attendance_file')->getRealPath(), 'rb');
        if ($handle === false) {
            throw ValidationException::withMessages(['attendance_file' => 'The attendance file could not be read.']);
        }

        $headers = fgetcsv($handle);
        $headers = collect($headers ?: [])->map(fn ($header) => str((string) $header)->trim()->lower()->replace([' ', '-'], '_')->toString())->all();
        if (! in_array('employee_code', $headers, true) || ! in_array('attendance_date', $headers, true)) {
            fclose($handle);
            throw ValidationException::withMessages(['attendance_file' => 'CSV headers must include employee_code and attendance_date.']);
        }

        $imported = 0;
        $errors = [];
        $line = 1;
        $statuses = ['present', 'absent', 'late', 'early_leave', 'half_day', 'on_leave', 'holiday', 'weekly_off', 'work_from_home', 'business_visit'];
        while (($values = fgetcsv($handle)) !== false && $line < 1001) {
            $line++;
            $values = array_pad($values, count($headers), null);
            $row = array_combine($headers, array_slice($values, 0, count($headers)));
            if (! collect($row)->filter(fn ($value) => filled($value))->count()) {
                continue;
            }

            try {
                $employee = Employee::where('employee_code', trim((string) ($row['employee_code'] ?? '')))->firstOrFail();
                $attendanceDate = Carbon::parse($row['attendance_date'])->toDateString();
                $status = str((string) ($row['status'] ?? 'present'))->lower()->replace(' ', '_')->toString();
                if (! in_array($status, $statuses, true)) {
                    throw new \RuntimeException('Invalid attendance status.');
                }

                $this->attendanceService->record($employee, [
                    'attendance_date' => $attendanceDate,
                    'actual_check_in' => $this->importDateTime($attendanceDate, $row['check_in'] ?? null),
                    'actual_check_out' => $this->importDateTime($attendanceDate, $row['check_out'] ?? null),
                    'status' => $status,
                    'source' => 'import',
                    'notes' => $row['notes'] ?? null,
                ], $request->user());
                $imported++;
            } catch (\Throwable $exception) {
                $errors[] = 'Line '.$line.': '.$exception->getMessage();
            }
        }
        fclose($handle);

        return back()->with('status', $imported.' attendance row(s) imported.')
            ->with('attendance_import_errors', array_slice($errors, 0, 20));
    }

    public function requestCorrection(StoreAttendanceCorrectionRequest $request, int $id)
    {
        $record = AttendanceRecord::findOrFail($id);
        if ($request->user()->employee_id && $request->user()->employee_id !== $record->employee_id && ! $request->user()->hasPermission('attendance.manage')) {
            abort(403);
        }
        $this->attendanceService->requestCorrection($record, $request->validated(), $request->user());

        return back()->with('status', 'Attendance correction submitted for review.');
    }

    public function reviewCorrection(ReviewAttendanceCorrectionRequest $request, int $id)
    {
        $correction = AttendanceCorrection::findOrFail($id);
        $this->attendanceService->reviewCorrection($correction, $request->string('decision')->toString(), $request->input('review_comment'), $request->user());

        return back()->with('status', 'Attendance correction '.$request->input('decision').'.');
    }

    private function importDateTime(string $date, mixed $value): ?string
    {
        if (blank($value)) {
            return null;
        }

        $value = trim((string) $value);

        return Carbon::parse(str_contains($value, ':') && ! str_contains($value, '-') ? $date.' '.$value : $value)->toDateTimeString();
    }
}
