<?php

namespace App\Http\Controllers\Billing;

use App\Http\Controllers\Controller;
use App\Models\Order;
use App\Models\Payment;
use App\Models\PricingPlan;
use App\Models\Subscription;
use App\Models\User;
use App\Models\WebhookEvent;
use App\Services\Billing\SubscriptionTransitionService;
use App\Services\Keywords\KeywordQuotaService;
use App\Services\System\SettingsService;
use Illuminate\Http\JsonResponse;
use Illuminate\Http\RedirectResponse;
use Illuminate\Http\Request;
use Illuminate\Support\Facades\Http;
use Illuminate\Support\Facades\Log;
use Illuminate\Support\Str;

class PayPalController extends Controller
{
    public function __construct(
        private readonly SubscriptionTransitionService $subscriptionTransition,
        private readonly KeywordQuotaService $keywordQuota
    ) {}

    public function createSubscription(Request $request): RedirectResponse|JsonResponse
    {
        $data = $request->validate([
            'plan_code' => 'required|string|in:personal_monthly,personal_annual,personal_lifetime',
        ]);

        $user = $request->user();
        if (! $user) {
            return response()->json(['error' => 'auth_required'], 401);
        }

        if ($this->hasActiveLifetimeSubscription((int) $user->id)) {
            return response()->json(['error' => 'lifetime_active'], 409);
        }

        if ($cooldownPayload = $this->pendingCheckoutCooldown((int) $user->id)) {
            return response()->json($cooldownPayload, 409);
        }

        $mode = $this->resolvePaypalMode($this->billingMode());
        if (! $this->paypalConfigured($mode)) {
            return response()->json(['error' => 'paypal_not_configured'], 422);
        }

        if ($data['plan_code'] === 'personal_lifetime') {
            return $this->createLifetimeOrder($request, $mode);
        }

        $planId = $this->resolvePlanId($data['plan_code'], $mode);
        if (! $planId) {
            return response()->json(['error' => 'paypal_plan_missing'], 422);
        }

        $token = $this->getAccessToken($mode);
        if (! $token) {
            return response()->json(['error' => 'paypal_auth_failed'], 502);
        }

        $appUrl = rtrim(config('app.url'), '/');
        $payload = [
            'plan_id' => $planId,
            'subscriber' => [
                'name' => [
                    'given_name' => $user->name ?? 'User',
                    'surname' => 'Dewemoji',
                ],
                'email_address' => $user->email,
            ],
            'application_context' => [
                'brand_name' => 'Dewemoji',
                'locale' => 'en-US',
                'user_action' => 'SUBSCRIBE_NOW',
                'return_url' => $appUrl.'/billing/paypal/return?status=success',
                'cancel_url' => $appUrl.'/billing/paypal/return?status=cancel',
            ],
        ];

        $apiBase = config("dewemoji.billing.providers.paypal.{$mode}.api_base");
        $res = Http::withToken($token)
            ->timeout((int) config('dewemoji.billing.providers.paypal.timeout', 10))
            ->post(rtrim($apiBase, '/').'/v1/billing/subscriptions', $payload);

        $body = $res->json();
        $subscriptionId = $body['id'] ?? null;
        $approveUrl = collect($body['links'] ?? [])->firstWhere('rel', 'approve')['href'] ?? null;

        if (! $subscriptionId || ! $approveUrl) {
            if (! $res->ok()) {
                Log::warning('PayPal create subscription failed', [
                    'status' => $res->status(),
                    'body' => $res->body(),
                ]);
            } else {
                Log::warning('PayPal create subscription missing approve link', [
                    'status' => $res->status(),
                    'body' => $res->body(),
                ]);
            }

            return response()->json(['error' => 'paypal_invalid_response'], 502);
        }

        $amountUsd = $this->resolvePlanAmountUsd($data['plan_code']);

        $this->subscriptionTransition->cancelPendingForUser((int) $user->id);

        $order = Order::create([
            'user_id' => $user->id,
            'plan_code' => $data['plan_code'],
            'type' => 'subscription',
            'currency' => 'USD',
            'amount' => $amountUsd,
            'status' => 'pending',
            'provider' => 'paypal',
            'provider_ref' => $subscriptionId,
        ]);

        Payment::create([
            'user_id' => $user->id,
            'order_id' => $order->id,
            'provider' => 'paypal',
            'type' => 'subscription',
            'plan_code' => $data['plan_code'],
            'currency' => 'USD',
            'amount' => $amountUsd,
            'status' => 'pending',
            'provider_ref' => $subscriptionId,
            'raw_payload' => $body,
        ]);

        Subscription::firstOrCreate([
            'provider' => 'paypal',
            'provider_ref' => $subscriptionId,
        ], [
            'user_id' => $user->id,
            'plan' => $data['plan_code'],
            'status' => 'pending',
            'started_at' => now(),
        ]);

        return response()->json(['approve_url' => $approveUrl]);
    }

    public function return(Request $request): RedirectResponse
    {
        if ((string) $request->query('flow', '') === 'lifetime' && (string) $request->query('status', '') === 'success') {
            $token = trim((string) $request->query('token', ''));
            if ($token !== '') {
                $captured = $this->captureLifetimeOrder($token, $request->user()?->id);
                if (! $captured) {
                    return redirect()->route('dashboard.billing', ['status' => 'error']);
                }
            }
        }

        $status = (string) $request->query('status', 'success');

        return redirect()->route('dashboard.billing', ['status' => $status]);
    }

    public function webhook(Request $request): JsonResponse
    {
        $payload = $request->all();
        $eventId = (string) ($payload['id'] ?? '');
        $eventType = (string) ($payload['event_type'] ?? '');

        $mode = config('dewemoji.billing.mode', 'sandbox');
        $webhookId = config("dewemoji.billing.providers.paypal.webhook_ids.{$mode}");

        if ($webhookId) {
            $verified = $this->verifySignature($mode, $webhookId, $payload, $request);
            if (! $verified) {
                return response()->json(['error' => 'invalid_signature'], 401);
            }
        }

        $event = WebhookEvent::create([
            'provider' => 'paypal',
            'event_id' => $eventId ?: (string) Str::uuid(),
            'event_type' => $eventType,
            'status' => 'received',
            'payload' => $payload,
            'headers' => $request->headers->all(),
            'received_at' => now(),
        ]);

        try {
            $processed = $this->processPayPalEvent($payload);
            $event->update([
                'status' => $processed ? 'processed' : 'received',
                'processed_at' => $processed ? now() : null,
            ]);
        } catch (\Throwable $e) {
            $event->update([
                'status' => 'error',
                'error' => $e->getMessage(),
                'processed_at' => now(),
            ]);
        }

        return response()->json(['ok' => true]);
    }

    private function processPayPalEvent(array $payload): bool
    {
        $type = (string) ($payload['event_type'] ?? '');
        $resource = $payload['resource'] ?? [];
        if ($type === 'BILLING.SUBSCRIPTION.ACTIVATED') {
            $subscriptionId = (string) ($resource['id'] ?? $resource['subscription_id'] ?? '');
            if ($subscriptionId === '') {
                return false;
            }
            $resolvedPlan = 'personal_monthly';
            $sub = Subscription::firstOrNew([
                'provider' => 'paypal',
                'provider_ref' => $subscriptionId,
            ]);
            if (! $sub->user_id) {
                $order = Order::where('provider', 'paypal')->where('provider_ref', $subscriptionId)->first();
                if ($order) {
                    $sub->user_id = $order->user_id;
                    $resolvedPlan = (string) ($order->plan_code ?: $resolvedPlan);
                }
            }
            if (! empty($sub->plan)) {
                $resolvedPlan = (string) $sub->plan;
            }
            $sub->plan = $resolvedPlan;
            $sub->status = 'active';
            $sub->started_at = $sub->started_at ?? now();
            $sub->next_renewal_at = $resource['billing_info']['next_billing_time'] ?? null;
            $sub->save();

            if ($sub->user_id) {
                User::where('id', $sub->user_id)->update(['tier' => 'personal']);
                $this->keywordQuota->enforceForUser((int) $sub->user_id, 'personal');
                $this->subscriptionTransition->settleForActivatedPlan(
                    (int) $sub->user_id,
                    'paypal',
                    $subscriptionId,
                    $resolvedPlan
                );
            }

            Order::where('provider', 'paypal')->where('provider_ref', $subscriptionId)->update(['status' => 'paid']);
            Payment::where('provider', 'paypal')->where('provider_ref', $subscriptionId)->update(['status' => 'paid']);

            return true;
        }

        if (in_array($type, ['BILLING.SUBSCRIPTION.CANCELLED', 'BILLING.SUBSCRIPTION.SUSPENDED'], true)) {
            $subscriptionId = (string) ($resource['id'] ?? $resource['subscription_id'] ?? '');
            if ($subscriptionId === '') {
                return false;
            }
            $sub = Subscription::where('provider', 'paypal')->where('provider_ref', $subscriptionId)->first();
            if ($sub) {
                $sub->status = 'canceled';
                $sub->canceled_at = now();
                $sub->save();
            }

            return true;
        }

        if ($type === 'PAYMENT.CAPTURE.COMPLETED') {
            $orderId = (string) ($resource['supplementary_data']['related_ids']['order_id'] ?? '');
            if ($orderId === '') {
                return false;
            }

            return $this->markLifetimeOrderPaid($orderId, $payload);
        }

        return false;
    }

    private function resolvePlanId(string $planCode, string $mode): ?string
    {
        $plan = PricingPlan::where('code', $planCode)->first();
        if ($plan) {
            $meta = $plan->meta ?? [];
            $stored = $meta['paypal'][$mode]['plan']['id'] ?? null;
            if ($stored) {
                return $stored;
            }
        }

        return config("dewemoji.billing.providers.paypal.plan_ids.{$mode}.{$planCode}") ?: null;
    }

    /**
     * @return array<string,mixed>|null
     */
    private function pendingCheckoutCooldown(int $userId): ?array
    {
        $cooldown = (int) config('dewemoji.billing.pending_cooldown_seconds', 120);
        if ($cooldown <= 0) {
            return null;
        }

        $pending = Payment::query()
            ->where('user_id', $userId)
            ->where('status', 'pending')
            ->orderByDesc('id')
            ->first();
        if (! $pending || ! $pending->created_at) {
            return null;
        }

        $age = max(0, now()->getTimestamp() - $pending->created_at->getTimestamp());
        $remaining = $cooldown - $age;
        if ($remaining <= 0) {
            return null;
        }

        return [
            'error' => 'pending_cooldown',
            'retry_after' => (int) $remaining,
            'pending_payment_id' => (int) $pending->id,
            'provider' => (string) ($pending->provider ?? ''),
            'created_at' => $pending->created_at->toIso8601String(),
        ];
    }

    private function resolvePlanAmountUsd(string $planCode): int
    {
        $plan = PricingPlan::where('code', $planCode)->first();
        if (! $plan) {
            return 0;
        }
        $rate = (int) config('dewemoji.pricing.usd_rate', 15000);
        if ($rate <= 0) {
            return 0;
        }

        return (int) round($plan->amount / $rate);
    }

    private function resolvePlanAmountUsdValue(string $planCode): string
    {
        $plan = PricingPlan::where('code', $planCode)->first();
        if (! $plan) {
            return '0.00';
        }
        $rate = (int) config('dewemoji.pricing.usd_rate', 15000);
        if ($rate <= 0) {
            return '0.00';
        }

        return number_format($plan->amount / $rate, 2, '.', '');
    }

    private function getAccessToken(string $mode): ?string
    {
        $clientId = config("dewemoji.billing.providers.paypal.{$mode}.client_id");
        $clientSecret = config("dewemoji.billing.providers.paypal.{$mode}.client_secret");
        $apiBase = config("dewemoji.billing.providers.paypal.{$mode}.api_base");

        if (! $clientId || ! $clientSecret || ! $apiBase) {
            return null;
        }

        $res = Http::asForm()
            ->withBasicAuth($clientId, $clientSecret)
            ->timeout((int) config('dewemoji.billing.providers.paypal.timeout', 10))
            ->post(rtrim($apiBase, '/').'/v1/oauth2/token', [
                'grant_type' => 'client_credentials',
            ]);

        if (! $res->successful()) {
            return null;
        }

        return $res->json('access_token');
    }

    private function paypalConfigured(string $mode): bool
    {
        $enabled = (bool) config('dewemoji.billing.providers.paypal.enabled', false);
        $clientId = config("dewemoji.billing.providers.paypal.{$mode}.client_id");
        $clientSecret = config("dewemoji.billing.providers.paypal.{$mode}.client_secret");
        $apiBase = config("dewemoji.billing.providers.paypal.{$mode}.api_base");

        return $enabled && $clientId && $clientSecret && $apiBase;
    }

    private function billingMode(): string
    {
        $settings = app(SettingsService::class);

        return (string) ($settings->get('billing_mode', config('dewemoji.billing.mode', 'sandbox')) ?: 'sandbox');
    }

    private function resolvePaypalMode(string $preferred): string
    {
        if ($this->paypalConfigured($preferred)) {
            return $preferred;
        }

        $fallback = $preferred === 'live' ? 'sandbox' : 'live';
        if ($this->paypalConfigured($fallback)) {
            return $fallback;
        }

        return $preferred;
    }

    private function verifySignature(string $mode, string $webhookId, array $payload, Request $request): bool
    {
        $token = $this->getAccessToken($mode);
        if (! $token) {
            return false;
        }

        $apiBase = config("dewemoji.billing.providers.paypal.{$mode}.api_base");
        $verifyPayload = [
            'auth_algo' => $request->header('paypal-auth-algo'),
            'cert_url' => $request->header('paypal-cert-url'),
            'transmission_id' => $request->header('paypal-transmission-id'),
            'transmission_sig' => $request->header('paypal-transmission-sig'),
            'transmission_time' => $request->header('paypal-transmission-time'),
            'webhook_id' => $webhookId,
            'webhook_event' => $payload,
        ];

        if (collect($verifyPayload)->contains(fn ($v) => empty($v))) {
            return false;
        }

        $res = Http::withToken($token)
            ->timeout((int) config('dewemoji.billing.providers.paypal.timeout', 10))
            ->post(rtrim($apiBase, '/').'/v1/notifications/verify-webhook-signature', $verifyPayload);

        return $res->ok() && $res->json('verification_status') === 'SUCCESS';
    }

    private function createLifetimeOrder(Request $request, string $mode): JsonResponse
    {
        $user = $request->user();
        if (! $user) {
            return response()->json(['error' => 'auth_required'], 401);
        }

        $token = $this->getAccessToken($mode);
        if (! $token) {
            return response()->json(['error' => 'paypal_auth_failed'], 502);
        }

        $amountUsd = $this->resolvePlanAmountUsd('personal_lifetime');
        $amountUsdValue = $this->resolvePlanAmountUsdValue('personal_lifetime');
        if ($amountUsd <= 0 || (float) $amountUsdValue <= 0) {
            return response()->json(['error' => 'invalid_plan_amount'], 422);
        }

        $appUrl = rtrim(config('app.url'), '/');
        $payload = [
            'intent' => 'CAPTURE',
            'purchase_units' => [
                [
                    'reference_id' => 'dewemoji-personal-lifetime',
                    'description' => 'Dewemoji Personal Lifetime',
                    'amount' => [
                        'currency_code' => 'USD',
                        'value' => $amountUsdValue,
                    ],
                ],
            ],
            'application_context' => [
                'brand_name' => 'Dewemoji',
                'locale' => 'en-US',
                'return_url' => $appUrl.'/billing/paypal/return?flow=lifetime&status=success',
                'cancel_url' => $appUrl.'/billing/paypal/return?flow=lifetime&status=cancel',
            ],
        ];

        $apiBase = config("dewemoji.billing.providers.paypal.{$mode}.api_base");
        $res = Http::withToken($token)
            ->timeout((int) config('dewemoji.billing.providers.paypal.timeout', 10))
            ->post(rtrim((string) $apiBase, '/').'/v2/checkout/orders', $payload);

        $body = $res->json();
        $orderId = (string) ($body['id'] ?? '');
        $approveUrl = collect($body['links'] ?? [])->firstWhere('rel', 'approve')['href'] ?? null;
        if ($orderId === '' || ! $approveUrl) {
            Log::warning('PayPal create lifetime order failed', [
                'status' => $res->status(),
                'body' => $res->body(),
            ]);

            return response()->json(['error' => 'paypal_invalid_response'], 502);
        }

        $this->subscriptionTransition->cancelPendingForUser((int) $user->id);

        $order = Order::create([
            'user_id' => $user->id,
            'plan_code' => 'personal_lifetime',
            'type' => 'one_time',
            'currency' => 'USD',
            'amount' => $amountUsd,
            'status' => 'pending',
            'provider' => 'paypal',
            'provider_ref' => $orderId,
        ]);

        Payment::create([
            'user_id' => $user->id,
            'order_id' => $order->id,
            'provider' => 'paypal',
            'type' => 'one_time',
            'plan_code' => 'personal_lifetime',
            'currency' => 'USD',
            'amount' => $amountUsd,
            'status' => 'pending',
            'provider_ref' => $orderId,
            'raw_payload' => $body,
        ]);

        return response()->json(['approve_url' => $approveUrl]);
    }

    private function captureLifetimeOrder(string $orderId, ?int $userId = null): bool
    {
        $mode = $this->resolvePaypalMode($this->billingMode());
        $token = $this->getAccessToken($mode);
        if (! $token) {
            return false;
        }

        $order = Order::where('provider', 'paypal')
            ->where('provider_ref', $orderId)
            ->where('type', 'one_time')
            ->first();
        if (! $order) {
            return false;
        }
        if ($userId !== null && (int) $order->user_id !== $userId) {
            return false;
        }
        if ($order->status === 'paid') {
            return true;
        }

        $apiBase = config("dewemoji.billing.providers.paypal.{$mode}.api_base");
        $res = Http::withToken($token)
            ->timeout((int) config('dewemoji.billing.providers.paypal.timeout', 10))
            ->post(rtrim((string) $apiBase, '/').'/v2/checkout/orders/'.$orderId.'/capture');

        if ($res->successful()) {
            return $this->markLifetimeOrderPaid($orderId, $res->json() ?? []);
        }

        $status = $res->status();
        if ($status === 422) {
            $check = Http::withToken($token)
                ->timeout((int) config('dewemoji.billing.providers.paypal.timeout', 10))
                ->get(rtrim((string) $apiBase, '/').'/v2/checkout/orders/'.$orderId);
            if ($check->successful() && strtoupper((string) $check->json('status')) === 'COMPLETED') {
                return $this->markLifetimeOrderPaid($orderId, $check->json() ?? []);
            }
        }

        Log::warning('PayPal lifetime capture failed', [
            'order_id' => $orderId,
            'status' => $status,
            'body' => $res->body(),
        ]);

        return false;
    }

    private function markLifetimeOrderPaid(string $orderId, array $rawPayload = []): bool
    {
        $order = Order::where('provider', 'paypal')
            ->where('provider_ref', $orderId)
            ->where('type', 'one_time')
            ->first();
        if (! $order) {
            return false;
        }

        $order->status = 'paid';
        $order->save();

        $payment = Payment::where('order_id', $order->id)
            ->where('provider', 'paypal')
            ->where('type', 'one_time')
            ->first();
        if ($payment) {
            $payment->status = 'paid';
            if (! empty($rawPayload)) {
                $payment->raw_payload = $rawPayload;
            }
            $payment->save();
        }

        Subscription::updateOrCreate([
            'provider' => 'paypal',
            'provider_ref' => $orderId,
        ], [
            'user_id' => $order->user_id,
            'plan' => 'personal_lifetime',
            'status' => 'active',
            'started_at' => now(),
            'expires_at' => null,
            'next_renewal_at' => null,
            'canceled_at' => null,
        ]);

        User::where('id', $order->user_id)->update(['tier' => 'personal']);
        $this->keywordQuota->enforceForUser((int) $order->user_id, 'personal');
        $this->subscriptionTransition->settleForActivatedPlan(
            (int) $order->user_id,
            'paypal',
            $orderId,
            'personal_lifetime'
        );

        return true;
    }

    private function hasActiveLifetimeSubscription(int $userId): bool
    {
        return Subscription::query()
            ->where('user_id', $userId)
            ->where('plan', 'personal_lifetime')
            ->where('status', 'active')
            ->where(function ($query) {
                $query->whereNull('expires_at')
                    ->orWhere('expires_at', '>', now());
            })
            ->exists();
    }
}
