<?php

namespace App\Services\Leave;

use App\Models\Leave;
use App\Models\LeaveTransaction;
use App\Models\LeaveType;
use App\Models\User;
use Carbon\Carbon;
use Carbon\CarbonPeriod;

class LeaveDashboardStatsService
{
    public function __construct(
        protected LeaveLedgerService $ledger
    ) {}

    /**
     * @return array{
     *   weekly: array{labels: list<string>, data: list<float>},
     *   by_type: array{labels: list<string>, data: list<float>},
     *   monthly: array{labels: list<string>, data: list<float>}
     * }
     */
    public function leaveStatsForUser(User $user, int $year): array
    {
        $leaves = Leave::query()
            ->with('leaveType:id,name')
            ->where('user_id', $user->id)
            ->when($user->company_id, fn ($q) => $q->where('company_id', $user->company_id))
            ->whereIn('status', Leave::approvedLikeStatuses())
            ->where(function ($q) use ($year) {
                $q->whereYear('start_date', $year)
                    ->orWhereYear('end_date', $year);
            })
            ->get(['id', 'leave_type_id', 'start_date', 'end_date', 'total_days', 'is_half_day']);

        $weekdayCounts = array_fill(0, 7, 0.0); // Mon=0 … Sun=6
        $monthCounts = array_fill(1, 12, 0.0);
        $byType = [];

        foreach ($leaves as $leave) {
            $typeName = $leave->leaveType?->name ?: 'Other';
            $byType[$typeName] = ($byType[$typeName] ?? 0) + (float) $leave->total_days;

            $start = Carbon::parse($leave->start_date)->startOfDay();
            $end = Carbon::parse($leave->end_date)->startOfDay();
            if ($end->lt($start)) {
                continue;
            }

            $period = CarbonPeriod::create($start, $end);
            $dayCount = max(1, $start->diffInDays($end) + 1);
            $perDay = $dayCount > 0 ? ((float) $leave->total_days / $dayCount) : (float) $leave->total_days;

            foreach ($period as $day) {
                if ((int) $day->year !== $year) {
                    continue;
                }
                $weekdayIndex = ((int) $day->dayOfWeekIso) - 1; // 1=Mon … 7=Sun
                $weekdayCounts[$weekdayIndex] = round($weekdayCounts[$weekdayIndex] + $perDay, 2);
                $month = (int) $day->month;
                $monthCounts[$month] = round($monthCounts[$month] + $perDay, 2);
            }
        }

        $typeLabels = array_keys($byType);
        $typeData = array_map(fn ($v) => round((float) $v, 2), array_values($byType));

        return [
            'weekly' => [
                'labels' => ['Mon', 'Tue', 'Wed', 'Thu', 'Fri', 'Sat', 'Sun'],
                'data' => array_values($weekdayCounts),
            ],
            'by_type' => [
                'labels' => $typeLabels,
                'data' => $typeData,
            ],
            'monthly' => [
                'labels' => ['Jan', 'Feb', 'Mar', 'Apr', 'May', 'Jun', 'Jul', 'Aug', 'Sep', 'Oct', 'Nov', 'Dec'],
                'data' => array_values($monthCounts),
            ],
        ];
    }

    /**
     * @param  array<int, array<string, mixed>>  $balances
     * @return array{
     *   last_month: ?string,
     *   warning: ?string,
     *   credited_months: list<string>,
     *   types: list<array<string, mixed>>
     * }
     */
    public function monthlyGeneration(User $user, int $year, array $balances, ?string $latestMonth, ?string $warning): array
    {
        $creditedMonths = LeaveTransaction::query()
            ->where('user_id', $user->id)
            ->when($user->company_id, fn ($q) => $q->where('company_id', $user->company_id))
            ->where('transaction_type', LeaveTransaction::TYPE_ACCRUAL)
            ->where('year', $year)
            ->whereNotNull('effective_date')
            ->get(['effective_date'])
            ->map(fn ($row) => Carbon::parse($row->effective_date)->format('Y-m'))
            ->unique()
            ->sort()
            ->values()
            ->all();

        $types = [];
        foreach ($balances as $row) {
            if (! empty($row['is_unlimited'])) {
                continue;
            }
            if (($row['accrual_mode'] ?? null) !== LeaveLedgerService::ACCRUAL_MONTHLY) {
                continue;
            }

            $entitlement = (float) ($row['entitlement'] ?? 0);
            $accrued = (float) ($row['accrued'] ?? 0);
            $earned = (float) ($row['earned_to_date'] ?? $accrued);

            $types[] = [
                'leave_type_id' => $row['leave_type_id'],
                'name' => $row['name'],
                'accrued' => $accrued,
                'earned_to_date' => $earned,
                'entitlement' => $entitlement,
                'progress_pct' => $entitlement > 0
                    ? min(100, round(($accrued / $entitlement) * 100, 1))
                    : 0,
            ];
        }

        return [
            'last_month' => $latestMonth,
            'warning' => $warning,
            'credited_months' => $creditedMonths,
            'types' => $types,
        ];
    }

    /**
     * @param  array<int, array<string, mixed>>  $balances
     * @return list<array<string, mixed>>
     */
    public function ruleSummaries(User $user, array $balances): array
    {
        $typeIds = collect($balances)->pluck('leave_type_id')->filter()->all();
        if ($typeIds === []) {
            return [];
        }

        $types = LeaveType::query()
            ->with('rule')
            ->when($user->company_id, fn ($q) => $q->where('company_id', $user->company_id))
            ->whereIn('id', $typeIds)
            ->orderBy('name')
            ->get();

        $summaries = [];
        foreach ($types as $type) {
            $rule = $type->rule;
            $chips = [];

            if ($type->is_unlimited) {
                $chips[] = 'Unlimited quota';
            } else {
                $mode = $rule?->accrual_mode ?? LeaveLedgerService::ACCRUAL_YEARLY;
                $chips[] = $mode === LeaveLedgerService::ACCRUAL_MONTHLY ? 'Monthly accrual' : 'Yearly allocation';
                $entitlement = $this->ledger->annualEntitlement($type);
                if ($entitlement !== PHP_FLOAT_MAX) {
                    $chips[] = 'Quota '.$this->formatDays($entitlement);
                }
            }

            if ($rule?->carry_forward_enabled) {
                $limit = $rule->carry_forward_limit;
                $chips[] = $limit !== null && $limit !== ''
                    ? 'Carryover max '.$this->formatDays((float) $limit)
                    : 'Carryover allowed';
            }

            if ($rule?->min_days_per_request) {
                $chips[] = 'Min '.$this->formatDays((float) $rule->min_days_per_request).'/request';
            }
            if ($rule?->max_days_per_request) {
                $chips[] = 'Max '.$this->formatDays((float) $rule->max_days_per_request).'/request';
            }

            $summaries[] = [
                'leave_type_id' => $type->id,
                'name' => $type->name,
                'short_code' => $type->short_code,
                'chips' => $chips,
            ];
        }

        return $summaries;
    }

    protected function formatDays(float $days): string
    {
        $rounded = round($days, 2);
        if (abs($rounded - (int) $rounded) < 0.001) {
            return ((int) $rounded).' day'.(((int) $rounded) === 1 ? '' : 's');
        }

        return rtrim(rtrim(number_format($rounded, 2, '.', ''), '0'), '.').' days';
    }
}
