<?php

namespace App\Http\Controllers\Web;

use App\Http\Controllers\Controller;
use App\Models\Attendance;
use App\Models\Department;
use App\Models\Holiday;
use App\Models\Leave;
use App\Models\User;
use App\Models\WeekoffRule;
use App\Services\Attendance\WeekoffResolver;
use Carbon\Carbon;
use Illuminate\Http\Request;
use Illuminate\Support\Facades\Auth;
use Symfony\Component\HttpFoundation\StreamedResponse;

class AttendanceReportController extends Controller
{
    public function __construct(
        protected WeekoffResolver $weekoffResolver
    ) {}

    public function index(Request $request)
    {
        $authUser = Auth::user();
        $companyId = $authUser?->company_id;
        if (! $companyId) {
            return view('attendance.reports', [
                'from' => null,
                'to' => null,
                'employees' => [],
                'allEmployees' => [],
                'rows' => [],
                'summary' => ['total_present' => 0, 'total_absent' => 0, 'total_late' => 0, 'total_on_leave' => 0, 'total_hours' => 0],
                'departments' => [],
            ]);
        }
        $visibleUserIds = $authUser->visibleStaffUserIds();

        $from = $request->filled('from') ? Carbon::parse($request->from)->startOfDay() : Carbon::now()->startOfMonth();
        $to = $request->filled('to') ? Carbon::parse($request->to)->endOfDay() : Carbon::now()->endOfMonth();
        if ($from->gt($to)) {
            $to = $from->copy()->endOfDay();
        }

        $departments = Department::where('company_id', $companyId)->orderBy('name')->get();

        $query = User::with('department')
            ->where('company_id', $companyId)
            ->forActiveDirectory()
            ->whereIn('id', $visibleUserIds)
            ->orderBy('name');
        if ($request->filled('employee_id')) {
            $query->where('id', $request->employee_id);
        }
        if ($request->filled('department_id')) {
            $query->where('department_id', $request->department_id);
        }
        $employees = $query->get();

        $allEmployees = User::with('department')
            ->where('company_id', $companyId)
            ->forActiveDirectory()
            ->whereIn('id', $visibleUserIds)
            ->orderBy('name')
            ->get();

        $holidayDates = Holiday::withoutGlobalScopes()
            ->where('company_id', $companyId)
            ->whereBetween('date', [$from->toDateString(), $to->toDateString()])
            ->pluck('date')
            ->map(fn ($d) => $d instanceof \DateTimeInterface ? $d->format('Y-m-d') : $d)
            ->all();

        $rows = [];
        $totalPresent = 0;
        $totalAbsent = 0;
        $totalLate = 0;
        $totalOnLeave = 0;
        $totalSeconds = 0;

        foreach ($employees as $user) {
            $workingDays = $this->workingDaysInPeriod($companyId, (int) $user->id, $from, $to, $holidayDates);
            $presentDays = $this->presentDaysForUser($user->id, $companyId, $from, $to);
            $leaveDays = $this->leaveDaysInPeriod($user->id, $companyId, $from, $to);
            $lateDays = $this->lateDaysForUser($user->id, $companyId, $from, $to);
            $seconds = $this->totalWorkedSecondsForUser($user->id, $companyId, $from, $to);
            $absentDays = max(0, $workingDays - $presentDays - $leaveDays);

            $rows[] = [
                'user' => $user,
                'present_days' => $presentDays,
                'absent_days' => $absentDays,
                'late_days' => $lateDays,
                'on_leave_days' => $leaveDays,
                'working_days' => $workingDays,
                'total_hours' => round($seconds / 3600, 2),
            ];
            $totalPresent += $presentDays;
            $totalAbsent += $absentDays;
            $totalLate += $lateDays;
            $totalOnLeave += $leaveDays;
            $totalSeconds += $seconds;
        }

        $summary = [
            'total_present' => round($totalPresent, 2),
            'total_absent' => round($totalAbsent, 2),
            'total_late' => round($totalLate, 2),
            'total_on_leave' => round($totalOnLeave, 2),
            'total_hours' => round($totalSeconds / 3600, 2),
        ];

        if ($request->get('export') === '1') {
            return $this->exportCsv($rows, $from, $to);
        }

        return view('attendance.reports', compact('from', 'to', 'employees', 'allEmployees', 'rows', 'summary', 'departments'));
    }

    protected function workingDaysInPeriod(int $companyId, int $userId, Carbon $start, Carbon $end, array $holidayDates): int
    {
        $weekoffMap = $this->weekoffResolver->dayTypeMap($companyId, $userId, $start, $end);
        $days = 0;
        $cursor = $start->copy();
        while ($cursor->lte($end)) {
            $date = $cursor->format('Y-m-d');
            $isWeekoff = ($weekoffMap[$date] ?? null) === WeekoffRule::DAY_WEEKLY_OFF;
            if (! $isWeekoff && ! in_array($date, $holidayDates, true)) {
                $days++;
            }
            $cursor->addDay();
        }

        return $days;
    }

    protected function presentDaysForUser(int $userId, int $companyId, Carbon $start, Carbon $end): float
    {
        $count = Attendance::withoutGlobalScopes()
            ->where('company_id', $companyId)
            ->where('user_id', $userId)
            ->whereBetween('date', [$start->toDateString(), $end->toDateString()])
            ->whereNotNull('clock_in_date_time')
            ->count();
        $halfDays = Attendance::withoutGlobalScopes()
            ->where('company_id', $companyId)
            ->where('user_id', $userId)
            ->whereBetween('date', [$start->toDateString(), $end->toDateString()])
            ->whereNotNull('clock_in_date_time')
            ->where('is_half_day', true)
            ->count();
        return $count - ($halfDays * 0.5);
    }

    protected function leaveDaysInPeriod(int $userId, int $companyId, Carbon $start, Carbon $end): float
    {
        $leaves = Leave::withoutGlobalScopes()
            ->where('company_id', $companyId)
            ->where('user_id', $userId)
            ->where('status', 'approved')
            ->where(function ($q) use ($start, $end) {
                $q->whereBetween('start_date', [$start->toDateString(), $end->toDateString()])
                    ->orWhereBetween('end_date', [$start->toDateString(), $end->toDateString()])
                    ->orWhere(fn ($q2) => $q2->where('start_date', '<=', $start)->where('end_date', '>=', $end));
            })
            ->get();
        $days = 0.0;
        foreach ($leaves as $leave) {
            $leaveEnd = $leave->end_date ? $leave->end_date->copy() : $leave->start_date->copy();
            $overlapStart = $start->copy()->gt($leave->start_date->copy()) ? $start->copy() : $leave->start_date->copy();
            $overlapEnd = $end->copy()->lt($leaveEnd) ? $end->copy() : $leaveEnd;
            if ($overlapStart->lte($overlapEnd)) {
                $segmentDays = $overlapStart->diffInDays($overlapEnd) + 1;
                $days += $leave->is_half_day ? 0.5 : min((float) ($leave->total_days ?? $segmentDays), $segmentDays);
            }
        }
        return round($days, 2);
    }

    protected function lateDaysForUser(int $userId, int $companyId, Carbon $start, Carbon $end): int
    {
        return Attendance::withoutGlobalScopes()
            ->where('company_id', $companyId)
            ->where('user_id', $userId)
            ->whereBetween('date', [$start->toDateString(), $end->toDateString()])
            ->where('is_late', true)
            ->count();
    }

    protected function totalWorkedSecondsForUser(int $userId, int $companyId, Carbon $start, Carbon $end): int
    {
        $attendances = Attendance::withoutGlobalScopes()
            ->where('company_id', $companyId)
            ->where('user_id', $userId)
            ->whereBetween('date', [$start->toDateString(), $end->toDateString()])
            ->whereNotNull('clock_in_date_time')
            ->get();
        $total = 0;
        foreach ($attendances as $a) {
            if ($a->total_duration) {
                $total += (int) $a->total_duration;
            } elseif ($a->clock_in_date_time && $a->clock_out_date_time) {
                $total += $a->clock_in_date_time->diffInSeconds($a->clock_out_date_time);
            }
        }
        return $total;
    }

    protected function exportCsv(array $rows, Carbon $from, Carbon $to): StreamedResponse
    {
        $filename = 'attendance-report-' . $from->format('Y-m-d') . '-to-' . $to->format('Y-m-d') . '.csv';
        return response()->streamDownload(function () use ($rows) {
            $out = fopen('php://output', 'w');
            fputcsv($out, ['Employee', 'Department', 'Present Days', 'Absent Days', 'Late Days', 'On Leave Days', 'Working Days', 'Total Hours']);
            foreach ($rows as $row) {
                fputcsv($out, [
                    $row['user']->name ?? '—',
                    $row['user']->department?->name ?? '—',
                    $row['present_days'],
                    $row['absent_days'],
                    $row['late_days'],
                    $row['on_leave_days'],
                    $row['working_days'],
                    $row['total_hours'],
                ]);
            }
            fclose($out);
        }, $filename, ['Content-Type' => 'text/csv']);
    }
}
