<?php

namespace App\Support;

use App\Models\Lead;
use App\Models\Offerwall;
use App\Models\User;
use App\Models\Withdrawal;
use Illuminate\Support\Carbon;
use Illuminate\Support\Collection;

/**
 * Read-only aggregation for the admin analytics dashboard. Every method returns
 * plain arrays ready for ApexCharts. Nothing here mutates data.
 */
class AdminAnalytics
{
    /**
     * Daily buckets over the last $days for signups, leads and revenue.
     *
     * @return array{labels: array<int,string>, signups: array<int,int>, leads: array<int,int>, revenue: array<int,float>}
     */
    public static function dailySeries(int $days = 14): array
    {
        $days = max(7, min(60, $days));
        $start = Carbon::today()->subDays($days - 1);

        $signups = self::countByDay(User::query()->where('created_at', '>=', $start), $start, $days);
        $leadsCount = self::countByDay(Lead::query()->where('created_at', '>=', $start), $start, $days);
        $revenue = self::sumByDay(Lead::query()->where('created_at', '>=', $start), 'payout', $start, $days);

        $labels = [];
        for ($i = 0; $i < $days; $i++) {
            $labels[] = $start->copy()->addDays($i)->format('M j');
        }

        return [
            'labels' => $labels,
            'signups' => array_values($signups),
            'leads' => array_values($leadsCount),
            'revenue' => array_map(fn ($v) => round((float) $v, 2), array_values($revenue)),
        ];
    }

    /**
     * Cashout status distribution for a donut chart.
     *
     * @return array{labels: array<int,string>, values: array<int,int>}
     */
    public static function cashoutBreakdown(): array
    {
        $counts = Withdrawal::query()
            ->selectRaw('status, COUNT(*) as total')
            ->groupBy('status')
            ->pluck('total', 'status');

        return [
            'labels' => ['Pending', 'Approved', 'Rejected'],
            'values' => [
                (int) ($counts['pending'] ?? 0),
                (int) ($counts['approved'] ?? 0),
                (int) ($counts['rejected'] ?? 0),
            ],
        ];
    }

    /**
     * Top providers by lead volume (last 30 days) for a bar chart.
     *
     * @return array{labels: array<int,string>, values: array<int,int>}
     */
    public static function topProviders(int $limit = 6): array
    {
        $rows = Lead::query()
            ->where('created_at', '>=', Carbon::today()->subDays(30))
            ->whereNotNull('company')
            ->selectRaw('company, COUNT(*) as total')
            ->groupBy('company')
            ->orderByDesc('total')
            ->limit($limit)
            ->get();

        return [
            'labels' => $rows->pluck('company')->map(fn ($c) => (string) $c)->all(),
            'values' => $rows->pluck('total')->map(fn ($v) => (int) $v)->all(),
        ];
    }

    /**
     * Headline figures shown above the charts.
     */
    public static function headline(): array
    {
        $thisWeek = Carbon::today()->subDays(6);
        $prevWeek = Carbon::today()->subDays(13);

        $signupsThisWeek = User::where('created_at', '>=', $thisWeek)->count();
        $signupsPrevWeek = User::whereBetween('created_at', [$prevWeek, $thisWeek])->count();

        $revThisWeek = round((float) Lead::where('created_at', '>=', $thisWeek)->sum('payout'), 2);
        $revPrevWeek = round((float) Lead::whereBetween('created_at', [$prevWeek, $thisWeek])->sum('payout'), 2);
        $leadsThisWeek = Lead::where('created_at', '>=', $thisWeek)->count();
        $leadsPrevWeek = Lead::whereBetween('created_at', [$prevWeek, $thisWeek])->count();

        $approved = Withdrawal::where('status', 'approved')->count();
        $decided = Withdrawal::whereIn('status', ['approved', 'rejected'])->count();

        return [
            'conversion_leads_7d' => $leadsThisWeek,
            'leads_trend' => self::trend($leadsThisWeek, $leadsPrevWeek),
            'revenue_7d' => $revThisWeek,
            'revenue_trend' => self::trend((int) round($revThisWeek * 100), (int) round($revPrevWeek * 100)),
            'signups_7d' => $signupsThisWeek,
            'signups_trend' => self::trend($signupsThisWeek, $signupsPrevWeek),
            'active_offerwalls' => Offerwall::where('is_active', true)->count(),
            'points_outstanding' => round((float) User::where('is_admin', false)->sum('current_points'), 2),
            'paid_out' => round((float) Withdrawal::where('status', 'approved')->sum('amount'), 2),
            'pending_amount' => round((float) Withdrawal::where('status', 'pending')->sum('amount'), 2),
            'approval_rate' => $decided > 0 ? (int) round(($approved / $decided) * 100) : 0,
            'avg_cashout' => $approved > 0
                ? round((float) Withdrawal::where('status', 'approved')->avg('amount'), 2)
                : 0,
        ];
    }

    /**
     * User account composition for a donut chart.
     */
    public static function userBreakdown(): array
    {
        $total = User::where('is_admin', false)->count();
        $verified = User::where('is_admin', false)->whereNotNull('email_verified_at')->where('is_banned', false)->count();
        $banned = User::where('is_admin', false)->where('is_banned', true)->count();
        $unverified = max(0, $total - $verified - $banned);

        return [
            'labels' => ['Verified', 'Unverified', 'Banned'],
            'values' => [$verified, $unverified, $banned],
        ];
    }

    /**
     * Lead review status distribution over the last $days.
     */
    public static function leadStatus(int $days = 30): array
    {
        $start = Carbon::today()->subDays(max(7, min(90, $days)) - 1);

        $counts = Lead::where('created_at', '>=', $start)
            ->selectRaw('status, COUNT(*) as total')
            ->groupBy('status')
            ->pluck('total', 'status');

        return [
            'labels' => ['Approved', 'Held', 'Rejected'],
            'values' => [
                (int) ($counts['approved'] ?? 0),
                (int) ($counts['held'] ?? 0),
                (int) ($counts['rejected'] ?? 0),
            ],
        ];
    }

    /**
     * Running cumulative revenue across the range (line chart).
     */
    public static function revenueCumulative(int $days = 14): array
    {
        $series = self::dailySeries($days);
        $running = 0.0;
        $cumulative = [];

        foreach ($series['revenue'] as $value) {
            $running += (float) $value;
            $cumulative[] = round($running, 2);
        }

        return ['labels' => $series['labels'], 'values' => $cumulative];
    }

    /**
     * Top countries by lead volume (last 30 days) for a bar chart.
     */
    public static function topCountries(int $limit = 6): array
    {
        $rows = Lead::query()
            ->where('created_at', '>=', Carbon::today()->subDays(30))
            ->whereNotNull('country')
            ->where('country', '!=', '')
            ->selectRaw('country, COUNT(*) as total')
            ->groupBy('country')
            ->orderByDesc('total')
            ->limit($limit)
            ->get();

        return [
            'labels' => $rows->pluck('country')->map(fn ($c) => strtoupper((string) $c))->all(),
            'values' => $rows->pluck('total')->map(fn ($v) => (int) $v)->all(),
        ];
    }

    /**
     * Signups vs approved leads by weekday, to spot the best days (radar/bar).
     */
    public static function weekdayActivity(int $days = 28): array
    {
        $start = Carbon::today()->subDays(max(14, min(90, $days)) - 1);
        $labels = ['Mon', 'Tue', 'Wed', 'Thu', 'Fri', 'Sat', 'Sun'];
        $buckets = array_fill(0, 7, 0);

        Lead::where('created_at', '>=', $start)
            ->where('status', 'approved')
            ->get(['created_at'])
            ->each(function ($lead) use (&$buckets) {
                // Carbon: Monday = 1 … Sunday = 7
                $buckets[$lead->created_at->dayOfWeekIso - 1]++;
            });

        return ['labels' => $labels, 'values' => array_values($buckets)];
    }

    private static function trend(int $current, int $previous): int
    {
        if ($previous <= 0) {
            return $current > 0 ? 100 : 0;
        }

        return (int) round((($current - $previous) / $previous) * 100);
    }

    private static function countByDay($query, Carbon $start, int $days): array
    {
        $rows = (clone $query)
            ->selectRaw(self::dateExpr().' as d, COUNT(*) as total')
            ->groupBy('d')
            ->pluck('total', 'd');

        return self::fill($rows, $start, $days);
    }

    private static function sumByDay($query, string $column, Carbon $start, int $days): array
    {
        $rows = (clone $query)
            ->selectRaw(self::dateExpr().' as d, SUM('.$column.') as total')
            ->groupBy('d')
            ->pluck('total', 'd');

        return self::fill($rows, $start, $days);
    }

    /** Portable "date part of created_at" for both SQLite and MySQL. */
    private static function dateExpr(): string
    {
        $driver = config('database.default');
        $connection = config("database.connections.{$driver}.driver");

        return $connection === 'sqlite'
            ? "strftime('%Y-%m-%d', created_at)"
            : "DATE(created_at)";
    }

    private static function fill(Collection $rows, Carbon $start, int $days): array
    {
        $out = [];
        for ($i = 0; $i < $days; $i++) {
            $key = $start->copy()->addDays($i)->format('Y-m-d');
            $out[$key] = (float) ($rows[$key] ?? 0);
        }

        return $out;
    }
}
