<?php

namespace App\Http\Controllers;

use App\Enums\UserPlan;
use Carbon\Carbon;
use Illuminate\Http\Request;
use Illuminate\Support\Facades\Log;
use Laravel\Cashier\Http\Controllers\WebhookController as CashierWebhookController;
use Symfony\Component\HttpFoundation\Response;

class StripeWebhookController extends CashierWebhookController
{
    /**
     * Handle a Stripe webhook call.
     *
     * Logged before dispatch so a delivery that arrives but is not acted on — an event
     * type nothing handles, a customer we do not know — is still visible. Without this,
     * "Stripe never sent it" and "Stripe sent it and nothing happened" look identical.
     */
    public function handleWebhook(Request $request): Response
    {
        $payload = json_decode($request->getContent(), true);

        Log::channel('stripe')->info('Webhook received', [
            'event' => $payload['type'] ?? null,
            'event_id' => $payload['id'] ?? null,
        ]);

        return parent::handleWebhook($request);
    }

    /**
     * Handle customer subscription created.
     *
     * @param  array<string, mixed>  $payload
     */
    protected function handleCustomerSubscriptionCreated(array $payload): Response
    {
        $response = parent::handleCustomerSubscriptionCreated($payload);

        $this->syncFromPayload($payload);

        return $response;
    }

    /**
     * Handle customer subscription updated.
     *
     * @param  array<string, mixed>  $payload
     */
    protected function handleCustomerSubscriptionUpdated(array $payload): ?Response
    {
        $response = parent::handleCustomerSubscriptionUpdated($payload);

        $this->syncFromPayload($payload);

        return $response;
    }

    /**
     * Handle customer subscription deleted.
     *
     * @param  array<string, mixed>  $payload
     */
    protected function handleCustomerSubscriptionDeleted(array $payload): Response
    {
        $response = parent::handleCustomerSubscriptionDeleted($payload);

        $this->syncFromPayload($payload);

        return $response;
    }

    /**
     * Mirror the Stripe subscription onto the two columns Cashier does not own:
     * the user's plan, and the local `cancel_at` that records whether Stripe will
     * renew the subscription (set at checkout, but changeable from the portal).
     *
     * Cashier's own handlers have already run, so the subscription row exists.
     *
     * @param  array<string, mixed>  $payload
     */
    protected function syncFromPayload(array $payload): void
    {
        $data = $payload['data']['object'];

        $context = [
            'event' => $payload['type'] ?? null,
            'event_id' => $payload['id'] ?? null,
            'subscription' => $data['id'] ?? null,
            'stripe_id' => $data['customer'] ?? null,
            'status' => $data['status'] ?? null,
        ];

        if (! $user = $this->getUserByStripeId($data['customer'])) {
            // Not an error: Stripe sends events for every customer on the account,
            // including ones created outside this app or belonging to another
            // environment sharing the same test-mode keys.
            Log::channel('stripe')->warning('Webhook for an unknown customer, ignored', $context);

            return;
        }

        $context['user_id'] = $user->id;

        $subscription = $user->subscriptions()
            ->where('stripe_id', $data['id'])
            ->first();

        if ($subscription) {
            $cancelAt = isset($data['cancel_at'])
                ? Carbon::createFromTimestamp($data['cancel_at'])
                : null;

            // Only worth a line when it actually moves — the renewal choice changing is
            // the thing this column exists to remember, and Stripe replays events.
            if (! $this->sameMoment($subscription->cancel_at, $cancelAt)) {
                Log::channel('stripe')->info('Renewal changed', $context + [
                    'cancel_at_was' => optional($subscription->cancel_at)->toIso8601String(),
                    'cancel_at_now' => optional($cancelAt)->toIso8601String(),
                    'auto_renews' => is_null($cancelAt),
                ]);
            }

            $subscription->cancel_at = $cancelAt;
            $subscription->save();
        } else {
            Log::channel('stripe')->warning('No local subscription row for this event', $context);
        }

        // `user_plan` is the single source of truth every consumer reads (mobile app
        // through coloid-api, the accreditation gate). Only an active Stripe
        // subscription grants Pro; anything else — canceled, unpaid, incomplete —
        // reverts to free.
        $plan = ($data['status'] ?? null) === 'active'
            ? UserPlan::PRO
            : UserPlan::FREE;

        if ($user->user_plan !== $plan) {
            Log::channel('stripe')->info('Plan changed', $context + [
                'plan_was' => $user->user_plan->value,
                'plan_now' => $plan->value,
            ]);

            $user->forceFill(['user_plan' => $plan])->save();
        }
    }

    /**
     * Whether two nullable dates point at the same instant, so a replayed webhook
     * carrying an unchanged `cancel_at` does not read as a change.
     */
    protected function sameMoment(?Carbon $a, ?Carbon $b): bool
    {
        if (is_null($a) || is_null($b)) {
            return is_null($a) && is_null($b);
        }

        return $a->equalTo($b);
    }
}
