<?php

namespace App\Services;

use App\Models\AdminActivityLog;
use App\Models\MemberTier;
use App\Models\Tier;
use App\Models\TierAssessment;
use App\Models\User;
use App\Notifications\TierDowngradedNotification;
use App\Notifications\TierUpgradedNotification;
use Carbon\Carbon;
use Illuminate\Support\Collection;

class TierEngine
{
    /**
     * Evaluate a single member and return a recommendation array.
     *
     * Returns:
     *  action        => 'upgrade' | 'downgrade' | 'maintain' | 'deactivate'
     *  current_tier  => Tier
     *  target_tier   => Tier
     *  reasons       => string[]
     *  at_risk       => bool  (member is close to losing their tier)
     */
    public function evaluate(User $user): array
    {
        $currentTier    = $user->currentTier?->tier ?? Tier::where('slug', 'invited')->first();
        $tierAssignedAt = $user->currentTier?->assigned_at ?? $user->enrolled_at ?? now();
        $now            = Carbon::now();
        $daysAtTier     = $tierAssignedAt ? Carbon::parse($tierAssignedAt)->diffInDays($now) : 0;

        // ── Minimum tenure guards ────────────────────────────────────────────
        // Don't assess until the member has been at their current tier long
        // enough to have a full data window. This prevents immediate upgrades
        // on day 1 or premature downgrades before the observation period ends.
        //
        //  Upgrade:   need 90 days at current tier (full 3-month window)
        //  Downgrade: need 60 days at current tier (2 full months of evidence)
        $canAssessUpgrade   = $daysAtTier >= 90;
        $canAssessDowngrade = $daysAtTier >= 60;

        $reasons = [];
        $action  = 'maintain';

        // ── Gather rolling window data ──────────────────────────────────────
        $visits3m  = $this->qualifyingVisits($user, $now->copy()->subMonths(3));
        $visits1m  = $this->qualifyingVisits($user, $now->copy()->subMonth(), $currentTier?->min_spend_per_visit ?? 100);
        $visits12m = $this->qualifyingVisits($user, $now->copy()->subYear());
        $points3m  = $this->pointsInPeriod($user, $now->copy()->subMonths(3));

        // ── Check upgrade eligibility ───────────────────────────────────────
        $tiers      = Tier::orderBy('display_order')->get();
        $nextTier   = $tiers->firstWhere('display_order', ($currentTier?->display_order ?? 1) + 1);
        $targetTier = $currentTier;

        if ($nextTier && $canAssessUpgrade) {
            if ($this->meetsUpgradeCriteria($user, $nextTier, $visits3m, $visits1m, $visits12m, $points3m)) {
                $action     = 'upgrade';
                $targetTier = $nextTier;
                $reasons[]  = "Meets {$nextTier->name} tier criteria.";
            }
        } elseif ($nextTier && ! $canAssessUpgrade) {
            $daysRemaining = 90 - $daysAtTier;
            $reasons[] = "Upgrade assessment not due yet — {$daysRemaining} day(s) remaining in observation window.";
        }

        // ── Check downgrade / deactivation risk (only if not upgrading) ─────
        if ($action === 'maintain' && $canAssessDowngrade) {
            $downgradeResult = $this->checkDowngrade($user, $currentTier, $visits3m, $visits1m, $visits12m, $points3m, $tiers);
            if ($downgradeResult['action'] !== 'maintain') {
                $action     = $downgradeResult['action'];
                $targetTier = $downgradeResult['tier'];
                $reasons    = array_merge($reasons, $downgradeResult['reasons']);
            }
        } elseif ($action === 'maintain' && ! $canAssessDowngrade) {
            $daysRemaining = 60 - $daysAtTier;
            $reasons[] = "Downgrade assessment not due yet — {$daysRemaining} day(s) remaining in observation window.";
        }

        // ── At-risk flag ────────────────────────────────────────────────────
        $atRisk = $action === 'maintain' && $canAssessDowngrade
            && $this->isAtRisk($user, $currentTier, $visits3m, $visits1m, $points3m);
        if ($atRisk) {
            $reasons[] = 'Member is approaching the minimum visit/spend threshold.';
        }

        return [
            'action'        => $action,
            'current_tier'  => $currentTier,
            'target_tier'   => $targetTier,
            'reasons'       => $reasons,
            'at_risk'       => $atRisk,
            'days_at_tier'  => $daysAtTier,
            'stats'         => [
                'visits_3m'  => $visits3m->count(),
                'visits_1m'  => $visits1m->count(),
                'visits_12m' => $visits12m->count(),
                'points_3m'  => $points3m,
            ],
        ];
    }

    /**
     * Run a full quarterly assessment across all active members.
     * Creates TierAssessment records for members needing action.
     * Returns count of assessments created.
     */
    public function runQuarterlyAssessment(?int $adminId = null): int
    {
        $created = 0;

        User::where('status', 'active')
            ->with(['currentTier.tier', 'visits'])
            ->chunk(100, function (Collection $members) use (&$created, $adminId) {
                foreach ($members as $user) {
                    $result = $this->evaluate($user);

                    // Only create an assessment if action is needed or member is at risk
                    if ($result['action'] !== 'maintain' || $result['at_risk']) {
                        // Avoid duplicate pending assessments
                        $existing = TierAssessment::where('user_id', $user->id)
                            ->where('status', 'pending')
                            ->exists();

                        if (! $existing) {
                            TierAssessment::create([
                                'user_id'              => $user->id,
                                'previous_tier_id'     => $result['current_tier']->id,
                                'recommended_tier_id'  => $result['target_tier']->id,
                                'status'               => 'pending',
                                'trigger'              => $adminId ? 'quarterly' : 'auto_suggest',
                                'actioned_by'          => $adminId,
                                'reason'               => implode(' ', $result['reasons']),
                                'assessed_at'          => now(),
                            ]);
                            $created++;
                        }
                    }
                }
            });

        return $created;
    }

    public function runAutoAssessment(bool $autoUpgrade = true, bool $autoDowngrade = false): array
    {
        $upgraded   = 0;
        $downgraded = 0;
        $flagged    = 0;

        User::where('status', 'active')
            ->with(['currentTier.tier', 'visits'])
            ->chunk(100, function (Collection $members) use (&$upgraded, &$downgraded, &$flagged, $autoUpgrade, $autoDowngrade) {
                foreach ($members as $user) {
                    $result = $this->evaluate($user);

                    if ($result['action'] === 'upgrade') {
                        if ($autoUpgrade) {
                            $this->applyTierChange($user, $result['target_tier'], null, implode(' ', $result['reasons']), 'auto_suggest');
                            TierAssessment::where('user_id', $user->id)->where('status', 'pending')
                                ->update(['status' => 'actioned', 'actioned_at' => now(), 'new_tier_id' => $result['target_tier']->id]);
                            $upgraded++;
                        } else {
                            $this->flagForReview($user, $result);
                            $flagged++;
                        }

                    } elseif (in_array($result['action'], ['downgrade', 'deactivate'])) {
                        if ($autoDowngrade) {
                            $this->applyTierChange($user, $result['target_tier'], null, implode(' ', $result['reasons']), 'auto_suggest');
                            TierAssessment::where('user_id', $user->id)->where('status', 'pending')
                                ->update(['status' => 'actioned', 'actioned_at' => now(), 'new_tier_id' => $result['target_tier']->id]);
                            $downgraded++;
                        } else {
                            $this->flagForReview($user, $result);
                            $flagged++;
                        }
                    }
                }
            });

        return compact('upgraded', 'downgraded', 'flagged');
    }

    private function flagForReview(User $user, array $result): void
    {
        $exists = TierAssessment::where('user_id', $user->id)->where('status', 'pending')->exists();
        if (! $exists) {
            TierAssessment::create([
                'user_id'             => $user->id,
                'previous_tier_id'    => $result['current_tier']->id,
                'recommended_tier_id' => $result['target_tier']->id,
                'status'              => 'pending',
                'trigger'             => 'auto_suggest',
                'reason'              => implode(' ', $result['reasons']),
                'assessed_at'         => now(),
            ]);
        }
    }

    /**
     * Apply a tier change to a member (upgrade or downgrade).
     */
    public function applyTierChange(User $user, Tier $newTier, ?int $adminId, string $reason, string $trigger = 'manual'): void
    {
        // Mark all existing tier records as not current
        MemberTier::where('user_id', $user->id)->update(['is_current' => false]);

        // Map trigger → valid assignment_type ENUM ('auto', 'manual', 'initial')
        $assignmentType = match ($trigger) {
            'quarterly', 'auto_suggest' => 'auto',
            'manual'                    => 'manual',
            default                     => 'auto',
        };

        // Create new current tier record
        MemberTier::create([
            'user_id'         => $user->id,
            'tier_id'         => $newTier->id,
            'is_current'      => true,
            'assigned_by'     => $adminId ?: null, // null for system-automated changes
            'assignment_type' => $assignmentType,
            'reason'          => $reason,
            'assigned_at'     => now(),
        ]);

        // Only log admin activity when triggered by a real admin
        if ($adminId) {
            AdminActivityLog::create([
                'admin_id'    => $adminId,
                'action'      => 'tier.change',
                'target_type' => User::class,
                'target_id'   => $user->id,
                'meta'        => [
                    'from'    => $user->currentTier?->tier?->name ?? 'unknown',
                    'to'      => $newTier->name,
                    'trigger' => $trigger,
                    'reason'  => $reason,
                ],
                'ip_address'  => request()->ip(),
            ]);
        }

        // Send tier change notification
        $previousTierName = $user->currentTier?->tier?->name ?? 'unknown';
        if ($newTier->display_order > ($user->currentTier?->tier?->display_order ?? 0)) {
            $user->notify(new TierUpgradedNotification($previousTierName, $newTier->name));
        } else {
            $user->notify(new TierDowngradedNotification($previousTierName, $newTier->name));
        }
    }

    // ── Private helpers ────────────────────────────────────────────────────────

    private function qualifyingVisits(User $user, Carbon $since, ?float $minSpend = null): Collection
    {
        return $user->visits()
            ->where('visit_date', '>=', $since->toDateString())
            ->when($minSpend, fn ($q) => $q->where('spend_amount', '>=', $minSpend))
            ->get();
    }

    private function pointsInPeriod(User $user, Carbon $since): int
    {
        return (int) $user->pointsTransactions()
            ->where('type', 'credit')
            ->where('created_at', '>=', $since)
            ->sum('amount');
    }

    private function meetsUpgradeCriteria(User $user, Tier $tier, Collection $v3m, Collection $v1m, Collection $v12m, int $points3m): bool
    {
        return match ($tier->slug) {
            'indulged'  => $v3m->where('spend_amount', '>=', 150)->count() >= 3
                           && $points3m >= 500,

            'limitless' => ($v3m->where('spend_amount', '>=', 200)->count() >= 4
                           || $v12m->where('spend_amount', '>=', 200)->count() >= 15)
                           && $points3m >= 1000,

            default => false,
        };
    }

    private function checkDowngrade(User $user, ?Tier $currentTier, Collection $v3m, Collection $v1m, Collection $v12m, int $points3m, Collection $allTiers): array
    {
        if (! $currentTier) {
            return ['action' => 'maintain', 'tier' => null, 'reasons' => []];
        }

        $reasons = [];
        $action  = 'maintain';
        $downTier = $allTiers->firstWhere('display_order', $currentTier->display_order - 1);

        switch ($currentTier->slug) {
            case 'invited':
                // Becomes inactive if no qualifying visit in 3 months
                if ($v3m->where('spend_amount', '>=', 100)->isEmpty()) {
                    $action    = 'deactivate';
                    $reasons[] = 'No qualifying visit (min $100) in the past 3 months.';
                }
                break;

            case 'indulged':
                $qualifyingMonths = $this->countQualifyingMonths($user, 2, 150);
                if ($qualifyingMonths < 1) {
                    $action    = 'downgrade';
                    $reasons[] = 'Failed to meet minimum 1 qualifying visit ($150) per month for 2+ consecutive months.';
                } elseif ($points3m < 500) {
                    $reasons[] = 'Points below 500 threshold for the quarter.';
                    // At-risk only — don't auto-downgrade purely on points
                }
                break;

            case 'limitless':
                $qualifyingMonths = $this->countQualifyingMonths($user, 2, 200);
                $qualifying12m    = $v12m->where('spend_amount', '>=', 200)->count();
                if ($qualifyingMonths < 2 && $qualifying12m < 15) {
                    $action    = 'downgrade';
                    $reasons[] = 'Failed to meet minimum visit frequency for Limitless tier for 2+ consecutive months.';
                } elseif ($points3m < 1000) {
                    $reasons[] = 'Points below 1000 threshold for the quarter.';
                }
                break;
        }

        return [
            'action'  => $action,
            'tier'    => $action === 'downgrade' ? ($downTier ?? $allTiers->first()) : $currentTier,
            'reasons' => $reasons,
        ];
    }

    private function countQualifyingMonths(User $user, int $monthsBack, float $minSpend): int
    {
        $failing = 0;
        for ($i = 1; $i <= $monthsBack; $i++) {
            $start = Carbon::now()->subMonths($i)->startOfMonth();
            $end   = Carbon::now()->subMonths($i)->endOfMonth();
            $count = $user->visits()
                ->whereBetween('visit_date', [$start->toDateString(), $end->toDateString()])
                ->where('spend_amount', '>=', $minSpend)
                ->count();
            if ($count < 1) $failing++;
        }
        return $monthsBack - $failing; // months where they DID qualify
    }

    private function isAtRisk(User $user, ?Tier $currentTier, Collection $v3m, Collection $v1m, int $points3m): bool
    {
        if (! $currentTier) return false;

        return match ($currentTier->slug) {
            'invited'   => $v3m->where('spend_amount', '>=', 100)->count() === 1,
            'indulged'  => $v1m->where('spend_amount', '>=', 150)->isEmpty() || $points3m < 300,
            'limitless' => $v1m->where('spend_amount', '>=', 200)->count() < 2 || $points3m < 700,
            default     => false,
        };
    }
}
