<?php

declare(strict_types=1);

namespace App\Services\Academy;

use App\Models\AcademyBillingEvent;
use Illuminate\Support\Collection;
use Laravel\Cashier\Subscription;

final class AcademyAdminBillingOverviewService
{
    public function __construct(
        private readonly AcademyBillingPlanService $plans,
    ) {}

    /**
     * @return array<string, mixed>
     */
    public function summary(): array
    {
        $subscriptions = Subscription::query()
            ->where('type', $this->plans->subscriptionName())
            ->with('items')
            ->get();

        $activeSubscriptions = $subscriptions->filter(
            fn (Subscription $subscription): bool => $subscription->active() || $subscription->onGracePeriod()
        );

        $subscriberTiers = [];
        $planBreakdown = [];
        $gracePeriodSubscribers = [];

        foreach ($activeSubscriptions as $subscription) {
            $userId = (int) $subscription->user_id;
            $tier = $this->tierForSubscription($subscription);

            if ($subscription->onGracePeriod()) {
                $gracePeriodSubscribers[$userId] = true;
            }

            if ($tier !== null) {
                $existingTier = $subscriberTiers[$userId] ?? null;

                if ($existingTier === null || $this->rankForTier($tier) > $this->rankForTier($existingTier)) {
                    $subscriberTiers[$userId] = $tier;
                }
            }

            foreach ($this->planKeysForSubscription($subscription) as $planKey) {
                $planBreakdown[$planKey] = (int) ($planBreakdown[$planKey] ?? 0) + 1;
            }
        }

        $recentEvents = AcademyBillingEvent::query()->count();
        $lastWebhookAt = AcademyBillingEvent::query()->latest('processed_at')->value('processed_at');

        return [
            'enabled' => $this->plans->enabled(),
            'active_subscribers' => count($subscriberTiers),
            'creator_subscribers' => count(array_filter($subscriberTiers, static fn (string $tier): bool => $tier === 'creator')),
            'pro_subscribers' => count(array_filter($subscriberTiers, static fn (string $tier): bool => $tier === 'pro')),
            'grace_period_subscribers' => count($gracePeriodSubscribers),
            'ended_subscriptions' => $subscriptions->filter(
                fn (Subscription $subscription): bool => ! $subscription->active() && ! $subscription->onGracePeriod()
            )->count(),
            'configured_plan_count' => count(array_keys($this->plans->plans())),
            'missing_plan_keys' => $this->plans->missingPriceIds(),
            'plan_breakdown' => $this->formatPlanBreakdown($planBreakdown),
            'recent_webhook_count' => $recentEvents,
            'last_webhook_at' => $lastWebhookAt?->toISOString(),
        ];
    }

    /**
     * @return array<int, array<string, mixed>>
     */
    public function recentEvents(int $limit = 15): array
    {
        return AcademyBillingEvent::query()
            ->latest('processed_at')
            ->latest('id')
            ->limit($limit)
            ->get()
            ->map(fn (AcademyBillingEvent $event): array => [
                'id' => (int) $event->id,
                'event_type' => (string) $event->event_type,
                'academy_tier' => $event->academy_tier ? (string) $event->academy_tier : null,
                'academy_plan' => $event->academy_plan ? (string) $event->academy_plan : null,
                'user_id' => $event->user_id ? (int) $event->user_id : null,
                'stripe_customer_id' => $event->stripe_customer_id ? (string) $event->stripe_customer_id : null,
                'stripe_subscription_id' => $event->stripe_subscription_id ? (string) $event->stripe_subscription_id : null,
                'processed_at' => $event->processed_at?->toISOString(),
                'created_at' => $event->created_at?->toISOString(),
                'payload_summary' => is_array($event->payload_summary) ? $event->payload_summary : [],
            ])
            ->values()
            ->all();
    }

    /**
     * @return array<int, array<string, mixed>>
     */
    private function formatPlanBreakdown(array $planBreakdown): array
    {
        return collect(array_keys($this->plans->plans()))
            ->map(function (string $planKey) use ($planBreakdown): array {
                $plan = $this->plans->plan($planKey);

                return [
                    'key' => $planKey,
                    'label' => (string) ($plan['label'] ?? $planKey),
                    'tier' => (string) ($plan['tier'] ?? 'free'),
                    'interval' => (string) ($plan['interval'] ?? 'monthly'),
                    'configured' => (bool) ($plan['configured'] ?? false),
                    'subscribers' => (int) ($planBreakdown[$planKey] ?? 0),
                ];
            })
            ->values()
            ->all();
    }

    /**
     * @return list<string>
     */
    private function planKeysForSubscription(Subscription $subscription): array
    {
        $keys = [];

        foreach ($this->priceIdsForSubscription($subscription) as $priceId) {
            $plan = $this->plans->planForPriceId($priceId);

            if ($plan === null) {
                continue;
            }

            $keys[] = (string) ($plan['key'] ?? '');
        }

        return array_values(array_unique(array_filter($keys, static fn (string $key): bool => $key !== '')));
    }

    private function tierForSubscription(Subscription $subscription): ?string
    {
        $matchedTier = null;

        foreach ($this->priceIdsForSubscription($subscription) as $priceId) {
            $plan = $this->plans->planForPriceId($priceId);

            if ($plan === null) {
                continue;
            }

            $tier = (string) ($plan['tier'] ?? 'free');

            if ($matchedTier === null || $this->rankForTier($tier) > $this->rankForTier($matchedTier)) {
                $matchedTier = $tier;
            }
        }

        return $matchedTier;
    }

    /**
     * @return Collection<int, string>
     */
    private function priceIdsForSubscription(Subscription $subscription): Collection
    {
        $priceIds = $subscription->items
            ->pluck('stripe_price')
            ->filter(fn ($value): bool => is_string($value) && trim($value) !== '')
            ->map(fn (string $value): string => trim($value));

        if ($priceIds->isNotEmpty()) {
            return $priceIds->values();
        }

        $fallbackPrice = trim((string) $subscription->stripe_price);

        return $fallbackPrice === ''
            ? collect()
            : collect([$fallbackPrice]);
    }

    private function rankForTier(string $tier): int
    {
        return match ($this->plans->normalizeTier($tier)) {
            'pro' => 2,
            'creator' => 1,
            default => 0,
        };
    }
}