<?php

namespace App\Http\Controllers\Billing;

use App\Models\Plan;
use App\Models\PlanSubscription;
use App\Models\Workspace;
use App\Services\Billing\PlanSubscriptionLedger;
use App\Services\Billing\WebhookIdempotency;
use Laravel\Cashier\Http\Controllers\WebhookController as CashierWebhookController;
use Symfony\Component\HttpFoundation\Response;

/**
 * Stripe → us. Cashier handles the heavy lifting (subscription row CRUD,
 * customer/payment method sync, invoice payment-required emails). We
 * extend it to also flip the workspace's local `plan_id` whenever the
 * subscription's active price changes — that's what gates feature access
 * and conversation quota in the rest of the app.
 *
 * Mapping is keyed off the Plan.stripe_price_id we persisted at checkout
 * time (see StripeProductSync). When a subscription is canceled the
 * workspace falls back to the 'free' plan.
 */
class WebhookController extends CashierWebhookController
{
    public function __construct(
        private readonly WebhookIdempotency $idempotency,
        private readonly PlanSubscriptionLedger $ledger,
    ) {
        parent::__construct();
    }

    protected function handleCustomerSubscriptionCreated(array $payload): Response
    {
        if ($this->isReplay($payload)) {
            return new Response('Webhook already processed.', 200);
        }
        $response = parent::handleCustomerSubscriptionCreated($payload);
        $this->syncWorkspacePlan($payload['data']['object'] ?? []);

        return $response;
    }

    protected function handleCustomerSubscriptionUpdated(array $payload): Response
    {
        if ($this->isReplay($payload)) {
            return new Response('Webhook already processed.', 200);
        }
        $response = parent::handleCustomerSubscriptionUpdated($payload);
        $this->syncWorkspacePlan($payload['data']['object'] ?? []);

        return $response;
    }

    protected function handleCustomerSubscriptionDeleted(array $payload): Response
    {
        if ($this->isReplay($payload)) {
            return new Response('Webhook already processed.', 200);
        }
        $response = parent::handleCustomerSubscriptionDeleted($payload);
        $this->revertToFreePlan($payload['data']['object'] ?? []);

        return $response;
    }

    /**
     * Stripe's `event.id` is stable across retries. Recording it on
     * first sight + checking the unique constraint on subsequent
     * deliveries lets us 200-OK a duplicate without re-running the
     * (un)subscribe logic — preventing audit-log doubling, race
     * conditions between two simultaneous workers, and accidental
     * mid-flight state regressions when a stale retry arrives after
     * a newer event has already been applied.
     *
     * @param  array<string, mixed>  $payload
     */
    private function isReplay(array $payload): bool
    {
        $eventId = (string) ($payload['id'] ?? '');
        $eventType = (string) ($payload['type'] ?? '');

        return ! $this->idempotency->recordOrSkip(WebhookIdempotency::STRIPE, $eventId, $eventType);
    }

    /**
     * Mirror the Stripe subscription's active price → workspace.plan_id.
     * Statuses considered "owns the plan": active, trialing, past_due
     * (grace period). Anything else (incomplete, canceled) doesn't grant
     * the plan yet.
     */
    private function syncWorkspacePlan(array $obj): void
    {
        $stripeCustomerId = (string) ($obj['customer'] ?? '');
        $status = (string) ($obj['status'] ?? '');
        $priceId = (string) ($obj['items']['data'][0]['price']['id'] ?? '');

        if ($stripeCustomerId === '' || $priceId === '') {
            return;
        }

        $workspace = Workspace::query()->where('stripe_id', $stripeCustomerId)->first();
        if ($workspace === null) {
            return;
        }

        if (! in_array($status, ['active', 'trialing', 'past_due'], true)) {
            return;
        }

        $plan = Plan::query()->where('stripe_price_id', $priceId)->first();
        if ($plan === null) {
            return;
        }

        if ($workspace->plan_id !== $plan->id) {
            $workspace->forceFill(['plan_id' => $plan->id])->save();
        }

        $subId = (string) ($obj['id'] ?? '');
        $this->ledger->record(
            workspaceId: (string) $workspace->id,
            planId: (string) $plan->id,
            gateway: PlanSubscription::GATEWAY_STRIPE,
            subscriptionId: $subId,
            status: $status,
            currentPeriodEnd: isset($obj['current_period_end'])
                ? (new \DateTimeImmutable('@'.(int) $obj['current_period_end']))
                : null,
            cancelAtPeriodEnd: (bool) ($obj['cancel_at_period_end'] ?? false),
        );
    }

    private function revertToFreePlan(array $obj): void
    {
        $stripeCustomerId = (string) ($obj['customer'] ?? '');
        if ($stripeCustomerId === '') {
            return;
        }

        $workspace = Workspace::query()->where('stripe_id', $stripeCustomerId)->first();
        if ($workspace === null) {
            return;
        }

        $subId = (string) ($obj['id'] ?? '');
        $this->ledger->record(
            workspaceId: (string) $workspace->id,
            planId: null,
            gateway: PlanSubscription::GATEWAY_STRIPE,
            subscriptionId: $subId,
            status: 'canceled',
        );

        $free = Plan::query()->where('slug', 'free')->first();
        if ($free === null) {
            return;
        }

        $workspace->forceFill(['plan_id' => $free->id])->save();
    }
}
