<?php

namespace App\Listeners;

use App\Jobs\SendPaymentFailedReminderJob;
use App\Models\Subscription;
use App\Models\User;
use App\Models\UserNotificationPreference;
use Illuminate\Support\Carbon;
use Illuminate\Support\Facades\Cache;
use Laravel\Cashier\Events\WebhookReceived;

final class HandleStripeWebhook
{
    public function handle(WebhookReceived $event): void
    {
        $type = $event->payload['type'] ?? null;
        $stripeCustomerId = $event->payload['data']['object']['customer'] ?? null;

        if ($stripeCustomerId === null) {
            return;
        }

        $user = User::where('stripe_id', $stripeCustomerId)->first();

        if ($user === null) {
            return;
        }

        match ($type) {
            'customer.subscription.created',
            'customer.subscription.updated' => $this->handleSubscriptionUpserted($user, $event->payload['data']['object'] ?? []),
            'customer.subscription.deleted' => $this->handleSubscriptionDeleted($user, $event->payload['data']['object'] ?? []),
            'invoice.payment_succeeded' => $this->handlePaymentSucceeded($user),
            'invoice.payment_failed' => $this->handlePaymentFailed($user),
            default => null,
        };
    }

    /**
     * @param  array<string, mixed>  $stripeSubscription
     */
    private function handleSubscriptionUpserted(User $user, array $stripeSubscription): void
    {
        $this->syncPeriodFromStripePayload($stripeSubscription);
        $this->bustPlanCache($user);
    }

    /**
     * @param  array<string, mixed>  $stripeSubscription
     */
    private function handleSubscriptionDeleted(User $user, array $stripeSubscription): void
    {
        UserNotificationPreference::query()
            ->where('user_id', $user->id)
            ->whereIn('channel', ['whatsapp', 'sms'])
            ->update(['enabled' => false]);

        $user->forceFill(['grace_period_until' => null])->save();

        $this->syncPeriodFromStripePayload($stripeSubscription);
        $this->bustPlanCache($user);
    }

    /**
     * Mirror current_period_start / current_period_end from a Stripe subscription
     * payload onto our local row so we don't depend on Stripe at read time.
     *
     * Stripe API ≤ 2024-11-19 places the period fields at the root of the
     * subscription; later versions move them to items.data[0]. We accept either.
     *
     * @param  array<string, mixed>  $stripeSubscription
     */
    private function syncPeriodFromStripePayload(array $stripeSubscription): void
    {
        $stripeId = $stripeSubscription['id'] ?? null;

        if ($stripeId === null) {
            return;
        }

        $subscription = Subscription::where('stripe_id', $stripeId)->first();

        if ($subscription === null) {
            return;
        }

        $start = $stripeSubscription['current_period_start']
            ?? ($stripeSubscription['items']['data'][0]['current_period_start'] ?? null);

        $end = $stripeSubscription['current_period_end']
            ?? ($stripeSubscription['items']['data'][0]['current_period_end'] ?? null);

        $subscription->stripe_data = $stripeSubscription;

        if ($start !== null) {
            $subscription->current_period_start = Carbon::createFromTimestamp($start);
        }

        if ($end !== null) {
            $subscription->current_period_end = Carbon::createFromTimestamp($end);
        }

        $subscription->save();
    }

    private function handlePaymentSucceeded(User $user): void
    {
        $user->forceFill(['grace_period_until' => null])->save();
        $this->bustPlanCache($user);
    }

    private function handlePaymentFailed(User $user): void
    {
        // Idempotency: only the first failed-payment event in a grace window
        // transitions state + dispatches reminders. Stripe may fire this event
        // multiple times per billing cycle (once per failed retry attempt).
        if ($user->grace_period_until !== null) {
            return;
        }

        $user->forceFill(['grace_period_until' => Carbon::now()->addDays(5)])->save();

        SendPaymentFailedReminderJob::dispatch($user->id, 3)->delay(Carbon::now()->addDays(3));
        SendPaymentFailedReminderJob::dispatch($user->id, 5)->delay(Carbon::now()->addDays(5));

        $this->bustPlanCache($user);
    }

    private function bustPlanCache(User $user): void
    {
        $tag = Cache::tags(['plans']);
        $tag->forget("plan_for_user_{$user->id}");
        $tag->forget("plan_cadence_for_user_{$user->id}");
    }
}
