<?php

declare(strict_types=1);

namespace App\Http\Controllers\Account;

use App\Events\SubscriptionCreated;
use App\Http\Controllers\Controller;
use App\Models\Coupon;
use App\Models\Plan;
use App\Models\PlanConfigGroup;
use App\Models\PlanConfigOption;
use App\Models\PlanConfigValue;
use App\Models\SubscriptionConfigSelection;
use App\Services\Billing\BillingServiceFactory;
use Illuminate\Http\JsonResponse;
use Illuminate\Http\RedirectResponse;
use Illuminate\Http\Request;
use Illuminate\Support\Facades\Log;
use Inertia\Inertia;
use Inertia\Response;

class CheckoutController extends Controller
{
    public function __construct(
        private BillingServiceFactory $billingFactory,
    ) {}

    public function show(Plan $plan): Response
    {
        if (! $plan->isAvailable()) {
            abort(404, 'This plan is not currently available.');
        }

        $user = request()->user();
        $stripeService = $this->billingFactory->make('stripe');

        // Fetch OS templates from VirtFusion if this is a VPS plan
        $osTemplates = [];
        $osTemplateGroups = [];
        if ($plan->service_type === 'vps') {
            try {
                $virtfusionService = app(\App\Services\Provisioning\VirtFusionService::class);
                $packageId = $plan->provisioning_config['package_id'] ?? null;
                $templatesData = $virtfusionService->getTemplatesByPackage($packageId);

                // Format flattened templates for backward compatibility
                $osTemplates = collect($templatesData['templates'])->map(fn ($t) => [
                    'id' => $t['id'],
                    'name' => ($t['name'] ?? 'Unknown').' '.($t['version'] ?? ''),
                    'description' => $t['description'] ?? '',
                    'category' => $this->categorizeOS($t['name'] ?? ''),
                    'icon' => $this->getOSIcon($t['name'] ?? ''),
                ])->toArray();

                // Format grouped templates for categorized display
                $osTemplateGroups = collect($templatesData['groups'])->map(function ($group) {
                    return [
                        'name' => $group['name'] ?? 'Other',
                        'description' => $group['description'] ?? '',
                        'icon' => $this->getOSIcon($group['name'] ?? ''),
                        'templates' => collect($group['templates'] ?? [])->map(fn ($t) => [
                            'id' => $t['id'],
                            'name' => ($t['name'] ?? 'Unknown').' '.($t['version'] ?? ''),
                            'description' => $t['description'] ?? '',
                            'category' => $this->categorizeOS($t['name'] ?? ''),
                            'icon' => $this->getOSIcon($t['name'] ?? ''),
                        ])->toArray(),
                    ];
                })->toArray();
            } catch (\Exception $e) {
                \Log::warning('Failed to fetch VirtFusion templates', ['error' => $e->getMessage()]);
            }
        }

        $configGroups = $plan->configGroups()
            ->active()
            ->with(['options' => fn ($q) => $q->active()->orderBy('sort_order'), 'options.values'])
            ->orderBy('sort_order')
            ->get();

        $prefilledSelections = $this->buildPrefilledSelections($configGroups, request());

        return Inertia::render('Checkout/Show', [
            'plan' => $plan->load('prices'),
            'configGroups' => $configGroups,
            'paymentMethods' => $stripeService->getPaymentMethods($user),
            'intent' => $user->hasStripeId() ? $user->createSetupIntent() : null,
            'stripeKey' => config('cashier.key'),
            'osTemplates' => $osTemplates,
            'osTemplateGroups' => $osTemplateGroups,
            'prefilledSelections' => $prefilledSelections,
            // Setup-fee amount per chassis. The Vue page decides whether to
            // display it based on the selected billing cycle — waived on
            // semi_annual / annual per the dedicated-server brainstorm.
            'setupFee' => (float) ($plan->setup_fee ?? 0),
        ]);
    }

    /**
     * Build pre-filled config selections from query params for estimator deep-links.
     *
     * Recognized params:
     *   ?ipv4=N      — total IPv4 count (1-8). N>1 sets the IPv4 quantity option to (N - 1) extras.
     *   ?managed=X   — value slug for the VPS Managed Support radio (self|basic|pro|pilot).
     *   ?backup=X    — value slug for the Off-site Backup radio (none|lite|standard|extended|vault).
     *
     * @param  \Illuminate\Database\Eloquent\Collection<int, PlanConfigGroup>  $configGroups
     * @return array<int, array{option_id: int, value_id: ?int, quantity: ?int, text_value: ?string, locked_price: float, locked_hourly_price: ?float}>
     */
    private function buildPrefilledSelections($configGroups, Request $request): array
    {
        $selections = [];

        $findOption = function (string $groupName, string $optionName) use ($configGroups): ?PlanConfigOption {
            foreach ($configGroups as $group) {
                if ($group->name !== $groupName) {
                    continue;
                }
                foreach ($group->options as $option) {
                    if ($option->name === $optionName) {
                        return $option;
                    }
                }
            }

            return null;
        };

        // IPv4 — total count, first is included free, charge starts at extra #1
        $ipv4Total = (int) $request->query('ipv4', 1);
        if ($ipv4Total > 1) {
            $option = $findOption('VPS Add-ons', 'IPv4 Addresses');
            if ($option) {
                $extras = max(0, $ipv4Total - 1);
                $selections[] = [
                    'option_id' => $option->id,
                    'value_id' => null,
                    'quantity' => $extras,
                    'text_value' => null,
                    'locked_price' => (float) ($option->monthly_price ?? 0) * $extras,
                    'locked_hourly_price' => $option->hourly_price !== null ? (float) $option->hourly_price * $extras : null,
                ];
            }
        }

        // Managed Support — radio
        $managed = $request->query('managed');
        if ($managed && $managed !== 'self') {
            $option = $findOption('VPS Managed Support', 'Managed Support');
            if ($option) {
                $value = $option->values->firstWhere('value', $managed);
                if ($value) {
                    $selections[] = [
                        'option_id' => $option->id,
                        'value_id' => $value->id,
                        'quantity' => null,
                        'text_value' => null,
                        'locked_price' => (float) ($value->monthly_price ?? 0),
                        'locked_hourly_price' => $value->hourly_price !== null ? (float) $value->hourly_price : null,
                    ];
                }
            }
        }

        // Off-site Backup — radio
        $backup = $request->query('backup');
        if ($backup && $backup !== 'none') {
            $option = $findOption('Off-site Backup', 'Backup Tier');
            if ($option) {
                $value = $option->values->firstWhere('value', $backup);
                if ($value) {
                    $selections[] = [
                        'option_id' => $option->id,
                        'value_id' => $value->id,
                        'quantity' => null,
                        'text_value' => null,
                        'locked_price' => (float) ($value->monthly_price ?? 0),
                        'locked_hourly_price' => $value->hourly_price !== null ? (float) $value->hourly_price : null,
                    ];
                }
            }
        }

        return $selections;
    }

    public function showCustom(string $serviceType): Response
    {
        $plan = Plan::where('slug', "{$serviceType}-custom")->firstOrFail();

        $configGroup = PlanConfigGroup::buildYourOwn()
            ->active()
            ->forServiceType($serviceType)
            ->with(['options' => fn ($q) => $q->active()->orderBy('sort_order'), 'options.values'])
            ->first();

        if (! $configGroup) {
            abort(404, 'Build Your Own is not available for this service type.');
        }

        // Use the same pattern as the existing show() method for payment methods
        $stripeService = $this->billingFactory->make('stripe');
        $paymentMethods = $stripeService->getPaymentMethods(request()->user());
        $intent = request()->user()->hasStripeId() ? request()->user()->createSetupIntent() : null;

        return Inertia::render('Checkout/Show', [
            'plan' => $plan->load('prices'),
            'configGroups' => [$configGroup],
            'mode' => 'custom',
            'paymentMethods' => $paymentMethods,
            'intent' => $intent,
            'stripeKey' => config('cashier.key'),
            'osTemplates' => [],
            'osTemplateGroups' => [],
        ]);
    }

    private function categorizeOS(string $name): string
    {
        $name = strtolower($name);

        if (str_contains($name, 'ubuntu')) {
            return 'ubuntu';
        }
        if (str_contains($name, 'debian')) {
            return 'debian';
        }
        if (str_contains($name, 'alma') || str_contains($name, 'rocky') || str_contains($name, 'centos') || str_contains($name, 'rhel')) {
            return 'redhat';
        }
        if (str_contains($name, 'fedora')) {
            return 'fedora';
        }
        if (str_contains($name, 'arch')) {
            return 'arch';
        }

        return 'linux';
    }

    private function getOSIcon(string $name): string
    {
        $category = $this->categorizeOS($name);

        return match ($category) {
            'ubuntu' => 'tabler-brand-ubuntu',
            'debian' => 'tabler-brand-debian',
            'redhat' => 'tabler-brand-redhat',
            'fedora' => 'tabler-brand-fedora',
            'arch' => 'tabler-brand-arch-linux',
            default => 'tabler-brand-linux',
        };
    }

    public function store(Request $request, Plan $plan): RedirectResponse|JsonResponse
    {
        $request->validate([
            'gateway' => ['required', 'in:stripe,paypal'],
            'payment_method_id' => ['required_if:gateway,stripe', 'nullable', 'string'],
            'coupon_code' => ['nullable', 'string', 'max:50'],
            'billing_cycle' => ['required', 'in:monthly,quarterly,semi_annual,annual'],
            'configuration' => ['nullable', 'array'],
            'configuration.os_template_id' => [$plan->service_type === 'vps' ? 'required' : 'nullable', 'integer'],
            'configuration.auth_method' => ['nullable', 'in:password,ssh'],
            'configuration.ssh_key' => ['nullable', 'string', 'max:4096'],
            'config_selections' => ['nullable', 'array'],
            'config_selections.*.option_id' => ['required', 'integer', 'exists:plan_config_options,id'],
            'config_selections.*.value_id' => ['nullable', 'integer', 'exists:plan_config_values,id'],
            'config_selections.*.quantity' => ['nullable', 'integer', 'min:0'],
            'config_selections.*.text_value' => ['nullable', 'string', 'max:500'],
        ]);

        if (! $plan->isAvailable()) {
            return back()->with('error', 'This plan is not currently available.');
        }

        $user = $request->user();
        $gateway = $request->input('gateway');
        $couponCode = $request->input('coupon_code');
        $billingCycle = $request->input('billing_cycle', 'monthly');
        $service = $this->billingFactory->make($gateway);

        try {
            $result = $service->createSubscription(
                $user,
                $plan,
                $request->input('payment_method_id'),
                $couponCode,
                $billingCycle,
            );

            // PayPal requires redirect to approval URL
            if ($gateway === 'paypal' && isset($result['approval_url'])) {
                return response()->json([
                    'redirect' => $result['approval_url'],
                ]);
            }

            // Stripe incomplete payment needs client-side confirmation
            if ($result['status'] === 'incomplete' && isset($result['client_secret'])) {
                return response()->json([
                    'requires_action' => true,
                    'client_secret' => $result['client_secret'],
                ]);
            }

            if ($couponCode) {
                $this->redeemCoupon($couponCode, $user, $plan, $billingCycle);
            }

            if (empty($result['subscription_id'])) {
                Log::error('Checkout completed but no subscription_id returned', [
                    'user_id' => $user->id,
                    'plan_id' => $plan->id,
                    'gateway' => $gateway,
                ]);

                return redirect()->route('account.dashboard')
                    ->with('warning', "Subscribed to {$plan->name}, but there was an issue linking your subscription. Please contact support if services aren't provisioned.");
            }

            $subscription = $user->subscriptions()->where('stripe_id', $result['subscription_id'])->first();
            if ($subscription) {
                // Store configuration on the subscription record for async provisioning
                // Cashier's Subscription model has no array cast for provisioning_config, so encode manually
                if ($request->has('configuration')) {
                    $subscription->update(['provisioning_config' => json_encode($request->input('configuration'))]);
                }

                // Save configurable option selections with server-side price recalculation
                if ($request->has('config_selections')) {
                    foreach ($request->input('config_selections', []) as $selection) {
                        $option = PlanConfigOption::find($selection['option_id']);
                        if (! $option) {
                            continue;
                        }

                        // Recalculate price server-side instead of trusting client
                        $lockedPrice = 0;
                        $lockedHourlyPrice = null;

                        if (in_array($option->type, ['slider', 'quantity'])) {
                            $qty = (int) ($selection['quantity'] ?? 0);
                            $lockedPrice = $option->calculatePrice($qty, $billingCycle);
                            $lockedHourlyPrice = $option->hourly_price ? $option->getHourlyPrice($qty) : null;
                        } elseif ($selection['value_id'] ?? null) {
                            $value = PlanConfigValue::find($selection['value_id']);
                            if ($value) {
                                $lockedPrice = $value->getPriceForCycle($billingCycle);
                                $lockedHourlyPrice = $value->hourly_price > 0 ? (float) $value->hourly_price : null;
                            }
                        }

                        SubscriptionConfigSelection::create([
                            'subscription_id' => $subscription->id,
                            'option_id' => $selection['option_id'],
                            'value_id' => $selection['value_id'] ?? null,
                            'quantity' => $selection['quantity'] ?? null,
                            'text_value' => $selection['text_value'] ?? null,
                            'locked_price' => $lockedPrice,
                            'locked_hourly_price' => $lockedHourlyPrice,
                            'billing_cycle' => $billingCycle,
                            'is_custom_build' => $request->boolean('is_custom_build', false),
                        ]);
                    }
                }

                SubscriptionCreated::dispatch($user, $subscription);
            } else {
                Log::error('Subscription not found after creation', [
                    'user_id' => $user->id,
                    'stripe_id' => $result['subscription_id'],
                ]);
            }

            return redirect()->route('account.dashboard')
                ->with('success', "Successfully subscribed to {$plan->name}!");
        } catch (\Exception $e) {
            return back()->with('error', 'Checkout failed: '.$e->getMessage());
        }
    }

    public function applyCoupon(Request $request): JsonResponse
    {
        $request->validate([
            'code' => ['required', 'string'],
            'plan_id' => ['required', 'exists:plans,id'],
            'billing_cycle' => ['nullable', 'string', 'in:monthly,quarterly,semi_annual,annual'],
        ]);

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

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

        $plan = Plan::findOrFail($request->input('plan_id'));
        $billingCycle = $request->input('billing_cycle', 'monthly');
        $price = (float) ($plan->priceForCycle($billingCycle)?->price ?? $plan->price);
        $discount = $this->calculateDiscount($coupon, $price);

        return response()->json([
            'valid' => true,
            'discount' => $discount,
            'new_total' => max(0, $price - $discount),
            'coupon_type' => $coupon->type,
            'coupon_value' => $coupon->value,
        ]);
    }

    public function paypalCallback(Request $request): RedirectResponse
    {
        $planId = $request->query('plan');
        $plan = Plan::findOrFail($planId);

        return redirect()->route('account.dashboard')
            ->with('success', "Successfully subscribed to {$plan->name}!");
    }

    public function paypalCancel(): RedirectResponse
    {
        return redirect()->route('account.plans.index')
            ->with('error', 'PayPal checkout was cancelled.');
    }

    private function redeemCoupon(string $code, \App\Models\User $user, Plan $plan, string $billingCycle = 'monthly'): void
    {
        $coupon = Coupon::where('code', $code)->first();

        if ($coupon && $coupon->isValid()) {
            $price = (float) ($plan->priceForCycle($billingCycle)?->price ?? $plan->price);
            $discount = $this->calculateDiscount($coupon, $price);

            $coupon->redemptions()->create([
                'user_id' => $user->id,
                'discount_amount' => $discount,
            ]);

            $coupon->increment('times_used');
        }
    }

    private function calculateDiscount(Coupon $coupon, float $price): float
    {
        return match ($coupon->type) {
            'percentage' => round($price * ($coupon->value / 100), 2),
            'fixed_amount' => min($coupon->value, $price),
            default => 0,
        };
    }
}
