<?php

namespace App\Http\Controllers;

use App\Models\Coupon;
use App\Models\Payment;
use App\Models\PaymentGateway;
use App\Models\Plan;
use App\Models\Subscription;
use App\Models\Workspace;
use App\Services\Payment\PaymentGatewayManager;
use Illuminate\Http\Request;
use Illuminate\Support\Facades\DB;
use Illuminate\Support\Facades\Log;
use Illuminate\Support\Str;
use Illuminate\View\View;

class CheckoutController extends Controller
{
    public function __construct(
        private readonly PaymentGatewayManager $gatewayManager,
    ) {}

    /**
     * Show the checkout page with currency/gateway selection.
     */
    public function show(Request $request, int $planId): View
    {
        $workspace = auth()->user()->activeWorkspace;
        $plan = Plan::where('is_active', true)->findOrFail($planId);

        if ($plan->isFree()) {
            return redirect()->route('settings.billing')
                ->with('error', 'Free plans do not require payment.');
        }

        $billingCycle = $request->query('cycle', 'monthly');
        $price = $billingCycle === 'yearly' ? $plan->yearly_price : $plan->monthly_price;

        // Load all active gateways ordered by sort_order
        $gateways = PaymentGateway::active()->orderBy('sort_order')->get();

        // Currency source: the `currencies` admin table is the single source
        // of truth. Previously this derived `availableCurrencies` from each
        // gateway's `supported_currencies` JSON — but a freshly-configured
        // gateway (Stripe) typically has that field empty, which collapsed
        // the dropdown to just USD and broke RON / EUR / etc. plans.
        //
        // Now: every active currency from the table is offered. Gateway
        // availability is filtered on the JS side based on whether the
        // gateway's `supported_currencies` is empty (= supports all) OR
        // includes the selected code.
        $currencies = \App\Models\Currency::where('is_active', true)
            ->orderBy('sort_order')->orderBy('code')->get()->keyBy('code');

        $availableCurrencies = $currencies->keys()->all();
        if (empty($availableCurrencies)) {
            // Truly fresh install with no currencies seeded — still don't
            // crash the page. Manufacturing a single USD row keeps the
            // existing Blade happy until admin opens Currencies and seeds.
            $availableCurrencies = ['USD'];
        }

        // Gateway → currency map used by the JS filter. Empty array means
        // "this gateway accepts every currency we show".
        $gatewayCurrencies = [];
        foreach ($gateways as $gw) {
            $gatewayCurrencies[$gw->slug] = $gw->supported_currencies ?? [];
        }

        // Default currency: workspace > user > platform default > USD.
        $defaultCurrency = \App\Models\Currency::getDefault()?->code
            ?? auth()->user()->currency_code
            ?? 'USD';

        // Symbol map for the dropdown labels.
        $currencySymbols = $currencies->mapWithKeys(fn ($c) => [$c->code => $c->symbol ?: $c->code])->all();
        foreach ($availableCurrencies as $code) {
            $currencySymbols[$code] ??= $code;
        }

        $couponCode = $request->query('coupon', '');

        return view('checkout.index', [
            'plan' => $plan,
            'price' => $price,
            'billingCycle' => $billingCycle,
            'gateways' => $gateways,
            'gatewayCurrencies' => $gatewayCurrencies,
            'defaultCurrency' => $defaultCurrency,
            'availableCurrencies' => $availableCurrencies,
            'currencySymbols' => $currencySymbols,
            'currencies' => $currencies,
            'workspace' => $workspace,
            'couponCode' => $couponCode,
        ]);
    }

    /**
     * Process the checkout — create payment record and initiate gateway.
     */
    public function process(Request $request, int $planId)
    {
        $request->validate([
            'gateway' => 'required|string|max:50',
            'billing_cycle' => 'required|in:monthly,yearly',
            'currency' => 'nullable|string|max:10',
            'coupon_code' => 'nullable|string|max:50',
        ]);

        $workspace = auth()->user()->activeWorkspace;
        $plan = Plan::where('is_active', true)->findOrFail($planId);

        if ($plan->isFree()) {
            return redirect()->route('settings.billing')
                ->with('error', 'Free plans do not require payment.');
        }

        $billingCycle = $request->input('billing_cycle', 'monthly');
        $price = (float) ($billingCycle === 'yearly' ? $plan->yearly_price : $plan->monthly_price);
        $currency = strtoupper($request->input('currency', 'USD'));
        $gatewaySlug = $request->input('gateway');

        // Apply the same exchange-rate conversion that the checkout JS
        // performs for display. Without this the customer sees one amount
        // (e.g. "EUR 3.83") and gets billed a different one (raw plan
        // price treated as if it were already in the selected currency).
        //
        // exchange_rate semantics: the Currency model treats the row marked
        // `is_default = true` in the `currencies` table as the platform's
        // BASE currency (NOT a hardcoded USD). Plan prices are stored in
        // that base. Each non-default row's exchange_rate is "1 unit of
        // base = N units of this currency". The default row's rate is 1.0
        // (no conversion when the customer picks the same currency the
        // plan is priced in).
        //
        // Example: admin sets RON as default with rate 1.0, EUR with rate
        // 0.20, USD with rate 0.22. A plan priced "19" means 19 RON. A
        // customer picking EUR gets billed 19 * 0.20 = 3.80 EUR; picking
        // RON gets billed 19 RON; picking USD gets billed 4.18 USD.
        $currencyRow = \App\Models\Currency::where('code', $currency)->where('is_active', true)->first();
        $rate = ($currencyRow && (float) $currencyRow->exchange_rate > 0)
            ? (float) $currencyRow->exchange_rate
            : 1.0;
        $price = round($price * $rate, 2);

        // Validate gateway exists and is active
        $gateway = PaymentGateway::where('slug', $gatewaySlug)
            ->where('is_active', true)
            ->first();

        if (! $gateway) {
            return back()->with('error', 'Selected payment method is not available.');
        }

        // Validate currency is supported by this gateway
        $supportedCurrencies = $gateway->supported_currencies ?? [];
        if (! empty($supportedCurrencies) && ! in_array($currency, $supportedCurrencies)) {
            return back()->with('error', 'Selected currency is not supported by this payment method.');
        }

        // Coupon validation
        $coupon = null;
        $originalAmount = $price;
        $discountAmount = 0;
        $couponCode = $request->input('coupon_code');

        if ($couponCode) {
            $coupon = Coupon::where('code', strtoupper($couponCode))->first();

            if (!$coupon || !$coupon->isValid()) {
                return back()->with('error', 'Invalid or expired coupon code.');
            }

            if (!$coupon->isApplicableToPlan($plan->id)) {
                return back()->with('error', 'This coupon is not applicable to the selected plan.');
            }

            $discountAmount = $coupon->calculateDiscount($price);
            $price = $coupon->getDiscountedPrice($price);
        }

        // Tax breakdown — uses the plan's tax_rate if set, else system default.
        // Plan.tax_inclusive controls whether $price already INCLUDES tax (so we
        // back-derive subtotal) or excludes it (so we add tax on top).
        $taxEnabled = \App\Models\SystemSetting::enabled('tax_enabled', false);
        $taxRate = (float) ($plan->tax_rate ?? 0);
        if ($taxRate === 0.0 && $taxEnabled) {
            $taxRate = (float) \App\Models\SystemSetting::get('tax_default_rate', '0');
        }
        $taxInclusive = (bool) ($plan->tax_inclusive ?? false);

        $breakdown = \App\Services\Billing\InvoiceService::calculateTax($price, $taxRate, $taxInclusive);
        $finalAmount = $breakdown['total']; // what the gateway actually charges

        // Capture optional billing info from the checkout form so the invoice
        // can be issued without an extra step. Falls back to the user's
        // stored profile billing fields, then empty.
        $u = auth()->user();
        $billing = [
            'company'    => trim((string) $request->input('billing_company', $u->billing_company ?? '')),
            'tax_id'     => trim((string) $request->input('billing_tax_id', $u->billing_tax_id ?? '')),
            'vat_number' => trim((string) $request->input('billing_vat_number', $u->billing_vat_number ?? '')),
            'address'    => trim((string) $request->input('billing_address', $u->billing_address ?? '')),
            'email'      => trim((string) $request->input('billing_email', $u->email ?? '')),
        ];

        // Persist back to the user so the next checkout pre-fills. The
        // payments row keeps its own frozen snapshot for EU invoice
        // compliance — this is purely a UX convenience copy.
        $u->forceFill([
            'billing_company'    => $billing['company']    ?: null,
            'billing_tax_id'     => $billing['tax_id']     ?: null,
            'billing_vat_number' => $billing['vat_number'] ?: null,
            'billing_address'    => $billing['address']    ?: null,
            'billing_email'      => $billing['email']      ?: null,
        ])->save();

        // Prevent duplicate in-flight payments using DB-level locking to avoid race conditions.
        // lockForUpdate() ensures concurrent requests serialize on this row check.
        $payment = DB::transaction(function () use ($workspace, $finalAmount, $breakdown, $currency, $gatewaySlug, $plan, $billingCycle, $couponCode, $discountAmount, $originalAmount, $coupon, $billing) {
            $existingPending = Payment::where('workspace_id', $workspace->id)
                ->where('status', 'pending')
                ->where('created_at', '>=', now()->subMinutes(30))
                ->lockForUpdate()
                ->first();

            if ($existingPending) {
                return null; // signal duplicate
            }

            return Payment::create([
                'workspace_id' => $workspace->id,
                'amount' => $finalAmount,
                'currency' => $currency,
                'status' => 'pending',
                'gateway_slug' => $gatewaySlug,
                'description' => "{$plan->name} ({$billingCycle}) subscription",
                'coupon_code' => $couponCode ? strtoupper($couponCode) : null,
                'discount_amount' => $discountAmount > 0 ? $discountAmount : null,
                'original_amount' => $couponCode ? $originalAmount : null,
                // Tax snapshot — frozen at purchase time so later plan-rate
                // changes don't rewrite historical invoices.
                'subtotal_amount' => $breakdown['subtotal'],
                'tax_rate'        => $breakdown['tax_rate'],
                'tax_amount'      => $breakdown['tax_amount'],
                // Buyer billing snapshot (from checkout form / user profile).
                'billing_company'    => $billing['company']    ?: null,
                'billing_tax_id'     => $billing['tax_id']     ?: null,
                'billing_vat_number' => $billing['vat_number'] ?: null,
                'billing_address'    => $billing['address']    ?: null,
                'billing_email'      => $billing['email']      ?: null,
                'metadata' => [
                    'plan_id' => $plan->id,
                    'billing_cycle' => $billingCycle,
                    'user_id' => auth()->id(),
                ],
            ]);
        });

        // Increment coupon usage after successful payment creation
        if ($payment && $coupon) {
            $coupon->incrementUsage();
        }

        if (! $payment) {
            return back()->with('error', 'You already have a pending payment. Please wait for it to complete or try again later.');
        }

        // Store plan info in session for callback
        session([
            'checkout_payment_id' => $payment->id,
            'checkout_plan_id' => $plan->id,
            'checkout_billing_cycle' => $billingCycle,
        ]);

        // Resolve gateway driver and initiate
        try {
            $driver = $this->gatewayManager->driverFromModel($gateway);
            $callbackUrl = route('payment.callback', ['gateway' => $gatewaySlug]);
            $result = $driver->initiate($payment, $callbackUrl);

            if (isset($result['redirect_url'])) {
                return redirect()->away($result['redirect_url']);
            }

            if (isset($result['html'])) {
                return view('checkout.gateway-form', [
                    'html' => $result['html'],
                    'plan' => $plan,
                    'payment' => $payment,
                ]);
            }

            return back()->with('error', 'Could not initiate payment. Please try another method.');
        } catch (\Throwable $e) {
            Log::error('Checkout initiation failed', [
                'gateway' => $gatewaySlug,
                'payment_id' => $payment->id,
                'error' => $e->getMessage(),
            ]);

            $payment->update(['status' => 'failed', 'failure_reason' => $e->getMessage()]);

            // Clear checkout session data so user can retry without stale state
            session()->forget(['checkout_payment_id', 'checkout_plan_id', 'checkout_billing_cycle']);

            return back()->with('error', 'Payment could not be initiated: ' . $e->getMessage());
        }
    }

    /**
     * Success page after payment completes.
     */
    public function success(Request $request)
    {
        $paymentId = session('checkout_payment_id');
        $payment = $paymentId ? Payment::find($paymentId) : null;

        // Clear checkout session
        session()->forget(['checkout_payment_id', 'checkout_plan_id', 'checkout_billing_cycle']);

        return view('checkout.success', [
            'payment' => $payment,
        ]);
    }

    /**
     * Cancel page when user aborts payment.
     */
    public function cancel()
    {
        session()->forget(['checkout_payment_id', 'checkout_plan_id', 'checkout_billing_cycle']);

        return redirect()->route('settings.billing')
            ->with('error', 'Payment was canceled. No charges were made.');
    }

    /**
     * Validate a coupon code via AJAX.
     */
    public function validateCoupon(Request $request)
    {
        $request->validate(['code' => 'required|string', 'plan_id' => 'required|integer']);

        $coupon = Coupon::where('code', strtoupper($request->code))->first();

        if (!$coupon || !$coupon->isValid()) {
            return response()->json(['valid' => false, 'message' => 'Invalid or expired coupon code.']);
        }

        if (!$coupon->isApplicableToPlan($request->plan_id)) {
            return response()->json(['valid' => false, 'message' => 'This coupon is not applicable to the selected plan.']);
        }

        return response()->json([
            'valid' => true,
            'type' => $coupon->type,
            'value' => $coupon->value,
            'name' => $coupon->name,
            'message' => $coupon->type === 'percent' ? "{$coupon->value}% off" : "$" . number_format($coupon->value, 2) . " off",
        ]);
    }
}
