<?php
namespace App\Services;

use App\Models\Assistance;
use App\Models\Membership_users;
use App\Models\Sale_details;
use App\Models\Sale;
use App\Models\User;
use Illuminate\Support\Collection;
use Illuminate\Support\Facades\DB;
use Carbon\Carbon;

class UserDashServices
{
    public function getDashboardData(int $userId): array
    {
        $user = User::query()
            ->with(['people', 'membership_users.membership', 'entry_time_users' => function ($query) {
                $query->where('is_active', 1)->with('entry_time');
            }])
            ->findOrFail($userId);

        $membershipUser = $this->resolveCurrentMembershipUser($user);
        $membershipName = $membershipUser?->membership?->name ?? 'Sin plan activo';
        $membershipDays = (int) ($membershipUser?->membership?->days ?? 0);
        $planDaysPerWeek = $this->resolvePlanDaysPerWeek($membershipDays, (int) $user->entry_time_users->count());
        $planDaysPerWeekForWorkweek = min(5, $planDaysPerWeek);
        $planDaysLabel = $planDaysPerWeekForWorkweek . ' dias / semana';

        $planStart = $membershipUser?->start_date ? Carbon::parse($membershipUser->start_date)->startOfDay() : null;
        $planEnd = $membershipUser?->end_date ? Carbon::parse($membershipUser->end_date)->endOfDay() : Carbon::now()->endOfDay();

        $weekStart = Carbon::now()->startOfWeek(Carbon::MONDAY);
        $weekEnd = $weekStart->copy()->addDays(4)->endOfDay();

        $attendanceBase = Assistance::query()->where('user_id', $userId);
        if ($planStart) {
            $attendanceBase->where('created_at', '>=', $planStart);
        }
        if ($planEnd) {
            $attendanceBase->where('created_at', '<=', $planEnd);
        }

        $totalAttendances = (int) (clone $attendanceBase)
            ->selectRaw('COUNT(DISTINCT DATE(created_at)) as total_days')
            ->value('total_days');

        $weekAttendances = (int) Assistance::query()
            ->where('user_id', $userId)
            ->whereBetween('created_at', [$weekStart, $weekEnd])
            ->selectRaw('COUNT(DISTINCT DATE(created_at)) as total_days')
            ->value('total_days');

        $weeklyProgressPercent = min(100, (int) round(($weekAttendances / max(1, $planDaysPerWeekForWorkweek)) * 100));

        $snackBase = Sale_details::query()
            ->join('sale', 'sale.id', '=', 'sale_details.sale_id')
            ->join('product', 'product.id', '=', 'sale_details.product_id')
            ->where('sale.user_id', $userId)
            ->whereNotNull('sale_details.product_id')
            ->where('sale.status', '!=', 'cancelada');

        $snacksTotalSpend = (float) (clone $snackBase)->sum(DB::raw('COALESCE(sale_details.subtotal, 0)'));
        $pendingMembershipSales = Sale::query()
            ->where('user_id', $userId)
            ->where('debt', '>', 0)
            ->where('status', 'pendiente')
            ->whereHas('saleDetails', function ($query) {
                $query->whereNotNull('membership_user_id');
            })
            ->orderBy('expiration_date')
            ->get(['id', 'debt', 'expiration_date']);

        $userDebtTotal = (float) $pendingMembershipSales->sum(function ($sale) {
            return (float) ($sale->debt ?? 0);
        });

        $nextExpirationDate = $pendingMembershipSales
            ->pluck('expiration_date')
            ->filter()
            ->map(function ($date) {
                return Carbon::parse($date);
            })
            ->sortBy(function (Carbon $date) {
                return $date->timestamp;
            })
            ->first();

        $plannedUntilNow = $this->plannedDaysUntilNow($planStart, $planDaysPerWeek);
        $compliance = $plannedUntilNow > 0
            ? min(100, round(($totalAttendances / $plannedUntilNow) * 100, 2))
            : 0;

        $monthlyAttendance = $this->buildMonthlyAttendance($userId);
        $snackInvoices = $this->buildSnackInvoices($snackBase);
        $attendanceTimeline = $this->buildAttendanceTimeline($userId, $planDaysPerWeekForWorkweek);
        $weeklyCompliance = $this->buildWeeklyCompliance($userId, $planDaysPerWeekForWorkweek);

        return [
            'header' => [
                'client_name' => trim(($user->name ?? '') . ' ' . ($user->people?->last_name ?? '')),
                'avatar' => $user->people?->url_face,
                'plan_label' => $planDaysLabel,
                'membership_name' => $membershipName,
                'cycle_label' => $this->formatCycleLabel(Carbon::now()),
                'week_attendances' => $weekAttendances,
                'week_target' => $planDaysPerWeekForWorkweek,
                'week_progress_percent' => $weeklyProgressPercent,
            ],
            'kpis' => [
                'total_attendances' => $totalAttendances,
                'debt_total' => $userDebtTotal,
                'debt_expiration_date' => $nextExpirationDate?->format('d/m/Y'),
                'snacks_spend' => $snacksTotalSpend,
                'compliance_percent' => $compliance,
            ],
            'monthly_attendance' => $monthlyAttendance,
            'snack_invoices' => $snackInvoices,
            'attendance_timeline' => $attendanceTimeline,
            'weekly_compliance' => $weeklyCompliance,
        ];
    }

    private function resolveCurrentMembershipUser(User $user): ?Membership_users
    {
        $now = Carbon::now();

        return $user->membership_users
            ->first(function ($membershipUser) use ($now) {
                if ((int) ($membershipUser->is_active ?? 0) !== 1) {
                    return false;
                }

                $start = $membershipUser->start_date ? Carbon::parse($membershipUser->start_date)->startOfDay() : null;
                $end = $membershipUser->end_date ? Carbon::parse($membershipUser->end_date)->endOfDay() : null;

                if ($start && $now->lt($start)) {
                    return false;
                }

                if ($end && $now->gt($end)) {
                    return false;
                }

                return true;
            })
            ?? $user->membership_users->firstWhere('is_active', 1)
            ?? $user->membership_users->sortByDesc('start_date')->first();
    }

    private function buildMonthlyAttendance(int $userId): array
    {
        $start = Carbon::now()->startOfMonth()->subMonths(5);
        $raw = Assistance::query()
            ->where('user_id', $userId)
            ->where('created_at', '>=', $start)
            ->selectRaw('YEAR(created_at) as y, MONTH(created_at) as m, COUNT(DISTINCT DATE(created_at)) as total')
            ->groupBy('y', 'm')
            ->get()
            ->keyBy(function ($row) {
                return sprintf('%04d-%02d', $row->y, $row->m);
            });

        $months = [];
        $labels = [];
        $values = [];

        for ($i = 5; $i >= 0; $i--) {
            $month = Carbon::now()->startOfMonth()->subMonths($i);
            $key = $month->format('Y-m');
            $value = (int) ($raw[$key]->total ?? 0);

            $labels[] = $this->shortMonthEs($month);
            $values[] = $value;
            $months[] = [
                'label' => $this->shortMonthEs($month),
                'value' => $value,
            ];
        }

        $maxValue = !empty($values) ? max($values) : 0;
        $maxIndex = array_search($maxValue, $values, true);
        $peakLabel = $maxIndex !== false ? ($labels[$maxIndex] ?? '-') : '-';

        return [
            'labels' => $labels,
            'values' => $values,
            'bars' => $months,
            'insight' => 'Pico de asistencia en ' . $peakLabel . ' (' . $maxValue . ' dias).',
        ];
    }

    private function buildSnackInvoices($snackBaseQuery): array
    {
        $invoices = (clone $snackBaseQuery)
            ->selectRaw('sale.created_at as sale_date, product.product_name as product_name, sale_details.quantity, COALESCE(sale_details.unit_price, 0) as unit_price, COALESCE(sale_details.subtotal, 0) as total, COALESCE(sale.total, 0) as sale_total, COALESCE(sale.debt, 0) as sale_debt')
            ->orderByDesc('sale.created_at')
            ->limit(8)
            ->get()
            ->map(function ($row) {
                return [
                    'date' => Carbon::parse($row->sale_date)->format('d/m/Y'),
                    'product_name' => $row->product_name,
                    'quantity' => (int) $row->quantity,
                    'unit_price' => (float) $row->unit_price,
                    'total' => (float) $row->total,
                    'sale_total' => (float) $row->sale_total,
                    'sale_debt' => (float) $row->sale_debt,
                ];
            })
            ->values()
            ->toArray();

        $last30DaysTotal = (float) (clone $snackBaseQuery)
            ->where('sale.created_at', '>=', Carbon::now()->subDays(30)->startOfDay())
            ->sum(DB::raw('COALESCE(sale_details.subtotal, 0)'));

        return [
            'rows' => $invoices,
            'last_30_days_total' => $last30DaysTotal,
        ];
    }

    private function buildAttendanceTimeline(int $userId, int $planDaysPerWeek): array
    {
        $plannedDayNumbers = $this->getPlannedWeekdayNumbers($planDaysPerWeek);
        $dateFrom = Carbon::now()->subDays(7)->startOfDay();

        $attendanceDays = Assistance::query()
            ->where('user_id', $userId)
            ->where('created_at', '>=', $dateFrom)
            ->whereRaw('WEEKDAY(created_at) BETWEEN 0 AND 4')
            ->selectRaw('DATE(created_at) as d')
            ->groupBy('d')
            ->pluck('d')
            ->toArray();

        $attendanceMap = array_fill_keys($attendanceDays, true);
        $rows = [];

        for ($i = 0; $i < 8; $i++) {
            $date = Carbon::now()->subDays($i);
            $isoDay = (int) $date->dayOfWeekIso;
            $isPlanned = in_array($isoDay, $plannedDayNumbers, true);
            $key = $date->toDateString();
            $attended = isset($attendanceMap[$key]);

            $rows[] = [
                'date' => $date->format('d/m'),
                'planned' => $isPlanned,
                'attended' => $attended,
                'note' => $attended ? '-' : ($isPlanned ? 'Sin asistencia' : '-'),
            ];
        }

        return [
            'rows' => $rows,
            'week_status' => 'Ultima semana: ' . min($planDaysPerWeek, count(array_filter($rows, function ($row) {
                return $row['planned'] && $row['attended'];
            }))) . '/' . $planDaysPerWeek . ' asistencias',
        ];
    }

    private function buildWeeklyCompliance(int $userId, int $planDaysPerWeek): array
    {
        $weeks = [];
        $labels = [];
        $values = [];

        for ($i = 3; $i >= 0; $i--) {
            $start = Carbon::now()->startOfWeek(Carbon::MONDAY)->subWeeks($i);
            $end = $start->copy()->addDays(4)->endOfDay();

            $attended = (int) Assistance::query()
                ->where('user_id', $userId)
                ->whereBetween('created_at', [$start, $end])
                ->selectRaw('COUNT(DISTINCT DATE(created_at)) as total_days')
                ->value('total_days');

            $percent = round(($attended / max(1, $planDaysPerWeek)) * 100, 2);
            $weekName = 'Semana ' . (4 - $i);

            $weeks[] = [
                'label' => $weekName,
                'percent' => (float) min(100, $percent),
            ];
            $labels[] = $weekName;
            $values[] = (float) min(100, $percent);
        }

        $avg = !empty($values) ? round(array_sum($values) / count($values), 2) : 0;

        return [
            'weeks' => $weeks,
            'labels' => $labels,
            'values' => $values,
            'average' => $avg,
            'insight' => 'El cumplimiento promedio es ' . $avg . '% (objetivo recomendado 80%).',
        ];
    }

    private function plannedDaysUntilNow(?Carbon $planStart, int $daysPerWeek): int
    {
        if (! $planStart) {
            return $daysPerWeek;
        }

        $weeks = max(1, (int) ceil($planStart->diffInDays(Carbon::now()) / 7));
        return max($daysPerWeek, $weeks * $daysPerWeek);
    }

    private function resolvePlanDaysPerWeek(int $membershipDays, int $fallbackEntrySlots): int
    {
        if ($membershipDays > 0) {
            // Regla de negocio: membresia mensual, distribuir dias en 4 semanas.
            return max(1, min(7, (int) round($membershipDays / 4)));
        }

        return max(1, min(7, $fallbackEntrySlots));
    }

    private function getPlannedWeekdayNumbers(int $daysPerWeek): array
    {
        $daysPerWeek = max(1, min(5, $daysPerWeek));
        if ($daysPerWeek === 1) {
            return [1];
        }

        $selected = [];
        $step = 4 / max(1, ($daysPerWeek - 1));
        for ($i = 0; $i < $daysPerWeek; $i++) {
            $selected[] = 1 + (int) round($i * $step);
        }

        $selected = array_values(array_unique(array_map(function ($n) {
            return max(1, min(5, $n));
        }, $selected)));

        while (count($selected) < $daysPerWeek) {
            for ($candidate = 1; $candidate <= 5; $candidate++) {
                if (! in_array($candidate, $selected, true)) {
                    $selected[] = $candidate;
                    break;
                }
            }
        }

        sort($selected);
        return $selected;
    }

    private function formatCycleLabel(Carbon $date): string
    {
        $months = [
            1 => 'Enero',
            2 => 'Febrero',
            3 => 'Marzo',
            4 => 'Abril',
            5 => 'Mayo',
            6 => 'Junio',
            7 => 'Julio',
            8 => 'Agosto',
            9 => 'Septiembre',
            10 => 'Octubre',
            11 => 'Noviembre',
            12 => 'Diciembre',
        ];

        return ($months[(int) $date->month] ?? 'Mes') . ' ' . $date->year;
    }

    private function shortMonthEs(Carbon $date): string
    {
        $months = [
            1 => 'Ene',
            2 => 'Feb',
            3 => 'Mar',
            4 => 'Abr',
            5 => 'May',
            6 => 'Jun',
            7 => 'Jul',
            8 => 'Ago',
            9 => 'Sep',
            10 => 'Oct',
            11 => 'Nov',
            12 => 'Dic',
        ];

        return $months[(int) $date->month] ?? $date->format('M');
    }

    public function getUserSalesData($userId, $startDate, $endDate): Collection
    {
        return Sale::where('user_id', $userId)
            ->whereBetween('created_at', [$startDate, $endDate])
            ->get();
    } 
}