<?php

namespace App\Services\Checkout;

use App\Models\CheckoutSession;
use App\Models\Package;
use App\Models\Tenant;
use App\Models\TenantPackage;
use App\Services\Coupons\CouponRedemptionService;
use App\Services\GiftVouchers\GiftVoucherService;
use App\Services\Paddle\PaddleSubscriptionService;
use Carbon\Carbon;
use Illuminate\Support\Arr;
use Illuminate\Support\Facades\Cache;
use Illuminate\Support\Facades\Log;
use Illuminate\Support\Str;

class CheckoutWebhookService
{
    public function __construct(
        private readonly CheckoutSessionService $sessions,
        private readonly CheckoutAssignmentService $assignment,
        private readonly PaddleSubscriptionService $paddleSubscriptions,
        private readonly CouponRedemptionService $couponRedemptions,
        private readonly GiftVoucherService $giftVouchers,
    ) {}

    public function handleStripeEvent(array $event): bool
    {
        $eventType = $event['type'] ?? null;
        $intent = $event['data']['object'] ?? null;

        if (! $eventType || ! is_array($intent)) {
            return false;
        }

        if (! str_starts_with($eventType, 'payment_intent.')) {
            return false;
        }

        $intentId = $intent['id'] ?? null;

        if (! $intentId) {
            return false;
        }

        $session = $this->locateStripeSession($intent);

        if (! $session) {
            return false;
        }

        $lock = Cache::lock("checkout:webhook:stripe:{$intentId}", 30);

        if (! $lock->get()) {
            Log::info('[CheckoutWebhook] Stripe intent lock busy', [
                'intent_id' => $intentId,
                'session_id' => $session->id,
            ]);

            return true;
        }

        try {
            $session->forceFill([
                'stripe_payment_intent_id' => $session->stripe_payment_intent_id ?: $intentId,
                'provider' => CheckoutSession::PROVIDER_STRIPE,
            ])->save();

            $metadata = [
                'stripe_last_event' => $eventType,
                'stripe_last_event_id' => $event['id'] ?? null,
                'stripe_intent_status' => $intent['status'] ?? null,
                'stripe_last_update_at' => now()->toIso8601String(),
            ];

            $this->mergeProviderMetadata($session, $metadata);

            return $this->applyStripeIntent($session, $eventType, $intent);
        } finally {
            $lock->release();
        }
    }

    public function handlePaddleEvent(array $event): bool
    {
        $eventType = $event['event_type'] ?? null;
        $data = $event['data'] ?? [];

        if (! $eventType || ! is_array($data)) {
            return false;
        }

        if (Str::startsWith($eventType, 'subscription.')) {
            return $this->handlePaddleSubscriptionEvent($eventType, $data);
        }

        if ($this->isGiftVoucherEvent($data)) {
            if ($eventType === 'transaction.completed') {
                $this->giftVouchers->issueFromPaddle($data);

                return true;
            }

            return in_array($eventType, ['transaction.processing', 'transaction.created', 'transaction.failed', 'transaction.cancelled'], true);
        }

        $session = $this->locatePaddleSession($data);

        if (! $session) {
            Log::info('[CheckoutWebhook] Paddle session not resolved', [
                'event_type' => $eventType,
                'transaction_id' => $data['id'] ?? null,
            ]);

            return false;
        }

        $transactionId = $data['id'] ?? $data['transaction_id'] ?? null;
        $lockKey = 'checkout:webhook:paddle:'.($transactionId ?: $session->id);
        $lock = Cache::lock($lockKey, 30);

        if (! $lock->get()) {
            Log::info('[CheckoutWebhook] Paddle lock busy', [
                'transaction_id' => $transactionId,
                'session_id' => $session->id,
            ]);

            return true;
        }

        try {
            if ($transactionId) {
                $session->forceFill([
                    'paddle_transaction_id' => $transactionId,
                    'provider' => CheckoutSession::PROVIDER_PADDLE,
                ])->save();
            } elseif ($session->provider !== CheckoutSession::PROVIDER_PADDLE) {
                $session->forceFill(['provider' => CheckoutSession::PROVIDER_PADDLE])->save();
            }

            $metadata = [
                'paddle_last_event' => $eventType,
                'paddle_transaction_id' => $transactionId,
                'paddle_status' => $data['status'] ?? null,
                'paddle_last_update_at' => now()->toIso8601String(),
            ];

            if (! empty($data['checkout_id'])) {
                $metadata['paddle_checkout_id'] = $data['checkout_id'];
            }

            $this->mergeProviderMetadata($session, $metadata);

            return $this->applyPaddleEvent($session, $eventType, $data);
        } finally {
            $lock->release();
        }
    }

    protected function applyStripeIntent(CheckoutSession $session, string $eventType, array $intent): bool
    {
        switch ($eventType) {
            case 'payment_intent.processing':
            case 'payment_intent.amount_capturable_updated':
                $this->sessions->markProcessing($session, [
                    'stripe_intent_status' => $intent['status'] ?? null,
                ]);

                return true;

            case 'payment_intent.requires_action':
                $reason = $intent['next_action']['type'] ?? 'requires_action';
                $this->sessions->markRequiresCustomerAction($session, $reason);

                return true;

            case 'payment_intent.payment_failed':
                $failure = $intent['last_payment_error']['message'] ?? 'payment_failed';
                $this->sessions->markFailed($session, $failure);

                return true;

            case 'payment_intent.succeeded':
                if ($session->status !== CheckoutSession::STATUS_COMPLETED) {
                    $this->sessions->markProcessing($session, [
                        'stripe_intent_status' => $intent['status'] ?? null,
                    ]);

                    $this->assignment->finalise($session, [
                        'source' => 'stripe_webhook',
                        'stripe_payment_intent_id' => $intent['id'] ?? null,
                        'stripe_charge_id' => $this->extractStripeChargeId($intent),
                    ]);

                    $this->sessions->markCompleted($session, now());
                }

                return true;

            default:
                return false;
        }
    }

    protected function applyPaddleEvent(CheckoutSession $session, string $eventType, array $data): bool
    {
        $status = strtolower((string) ($data['status'] ?? ''));

        switch ($eventType) {
            case 'transaction.created':
            case 'transaction.processing':
                $this->sessions->markProcessing($session, [
                    'paddle_status' => $status ?: null,
                ]);

                return true;

            case 'transaction.completed':
                if ($session->status !== CheckoutSession::STATUS_COMPLETED) {
                    $this->sessions->markProcessing($session, [
                        'paddle_status' => $status ?: 'completed',
                    ]);

                    $this->assignment->finalise($session, [
                        'source' => 'paddle_webhook',
                        'provider' => CheckoutSession::PROVIDER_PADDLE,
                        'provider_reference' => $data['id'] ?? null,
                        'payload' => $data,
                    ]);

                    $this->sessions->markCompleted($session, now());
                    $this->couponRedemptions->recordSuccess($session, $data);
                }

                return true;

            case 'transaction.failed':
            case 'transaction.cancelled':
                $reason = $status ?: ($eventType === 'transaction.failed' ? 'paddle_failed' : 'paddle_cancelled');
                $this->sessions->markFailed($session, $reason);
                $this->couponRedemptions->recordFailure($session, $reason);

                return true;

            default:
                return false;
        }
    }

    protected function handlePaddleSubscriptionEvent(string $eventType, array $data): bool
    {
        $subscriptionId = $data['id'] ?? null;

        if (! $subscriptionId) {
            return false;
        }

        $metadata = $data['metadata'] ?? [];
        $tenant = $this->resolveTenantFromSubscription($data, $metadata, $subscriptionId);

        if (! $tenant) {
            Log::info('[CheckoutWebhook] Paddle subscription tenant not resolved', [
                'subscription_id' => $subscriptionId,
            ]);

            return false;
        }

        $package = $this->resolvePackageFromSubscription($data, $metadata, $subscriptionId);

        if (! $package) {
            Log::info('[CheckoutWebhook] Paddle subscription package not resolved', [
                'subscription_id' => $subscriptionId,
            ]);

            return false;
        }

        $status = strtolower((string) ($data['status'] ?? ''));
        $expiresAt = $this->resolveSubscriptionExpiry($data);
        $startedAt = $this->resolveSubscriptionStart($data);

        $tenantPackage = TenantPackage::firstOrNew([
            'tenant_id' => $tenant->id,
            'package_id' => $package->id,
        ]);

        $tenantPackage->fill([
            'paddle_subscription_id' => $subscriptionId,
            'price' => $package->price,
        ]);

        $tenantPackage->expires_at = $expiresAt ?? $tenantPackage->expires_at ?? $startedAt?->copy()->addYear();
        $tenantPackage->purchased_at = $tenantPackage->purchased_at
            ?? $tenant->purchases()->where('package_id', $package->id)->latest('purchased_at')->value('purchased_at')
            ?? $startedAt;

        $tenantPackage->active = $this->isSubscriptionActive($status);
        $tenantPackage->save();

        if ($eventType === 'subscription.cancelled' || $eventType === 'subscription.paused') {
            $tenantPackage->forceFill(['active' => false])->save();
        }

        $tenant->forceFill([
            'subscription_status' => $this->mapSubscriptionStatus($status),
            'subscription_expires_at' => $expiresAt,
            'paddle_customer_id' => $tenant->paddle_customer_id ?: ($data['customer_id'] ?? null),
        ])->save();

        Log::info('[CheckoutWebhook] Paddle subscription event processed', [
            'tenant_id' => $tenant->id,
            'package_id' => $package->id,
            'subscription_id' => $subscriptionId,
            'event_type' => $eventType,
            'status' => $status,
        ]);

        return true;
    }

    protected function resolveTenantFromSubscription(array $data, array $metadata, string $subscriptionId): ?Tenant
    {
        if (isset($metadata['tenant_id'])) {
            $tenant = Tenant::find((int) $metadata['tenant_id']);
            if ($tenant) {
                return $tenant;
            }
        }

        $customerId = $data['customer_id'] ?? null;

        if ($customerId) {
            $tenant = Tenant::where('paddle_customer_id', $customerId)->first();
            if ($tenant) {
                return $tenant;
            }
        }

        $subscription = $this->paddleSubscriptions->retrieve($subscriptionId);
        $customerId = Arr::get($subscription, 'data.customer_id');

        if ($customerId) {
            return Tenant::where('paddle_customer_id', $customerId)->first();
        }

        return null;
    }

    protected function resolvePackageFromSubscription(array $data, array $metadata, string $subscriptionId): ?Package
    {
        if (isset($metadata['package_id'])) {
            $package = Package::withTrashed()->find((int) $metadata['package_id']);
            if ($package) {
                return $package;
            }
        }

        $priceId = Arr::get($data, 'items.0.price_id') ?? Arr::get($data, 'items.0.price.id');

        if ($priceId) {
            $package = Package::withTrashed()->where('paddle_price_id', $priceId)->first();
            if ($package) {
                return $package;
            }
        }

        $subscription = $this->paddleSubscriptions->retrieve($subscriptionId);
        $priceId = Arr::get($subscription, 'data.items.0.price_id') ?? Arr::get($subscription, 'data.items.0.price.id');

        if ($priceId) {
            return Package::withTrashed()->where('paddle_price_id', $priceId)->first();
        }

        return null;
    }

    protected function resolveSubscriptionExpiry(array $data): ?Carbon
    {
        $nextBilling = Arr::get($data, 'next_billing_date') ?? Arr::get($data, 'next_payment_date');

        if ($nextBilling) {
            return Carbon::parse($nextBilling);
        }

        $endsAt = Arr::get($data, 'billing_period_ends_at') ?? Arr::get($data, 'pays_out_at');

        return $endsAt ? Carbon::parse($endsAt) : null;
    }

    protected function resolveSubscriptionStart(array $data): Carbon
    {
        $created = Arr::get($data, 'created_at') ?? Arr::get($data, 'activated_at');

        return $created ? Carbon::parse($created) : now();
    }

    protected function isSubscriptionActive(string $status): bool
    {
        return in_array($status, ['active', 'trialing'], true);
    }

    protected function mapSubscriptionStatus(string $status): string
    {
        return match ($status) {
            'active', 'trialing' => 'active',
            'paused' => 'suspended',
            'cancelled', 'past_due', 'halted' => 'expired',
            default => 'free',
        };
    }

    protected function mergeProviderMetadata(CheckoutSession $session, array $data): void
    {
        $session->provider_metadata = array_merge($session->provider_metadata ?? [], $data);
        $session->save();
    }

    protected function locateStripeSession(array $intent): ?CheckoutSession
    {
        $intentId = $intent['id'] ?? null;

        if ($intentId) {
            $session = CheckoutSession::query()
                ->where('stripe_payment_intent_id', $intentId)
                ->first();

            if ($session) {
                return $session;
            }
        }

        $metadata = $intent['metadata'] ?? [];
        $sessionId = $metadata['checkout_session_id'] ?? null;

        if ($sessionId) {
            return CheckoutSession::find($sessionId);
        }

        return null;
    }

    protected function isGiftVoucherEvent(array $data): bool
    {
        $metadata = $data['metadata'] ?? [];

        $type = is_array($metadata) ? ($metadata['type'] ?? $metadata['kind'] ?? $metadata['category'] ?? null) : null;

        if ($type && in_array(strtolower($type), ['gift_card', 'gift_voucher'], true)) {
            return true;
        }

        $priceId = $data['price_id'] ?? Arr::get($metadata, 'paddle_price_id');
        $tiers = collect(config('gift-vouchers.tiers', []))
            ->pluck('paddle_price_id')
            ->filter()
            ->all();

        return $priceId && in_array($priceId, $tiers, true);
    }

    protected function locatePaddleSession(array $data): ?CheckoutSession
    {
        $metadata = $data['metadata'] ?? [];

        if (is_array($metadata)) {
            $sessionId = $metadata['checkout_session_id'] ?? null;

            if ($sessionId && $session = CheckoutSession::find($sessionId)) {
                return $session;
            }

            $tenantId = $metadata['tenant_id'] ?? null;
            $packageId = $metadata['package_id'] ?? null;

            if ($tenantId && $packageId) {
                $session = CheckoutSession::query()
                    ->where('tenant_id', $tenantId)
                    ->where('package_id', $packageId)
                    ->whereNotIn('status', [CheckoutSession::STATUS_COMPLETED, CheckoutSession::STATUS_CANCELLED])
                    ->latest()
                    ->first();

                if ($session) {
                    return $session;
                }
            }
        }

        $checkoutId = $data['checkout_id'] ?? Arr::get($data, 'details.checkout_id');

        if ($checkoutId) {
            return CheckoutSession::query()
                ->where('provider_metadata->paddle_checkout_id', $checkoutId)
                ->first();
        }

        return null;
    }

    protected function extractStripeChargeId(array $intent): ?string
    {
        $charges = $intent['charges']['data'] ?? null;
        if (is_array($charges) && count($charges) > 0) {
            return $charges[0]['id'] ?? null;
        }

        return null;
    }
}
