<?php

namespace App\Services\Attendance;

use App\Models\Attendance;
use App\Models\AttendanceRegularization;
use App\Models\User;
use Carbon\Carbon;

class AttendanceCalendarDayResolver
{
    public const GRACE_MINUTES = 15;

    public const DEFAULT_SHIFT_IN = '09:00';

    public const DEFAULT_SHIFT_OUT = '18:00';

    /**
     * @return array{
     *     visual_status: string,
     *     display_status: string,
     *     regularization_status: ?string,
     *     icon: string,
     *     flags: array<string, bool>,
     *     tooltip: string
     * }
     */
    public function resolve(
        string $bulkStatus,
        string $date,
        ?Attendance $attendance,
        ?AttendanceRegularization $regularization,
        User $user,
        array $detail,
        ?Carbon $now = null
    ): array {
        $now = $now ?? now();
        $regStatus = $regularization?->status;

        if ($bulkStatus === 'future' || $bulkStatus === 'outside') {
            return $this->emptyCell();
        }

        if (in_array($bulkStatus, ['holiday', 'weekoff'], true)) {
            return $this->staticStatus($bulkStatus, $regStatus, $detail, $regularization);
        }

        if ($bulkStatus === 'leave' || ($attendance && ($attendance->is_leave || $attendance->status === 'leave'))) {
            return $this->staticStatus('leave', $regStatus, $detail, $regularization);
        }

        if ($bulkStatus === 'absent' && ! ($detail['has_data'] ?? false)) {
            return $this->staticStatus('absent', $regStatus, $detail, $regularization);
        }

        if ($bulkStatus === 'half_day' && ! ($attendance?->clock_in_date_time)) {
            return $this->staticStatus('half_day', $regStatus, $detail, $regularization);
        }

        $visualStatus = $this->resolvePunchVisualStatus($date, $attendance, $user, $now, $bulkStatus);

        return $this->buildResult($visualStatus, $regStatus, $detail, $regularization, $user, $date);
    }

    protected function resolvePunchVisualStatus(
        string $date,
        ?Attendance $attendance,
        User $user,
        Carbon $now,
        string $bulkStatus
    ): string {
        $clockIn = $attendance?->clock_in_date_time;
        $clockOut = $attendance?->clock_out_date_time;
        $day = Carbon::parse($date)->startOfDay();
        $isToday = $day->isSameDay($now);
        $isPast = $day->lt($now->copy()->startOfDay());

        [$shiftStart, $shiftEnd] = $this->shiftWindow($user, $date);

        if ($clockIn && ! $clockOut) {
            if ($isPast || ($isToday && $now->greaterThan($shiftEnd))) {
                return 'missing_punch_out';
            }

            return 'in_progress';
        }

        if ($clockIn && $clockOut) {
            if ($this->isLateArrival($clockIn, $shiftStart, $attendance)) {
                return 'late';
            }

            if ($this->isEarlyDeparture($clockOut, $shiftEnd)) {
                return 'early_out';
            }

            if ($bulkStatus === 'half_day') {
                return 'half_day';
            }

            return 'present';
        }

        if ($bulkStatus === 'half_day') {
            return 'half_day';
        }

        return 'absent';
    }

    public function isLateArrivalForUser(User $user, Carbon $clockIn): bool
    {
        $user->loadMissing('shift');
        [$shiftStart] = $this->shiftWindow($user, $clockIn->toDateString());

        return $this->isLateArrival($clockIn, $shiftStart, null);
    }

    protected function isLateArrival(Carbon $clockIn, Carbon $shiftStart, ?Attendance $attendance): bool
    {
        if ($attendance?->is_late) {
            return true;
        }

        $graceEnd = $shiftStart->copy()->addMinutes(self::GRACE_MINUTES);

        return $clockIn->greaterThan($graceEnd);
    }

    protected function isEarlyDeparture(Carbon $clockOut, Carbon $shiftEnd): bool
    {
        $graceStart = $shiftEnd->copy()->subMinutes(self::GRACE_MINUTES);

        return $clockOut->lessThan($graceStart);
    }

    /**
     * @return array{0: Carbon, 1: Carbon}
     */
    protected function shiftWindow(User $user, string $date): array
    {
        $user->loadMissing('shift');
        $shift = $user->shift;

        $inTime = $this->formatShiftTime($shift?->clock_in_time, self::DEFAULT_SHIFT_IN);
        $outTime = $this->formatShiftTime($shift?->clock_out_time, self::DEFAULT_SHIFT_OUT);

        $start = Carbon::parse($date . ' ' . $inTime);
        $end = Carbon::parse($date . ' ' . $outTime);

        if ($end->lessThanOrEqualTo($start)) {
            $end->addDay();
        }

        return [$start, $end];
    }

    protected function formatShiftTime(mixed $value, string $fallback): string
    {
        if ($value instanceof Carbon) {
            return $value->format('H:i');
        }

        if (is_string($value) && $value !== '') {
            return strlen($value) >= 5 ? substr($value, 0, 5) : $fallback;
        }

        return $fallback;
    }

    /**
     * @param  array<string, mixed>  $detail
     * @return array{
     *     visual_status: string,
     *     display_status: string,
     *     regularization_status: ?string,
     *     icon: string,
     *     flags: array<string, bool>,
     *     tooltip: string
     * }
     */
    protected function staticStatus(
        string $visualStatus,
        ?string $regStatus,
        array $detail,
        ?AttendanceRegularization $regularization
    ): array {
        return $this->buildResult($visualStatus, $regStatus, $detail, $regularization, null, null);
    }

    /**
     * @param  array<string, mixed>  $detail
     * @return array{
     *     visual_status: string,
     *     display_status: string,
     *     regularization_status: ?string,
     *     icon: string,
     *     flags: array<string, bool>,
     *     tooltip: string
     * }
     */
    protected function buildResult(
        string $visualStatus,
        ?string $regStatus,
        array $detail,
        ?AttendanceRegularization $regularization,
        ?User $user,
        ?string $date
    ): array {
        $displayStatus = $this->displayLabel($visualStatus);
        $icon = $this->iconFor($visualStatus);

        $flags = [
            'has_clock_in' => ! empty($detail['clock_in']),
            'has_clock_out' => ! empty($detail['clock_out']),
            'missing_punch_out' => $visualStatus === 'missing_punch_out',
            'in_progress' => $visualStatus === 'in_progress',
            'has_regularization' => $regStatus !== null,
        ];

        $tooltip = $this->buildTooltip($displayStatus, $detail, $regStatus, $user, $date);

        return [
            'visual_status' => $visualStatus,
            'display_status' => $displayStatus,
            'regularization_status' => $regStatus,
            'icon' => $icon,
            'flags' => $flags,
            'tooltip' => $tooltip,
        ];
    }

    protected function displayLabel(string $visualStatus): string
    {
        return match ($visualStatus) {
            'present' => 'Present',
            'late' => 'Late',
            'missing_punch_out' => 'Missing Punch Out',
            'in_progress' => 'In Progress',
            'early_out' => 'Early Out',
            'half_day' => 'Half Day',
            'leave' => 'On Leave',
            'holiday' => 'Holiday',
            'weekoff' => 'Week Off',
            'absent' => 'Absent',
            default => '',
        };
    }

    protected function iconFor(string $visualStatus): string
    {
        return match ($visualStatus) {
            'present' => 'check-circle-fill',
            'late' => 'check-circle',
            'missing_punch_out' => 'clock-fill',
            'in_progress' => 'clock-history',
            'early_out' => 'box-arrow-right',
            'half_day' => 'circle-half',
            'leave' => 'calendar-x',
            'holiday' => 'calendar-event',
            'weekoff' => 'moon',
            'absent' => 'x-circle-fill',
            default => '',
        };
    }

    /**
     * @param  array<string, mixed>  $detail
     */
    protected function buildTooltip(
        string $displayStatus,
        array $detail,
        ?string $regStatus,
        ?User $user,
        ?string $date
    ): string {
        if ($displayStatus === '') {
            return '';
        }

        $parts = [$displayStatus];

        if (! empty($detail['clock_in_label'])) {
            $parts[] = 'In: ' . $detail['clock_in_label'];
        }
        if (! empty($detail['clock_out_label'])) {
            $parts[] = 'Out: ' . $detail['clock_out_label'];
        } elseif (($detail['clock_in'] ?? null) && ! ($detail['clock_out'] ?? null)) {
            $parts[] = 'Out: missing';
        }

        if ($user && $date) {
            [$shiftStart, $shiftEnd] = $this->shiftWindow($user, $date);
            $parts[] = 'Shift: ' . $shiftStart->format('h:i A') . '–' . $shiftEnd->format('h:i A');
        }

        if ($regStatus) {
            $parts[] = 'Regularization: ' . ucfirst($regStatus);
        }

        return implode(' · ', $parts);
    }

  /**
     * @return array{
     *     visual_status: string,
     *     display_status: string,
     *     regularization_status: ?string,
     *     icon: string,
     *     flags: array<string, bool>,
     *     tooltip: string
     * }
     */
    protected function emptyCell(): array
    {
        return [
            'visual_status' => '',
            'display_status' => '',
            'regularization_status' => null,
            'icon' => '',
            'flags' => [],
            'tooltip' => '',
        ];
    }
}
