<?php

namespace App\Http\Controllers;

use App\Models\Payment;
use App\Models\PaymentGateway;
use App\Models\Coupon;
use App\Models\SiteSetting;
use App\Models\SubscriptionPlan;
use App\Models\UserSubscription;
use App\Services\Payments\PaymentManagerService;
use Illuminate\Http\RedirectResponse;
use Illuminate\Http\Request;
use Illuminate\Support\Facades\DB;
use Illuminate\Support\Str;
use Illuminate\View\View;

class PaymentController extends Controller
{
    public function initialize(Request $request, SubscriptionPlan $plan, PaymentManagerService $payments): RedirectResponse
    {
        if (! SiteSetting::paymentModeEnabled()) {
            return redirect()
                ->route('pricing')
                ->with('error', 'Subscription payment is not active yet.');
        }

        if (! $plan->is_active) {
            return redirect()
                ->route('pricing')
                ->with('error', 'This subscription plan is not available.');
        }

        $gateway = $payments->activeConfiguredGateway();

        if (! $gateway) {
            return redirect()
                ->route('pricing')
                ->with('error', 'Payment gateway is not available right now.');
        }

        $coupon = null;
        $discountAmount = 0;

        if (filled($request->input('coupon_code'))) {
            $coupon = Coupon::query()
                ->where('code', strtoupper(trim($request->input('coupon_code'))))
                ->first();

            if (! $coupon || ! $coupon->isActive()) {
                return redirect()
                    ->route('pricing')
                    ->with('error', 'The coupon code is invalid, expired, or fully used.');
            }

            $discountAmount = $coupon->discountFor((float) $plan->price);
        }

        $amountDue = max(round((float) $plan->price - $discountAmount, 2), 0);

        if ($amountDue <= 0) {
            return redirect()
                ->route('pricing')
                ->with('error', 'This coupon reduces the price to zero. Free coupon activation will be added later by admin approval.');
        }

        $payment = Payment::create([
            'user_id' => $request->user()->id,
            'subscription_plan_id' => $plan->id,
            'payment_gateway_id' => $gateway->id,
            'coupon_id' => $coupon?->id,
            'reference' => $this->generateReference(),
            'status' => Payment::STATUS_PENDING,
            'amount' => $amountDue,
            'discount_amount' => $discountAmount,
            'currency' => strtoupper($plan->currency),
            'metadata' => [
                'plan_name' => $plan->name,
                'billing_period' => $plan->billing_period,
                'gateway_slug' => $gateway->slug,
                'gateway_mode' => $gateway->is_test_mode ? 'test' : 'live',
                'original_amount' => (float) $plan->price,
                'coupon_code' => $coupon?->code,
            ],
        ]);

        $result = $payments->initializePayment($request->user(), $plan, $payment);

        if (! ($result['success'] ?? false)) {
            $payment->update([
                'status' => Payment::STATUS_FAILED,
                'metadata' => array_merge($payment->metadata ?? [], [
                    'initialization_error' => $result['message'] ?? 'Payment initialization failed.',
                    'initialization_response' => $result['response'] ?? null,
                ]),
            ]);

            return redirect()
                ->route('payments.failed', $payment)
                ->with('error', $result['message'] ?? 'Unable to initialize payment.');
        }

        $payment->update([
            'gateway_reference' => $result['access_code'] ?? null,
            'metadata' => array_merge($payment->metadata ?? [], [
                'authorization_url_created' => true,
            ]),
        ]);

        return redirect()->away($result['authorization_url']);
    }

    public function paystackCallback(Request $request, PaymentManagerService $payments): RedirectResponse
    {
        $reference = $request->query('reference');

        if (blank($reference)) {
            return redirect()
                ->route('payments.failed.reference')
                ->with('error', 'Payment reference was missing from the Paystack callback.');
        }

        $payment = Payment::query()
            ->with(['gateway', 'plan', 'user'])
            ->where('reference', $reference)
            ->first();

        if (! $payment || ! $payment->gateway || ! $payment->plan) {
            return redirect()
                ->route('payments.failed.reference')
                ->with('error', 'Payment record was not found.');
        }

        if ($payment->isSuccessful()) {
            return redirect()->route('payments.success', $payment);
        }

        $result = $payments->verifyPayment($payment->gateway, $payment->reference);

        if (! ($result['success'] ?? false)) {
            $payment->update([
                'status' => Payment::STATUS_FAILED,
                'metadata' => array_merge($payment->metadata ?? [], [
                    'verification_error' => $result['message'] ?? 'Payment verification failed.',
                    'verification_response' => $result['response'] ?? null,
                ]),
            ]);

            return redirect()
                ->route('payments.failed', $payment)
                ->with('error', $result['message'] ?? 'Payment verification failed.');
        }

        if (
            isset($result['amount'])
            && abs((float) $result['amount'] - (float) $payment->amount) > 0.01
        ) {
            $payment->update([
                'status' => Payment::STATUS_FAILED,
                'metadata' => array_merge($payment->metadata ?? [], [
                    'verification_error' => 'Verified amount did not match the expected payment amount.',
                    'verification_response' => $result['response'] ?? null,
                ]),
            ]);

            return redirect()
                ->route('payments.failed', $payment)
                ->with('error', 'Verified amount did not match the expected payment amount.');
        }

        $this->activateSubscription($payment, $result);

        return redirect()->route('payments.success', $payment->fresh());
    }

    public function gatewayCallback(string $gateway): RedirectResponse
    {
        return redirect()
            ->route('payments.failed.reference')
            ->with('error', ucfirst($gateway).' callback route is prepared, but real verification for this gateway is not connected yet.');
    }

    public function webhook(Request $request, string $gateway, PaymentManagerService $payments): array
    {
        $paymentGateway = PaymentGateway::query()
            ->where('slug', $gateway)
            ->active()
            ->firstOrFail();

        $result = $payments->handleWebhook($paymentGateway, $request);

        if (($result['success'] ?? false) && filled($result['response']['data']['reference'] ?? null)) {
            $payment = Payment::query()
                ->where('reference', $result['response']['data']['reference'])
                ->where('status', '!=', Payment::STATUS_SUCCESSFUL)
                ->first();

            if ($payment) {
                $this->activateSubscription($payment->load(['plan', 'user']), $result);
            }
        }

        return [
            'success' => (bool) ($result['success'] ?? false),
            'message' => $result['message'] ?? 'Webhook received.',
        ];
    }

    public function success(Payment $payment): View
    {
        abort_unless($payment->user_id === auth()->id(), 403);

        return view('payments.success', [
            'payment' => $payment->load(['plan', 'subscription', 'gateway']),
        ]);
    }

    public function failed(?Payment $payment = null): View
    {
        if ($payment) {
            abort_unless($payment->user_id === auth()->id(), 403);
            $payment->load(['plan', 'gateway']);
        }

        return view('payments.failed', [
            'payment' => $payment,
        ]);
    }

    private function activateSubscription(Payment $payment, array $verification): void
    {
        DB::transaction(function () use ($payment, $verification): void {
            $payment = Payment::query()
                ->with(['plan', 'user'])
                ->whereKey($payment->id)
                ->lockForUpdate()
                ->firstOrFail();

            if ($payment->isSuccessful()) {
                return;
            }

            $now = now();
            $plan = $payment->plan;

            UserSubscription::query()
                ->where('user_id', $payment->user_id)
                ->where('status', UserSubscription::STATUS_ACTIVE)
                ->update([
                    'status' => UserSubscription::STATUS_CANCELLED,
                    'cancelled_at' => $now,
                ]);

            $subscription = UserSubscription::create([
                'user_id' => $payment->user_id,
                'subscription_plan_id' => $plan->id,
                'status' => UserSubscription::STATUS_ACTIVE,
                'starts_at' => $now,
                'ends_at' => $now->copy()->addDays($plan->duration_days),
                'amount_paid' => $verification['amount'] ?? $payment->amount,
                'currency' => strtoupper($verification['currency'] ?? $payment->currency),
                'payment_reference' => $payment->reference,
                'notes' => 'Activated after verified payment.',
            ]);

            $payment->update([
                'user_subscription_id' => $subscription->id,
                'gateway_reference' => $verification['gateway_reference'] ?? $payment->gateway_reference,
                'status' => Payment::STATUS_SUCCESSFUL,
                'amount' => $verification['amount'] ?? $payment->amount,
                'currency' => strtoupper($verification['currency'] ?? $payment->currency),
                'paid_at' => $verification['paid_at'] ?? $now,
                'metadata' => array_merge($payment->metadata ?? [], [
                    'verification_response' => $verification['response'] ?? null,
                ]),
            ]);

            if ($payment->coupon_id) {
                Coupon::query()
                    ->whereKey($payment->coupon_id)
                    ->increment('used_count');
            }
        });
    }

    private function generateReference(): string
    {
        do {
            $reference = 'VZT-'.now()->format('YmdHis').'-'.Str::upper(Str::random(8));
        } while (Payment::where('reference', $reference)->exists());

        return $reference;
    }
}
