<?php

namespace App\Http\Controllers\Portal;

use App\Http\Controllers\Controller;
use App\Models\Cart;
use App\Models\CartItem;
use App\Models\ClientService;
use App\Models\CustomerProfile;
use App\Models\Domain;
use App\Models\Invoice;
use App\Models\Order;
use App\Models\User;
use App\Services\CurrencyResolver;
use App\Services\PricingService;
use App\Services\PromotionService;
use App\Services\WhmcsBillingService;
use App\Services\WhmcsSupportService;
use App\Services\ZtApiService;
use Illuminate\Http\JsonResponse;
use Illuminate\Http\RedirectResponse;
use Illuminate\Http\Request;
use Illuminate\Support\Facades\Auth;
use Illuminate\Support\Facades\DB;
use Illuminate\Support\Facades\Hash;
use Illuminate\Support\Str;
use Illuminate\Validation\Rule;
use Illuminate\Validation\Rules;
use RuntimeException;
use Illuminate\View\View;

class CheckoutController extends Controller
{
    public function __construct(
        private readonly PricingService $pricing,
        private readonly CurrencyResolver $currencyResolver,
        private readonly WhmcsBillingService $whmcsBilling,
        private readonly ZtApiService $api,
        private readonly WhmcsSupportService $support,
        private readonly PromotionService $promotions,
    ) {}

    public function index(Request $request): View
    {
        $currencyInfo = $this->currencyResolver->resolve($request);
        $cart = $this->cart($request, $currencyInfo);
        $groups = $this->pricing->productGroups($cart->currency);
        $selectedGroup = $request->query('group', $groups[0]['slug'] ?? 'web-hosting');

        if ($request->query('product')) {
            $product = $this->pricing->findProductBySlug($request->query('product'), $cart->currency);
            if ($product) {
                $cycle = $request->query('cycle', array_key_first($product['available_cycles']));
                $selectedGroup = $product['group_slug'] ?? $selectedGroup;

                // Server products must be configured before they are placed in the cart.
                if (! $this->pricing->productRequiresServerConfiguration($product)) {
                    $this->addProductToCart($cart, (int) $product['pid'], $cycle);
                    $this->promotions->refresh($cart, Auth::user());
                }
            }
        }

        $cart->load('items');

        $domainResult = null;
        if ($request->boolean('domain_check') && $request->filled('domain_name')) {
            try {
                $domainResult = $this->pricing->domainSearch(
                    $request->query('domain_name'),
                    $request->query('operation', 'register'),
                    $cart->currency
                );
            } catch (\Throwable $e) {
                $domainResult = [
                    'domain' => $request->query('domain_name'),
                    'operation' => $request->query('operation', 'register'),
                    'error' => $e->getMessage(),
                    'availability' => ['status' => 'unknown'],
                    'prices' => [],
                    'alternatives' => [],
                ];
            }
        }

        return view('checkout.index', [
            'cart' => $cart,
            'groups' => $groups,
            'selectedGroup' => $selectedGroup,
            'selectedGroupLabel' => $this->pricing->productGroupLabel($selectedGroup),
            'products' => $this->pricing->products($cart->currency, $selectedGroup),
            'paymentMethods' => $this->paymentMethods($cart->currency),
            'bankChannels' => config('zt.manual_payments.bank_channels', []),
            'pricing' => $this->pricing,
            'currencyInfo' => $currencyInfo,
            'domainResult' => $domainResult,
        ]);
    }

    public function domainSearch(Request $request): View|RedirectResponse
    {
        $currencyInfo = $this->currencyResolver->resolve($request);
        $cart = $this->cart($request, $currencyInfo);
        $cart->load('items');

        $domainResult = null;
        if ($request->isMethod('post')) {
            $data = $request->validate([
                'domain_name' => ['required', 'string', 'max:255'],
                'operation' => ['required', 'in:register,transfer'],
            ]);

            try {
                $domainResult = $this->pricing->domainSearch($data['domain_name'], $data['operation'], $cart->currency);
            } catch (\Throwable $e) {
                return back()->withErrors(['domain' => $e->getMessage()]);
            }
        }

        return view('domains.search', [
            'cart' => $cart,
            'pricing' => $this->pricing,
            'currencyInfo' => $currencyInfo,
            'domainResult' => $domainResult,
            'operation' => $request->input('operation', $request->route('mode', 'register')),
        ]);
    }

    public function domainSearchAjax(Request $request): JsonResponse
    {
        $data = $request->validate([
            'domain_name' => ['required', 'string', 'max:255'],
            'operation' => ['required', 'in:register,transfer'],
            'dns_has_records' => ['nullable', 'boolean'],
        ]);

        $currencyInfo = $this->currencyResolver->resolve($request);
        $cart = $this->cart($request, $currencyInfo);
        $cart->load('items');

        $knownAvailability = $request->boolean('dns_has_records')
            ? [
                'status' => 'unavailable',
                'source' => 'browser-dns',
                'raw' => null,
                'supported' => true,
            ]
            : null;

        try {
            $domainResult = $this->pricing->domainSearch(
                $data['domain_name'],
                $data['operation'],
                $cart->currency,
                $knownAvailability,
            );
        } catch (\Throwable $e) {
            $domainResult = [
                'domain' => $data['domain_name'],
                'operation' => $data['operation'],
                'error' => $e->getMessage(),
                'availability' => ['status' => 'unknown'],
                'prices' => [],
                'alternatives' => [],
                'suggestion_candidates' => [],
            ];
        }

        return $this->domainResultResponse($domainResult, $cart);
    }

    public function domainSuggestionAjax(Request $request): JsonResponse
    {
        $data = $request->validate([
            'candidate_domain' => ['required', 'string', 'max:255'],
            'operation' => ['required', 'in:register'],
            'context' => ['nullable', 'in:suggestion,brand'],
        ]);

        $currencyInfo = $this->currencyResolver->resolve($request);
        $cart = $this->cart($request, $currencyInfo);
        $cart->load('items');

        try {
            $suggestion = $this->pricing->checkDomainSuggestion($data['candidate_domain'], $data['operation'], $cart->currency);
        } catch (\Throwable $e) {
            return response()->json([
                'ok' => true,
                'available' => false,
                'checked_domain' => $data['candidate_domain'],
                'message' => $e->getMessage(),
            ]);
        }

        $partial = ($data['context'] ?? 'suggestion') === 'brand'
            ? 'checkout.partials.domain-brand-row'
            : 'checkout.partials.domain-suggestion-row';

        return response()->json([
            'ok' => true,
            'available' => (bool) $suggestion,
            'checked_domain' => $data['candidate_domain'],
            'result_html' => $suggestion ? view($partial, [
                'alt' => $suggestion,
                'cart' => $cart,
                'pricing' => $this->pricing,
            ])->render() : '',
        ]);
    }

    public function cartSummary(Request $request): JsonResponse
    {
        $currencyInfo = $this->currencyResolver->resolve($request);
        $cart = $this->cart($request, $currencyInfo);
        $cart->load('items');

        return $this->cartResponse($cart);
    }

    public function browserCountry(Request $request)
    {
        if (! config('zt.ip_lookup.browser_detection_enabled', true)) {
            return response()->json(['ok' => false, 'message' => 'Browser currency detection is disabled.'], 403);
        }

        $currencyInfo = $this->currencyResolver->resolve($request);
        if (($currencyInfo['locked'] ?? false) === true) {
            return response()->json([
                'ok' => false,
                'locked' => true,
                'currency' => $currencyInfo['currency'],
                'message' => 'Account currency is locked after the first order.',
            ], 409);
        }

        $data = $request->validate([
            'country' => ['required', 'string', 'size:2'],
        ]);

        $country = strtoupper($data['country']);
        $currency = $this->currencyResolver->currencyForCountry($country);

        $request->session()->put('currency_auto_country', $country);
        $request->session()->forget(['currency', 'currency_manual']);

        return response()->json([
            'ok' => true,
            'country' => $country,
            'currency' => $currency,
        ]);
    }

    public function addProduct(Request $request): RedirectResponse|JsonResponse
    {
        $data = $request->validate([
            'api_pid' => ['required', 'integer'],
            'billing_cycle' => ['required', 'string'],
            'currency' => ['nullable', 'in:USD,PKR'],
            'config_options' => ['nullable', 'array'],
            'hostname' => ['nullable', 'string', 'max:255'],
            'root_password' => ['nullable', 'string', 'max:255'],
            'ns1_prefix' => ['nullable', 'string', 'max:50'],
            'ns2_prefix' => ['nullable', 'string', 'max:50'],
        ]);

        if (! empty($data['currency'])) {
            $request->session()->put('currency', $data['currency']);
        }

        try {
            $currencyInfo = $this->currencyResolver->resolve($request);
            $cart = $this->cart($request, $currencyInfo);
            $this->addProductToCart($cart, (int) $data['api_pid'], $data['billing_cycle'], $data);
            $this->promotions->refresh($cart, Auth::user());
        } catch (\Throwable $e) {
            if ($this->wantsJson($request)) {
                return response()->json(['ok' => false, 'message' => $e->getMessage()], 422);
            }

            return back()->withErrors(['product' => $e->getMessage()]);
        }

        if ($this->wantsJson($request)) {
            return $this->cartResponse($cart, 'Product added to cart.');
        }

        return redirect()->route('checkout.index')->with('status', 'Product added to cart.');
    }

    public function addDomain(Request $request): RedirectResponse|JsonResponse
    {
        $data = $request->validate([
            'domain_mode' => ['required', 'in:register,transfer,existing'],
            'domain_name' => ['required', 'string', 'max:255'],
            'years' => ['required_unless:domain_mode,existing', 'nullable', 'integer', 'min:1', 'max:10'],
            'epp_code' => ['required_if:domain_mode,transfer', 'nullable', 'string', 'min:6', 'max:255', 'not_regex:/\s/'],
            'domain_checked' => ['nullable', 'boolean'],
            'domain_verified' => ['nullable', 'boolean'],
            'domain_status' => ['nullable', 'string', 'max:50'],
        ]);

        $currencyInfo = $this->currencyResolver->resolve($request);
        $cart = $this->cart($request, $currencyInfo);
        $domain = $this->normalizeDomain($data['domain_name']);

        if ($data['domain_mode'] === 'existing') {
            $cart->items()->updateOrCreate(
                ['item_type' => 'domain_existing'],
                [
                    'name' => 'Existing Domain',
                    'domain_name' => $domain,
                    'operation' => 'existing',
                    'years' => null,
                    'quantity' => 1,
                    'currency' => $cart->currency,
                    'unit_price' => 0,
                    'total' => 0,
                    'price_source' => 'manual',
                    'price_checked_at' => now(),
                ]
            );
            $this->promotions->refresh($cart, Auth::user());

            if ($this->wantsJson($request)) {
                return $this->cartResponse($cart, 'Existing domain attached to cart.');
            }

            return redirect()->route('checkout.index')->with('status', 'Existing domain attached to cart.');
        }

        $skipAvailabilityCheck = $request->boolean('domain_checked') || $request->boolean('domain_verified');

        try {
            if (! $skipAvailabilityCheck) {
                $availabilityResult = $this->pricing->domainSearch($domain, $data['domain_mode'], $cart->currency);
                $availabilityStatus = $availabilityResult['availability']['status'] ?? 'unknown';

                if ($data['domain_mode'] === 'register' && $availabilityStatus !== 'available') {
                    if ($this->wantsJson($request)) {
                        return response()->json(['ok' => false, 'message' => 'This domain is not available for registration.'], 422);
                    }

                    return back()->withErrors(['domain' => 'This domain is not available for registration.']);
                }

                if ($data['domain_mode'] === 'transfer' && $availabilityStatus === 'available') {
                    if ($this->wantsJson($request)) {
                        return response()->json(['ok' => false, 'message' => 'This domain is not registered yet, so it cannot be transferred. Please register it instead.'], 422);
                    }

                    return back()->withErrors(['domain' => 'This domain is not registered yet, so it cannot be transferred. Please register it instead.']);
                }

                if ($data['domain_mode'] === 'transfer' && $availabilityStatus === 'unknown') {
                    if ($this->wantsJson($request)) {
                        return response()->json(['ok' => false, 'message' => 'Transfer availability could not be confirmed. Please contact support or try again.'], 422);
                    }

                    return back()->withErrors(['domain' => 'Transfer availability could not be confirmed. Please contact support or try again.']);
                }
            }

            $years = (int) ($data['years'] ?: 1);
            $price = $this->pricing->domainPrice($domain, $data['domain_mode'], $years, $cart->currency);
        } catch (\Throwable $e) {
            if ($this->wantsJson($request)) {
                return response()->json(['ok' => false, 'message' => $e->getMessage()], 422);
            }

            return back()->withErrors(['domain' => $e->getMessage()]);
        }

        $cart->items()->updateOrCreate(
            ['domain_name' => $domain, 'operation' => $data['domain_mode']],
            [
                'item_type' => 'domain_'.$data['domain_mode'],
                'name' => Str::headline($data['domain_mode']).' Domain',
                'tld' => $price['tld'],
                'years' => $years,
                'quantity' => 1,
                'currency' => $cart->currency,
                'base_usd_price' => $price['base_usd_price'],
                'unit_price' => $price['unit_price'],
                'total' => $price['unit_price'],
                'price_source' => $price['price_source'],
                'price_checked_at' => now(),
                'metadata' => ['epp_code' => $data['epp_code'] ?? null],
            ]
        );
        $this->promotions->refresh($cart, Auth::user());

        if ($this->wantsJson($request)) {
            return $this->cartResponse($cart, 'Domain added to cart.');
        }

        return redirect()->route('checkout.index')->with('status', 'Domain added to cart.');
    }

    public function addToCartLink(Request $request): RedirectResponse
    {
        $identifier = $this->productIdentifierFromRequest($request);
        $domainName = $this->domainNameFromRequest($request);

        if (! $identifier && ! $domainName) {
            return $this->redirectToCheckoutAnchor('plans')->withErrors([
                'cart' => 'The selected item could not be added. Please choose it again.',
            ]);
        }

        $currency = strtoupper((string) $request->query('currency', ''));
        if (in_array($currency, ['USD', 'PKR'], true)) {
            $request->session()->put('currency', $currency);
        }

        $mode = (string) ($request->query('mode') ?: $request->query('domain_mode') ?: 'register');
        $messages = [];

        try {
            $currencyInfo = $this->currencyResolver->resolve($request);
            $cart = $this->cart($request, $currencyInfo);

            if ($identifier) {
                $product = is_numeric($identifier)
                    ? $this->pricing->findProductByPid((int) $identifier, $cart->currency)
                    : $this->pricing->findProductBySlug((string) $identifier, $cart->currency);

                if (! $product && ! is_numeric($identifier)) {
                    $product = $this->pricing->findProductBySlug(Str::slug((string) $identifier), $cart->currency);
                }

                if (! $product) {
                    throw new RuntimeException('Product was not found or has no supported price in this currency.');
                }

                $cycle = (string) ($request->query('cycle')
                    ?: $request->query('billing_cycle')
                    ?: $request->query('billingcycle')
                    ?: array_key_first($product['available_cycles']));

                if (! isset($product['available_cycles'][$cycle])) {
                    throw new RuntimeException('Selected billing cycle is not available for this product.');
                }

                if ($this->pricing->productRequiresServerConfiguration($product)) {
                    $group = (string) ($product['group_slug'] ?? 'vps-hosting');
                    $url = route('checkout.index', [
                        'step' => 'cart',
                        'group' => $group,
                        'configure' => (int) $product['pid'],
                        'cycle' => $cycle,
                    ]).'#plan-config-panel-'.(int) $product['pid'];

                    return redirect()->to($url)->with('status', 'Complete the server details to add this plan to your cart.');
                }

                $this->addProductToCart($cart, (int) $product['pid'], $cycle);
                $this->promotions->refresh($cart, Auth::user());
                $messages[] = 'Product added to cart';
            }

            if ($domainName) {
                if (! in_array($mode, ['register', 'transfer', 'existing'], true)) {
                    throw new RuntimeException('Invalid domain mode. Use register, transfer, or existing.');
                }

                $domain = $this->normalizeDomain((string) $domainName);

                if ($request->boolean('check_domain') || $request->boolean('domain_check_before_add')) {
                    $availabilityResult = $this->pricing->domainSearch($domain, $mode, $cart->currency);
                    $availabilityStatus = $availabilityResult['availability']['status'] ?? 'unknown';

                    if ($mode === 'register' && $availabilityStatus !== 'available') {
                        throw new RuntimeException('This domain is not available for registration.');
                    }

                    if ($mode === 'transfer' && $availabilityStatus === 'available') {
                        throw new RuntimeException('This domain is not registered yet, so it cannot be transferred. Please register it instead.');
                    }

                    if ($mode === 'transfer' && $availabilityStatus === 'unknown') {
                        throw new RuntimeException('Transfer availability could not be confirmed. Please contact support or try again.');
                    }
                }

                if ($mode === 'existing') {
                    $cart->items()->updateOrCreate(
                        ['item_type' => 'domain_existing'],
                        [
                            'name' => 'Existing Domain',
                            'domain_name' => $domain,
                            'operation' => 'existing',
                            'years' => null,
                            'quantity' => 1,
                            'currency' => $cart->currency,
                            'unit_price' => 0,
                            'total' => 0,
                            'price_source' => 'manual',
                            'price_checked_at' => now(),
                        ]
                    );

                    $messages[] = 'Existing domain attached';
                } else {
                    if ($mode === 'transfer' && ! $request->filled('epp_code')) {
                        throw new RuntimeException('Transfer domain needs EPP/Auth code before it can be added to cart.');
                    }

                    $years = (int) ($request->query('years') ?: $request->query('year') ?: 1);
                    if ($years < 1 || $years > 10) {
                        throw new RuntimeException('Domain years must be between 1 and 10.');
                    }

                    $price = $this->pricing->domainPrice($domain, $mode, $years, $cart->currency);

                    $cart->items()->updateOrCreate(
                        ['domain_name' => $domain, 'operation' => $mode],
                        [
                            'item_type' => 'domain_'.$mode,
                            'name' => Str::headline($mode).' Domain',
                            'tld' => $price['tld'],
                            'years' => $years,
                            'quantity' => 1,
                            'currency' => $cart->currency,
                            'base_usd_price' => $price['base_usd_price'],
                            'unit_price' => $price['unit_price'],
                            'total' => $price['unit_price'],
                            'price_source' => $price['price_source'],
                            'price_checked_at' => now(),
                            'metadata' => ['epp_code' => $request->query('epp_code')],
                        ]
                    );

                    $messages[] = $mode === 'transfer' ? 'Domain transfer added to cart' : 'Domain added to cart';
                }
            }

            $this->promotions->refresh($cart, Auth::user());
        } catch (\Throwable $e) {
            $params = ['step' => 'cart'];
            if ($domainName) {
                $params['operation'] = $mode;
                $params['domain_name'] = (string) $domainName;
            }

            return redirect()->to(route('checkout.index', $params).'#checkout-step')->withErrors(['cart' => $e->getMessage()]);
        }

        return redirect()->to(route('checkout.index', ['step' => 'cart']).'#cart-summary')
            ->with('status', implode('. ', $messages).'.');
    }

    public function addProductLink(Request $request): RedirectResponse
    {
        return $this->addToCartLink($request);
    }

    public function addDomainLink(Request $request): RedirectResponse
    {
        return $this->addToCartLink($request);
    }

    protected function productIdentifierFromRequest(Request $request): string|int|null
    {
        return $request->query('pid')
            ?: $request->query('id')
            ?: $request->query('api_pid')
            ?: $request->query('product');
    }

    protected function domainNameFromRequest(Request $request): ?string
    {
        return $request->query('domain')
            ?: $request->query('domian')
            ?: $request->query('domain_name');
    }

    public function updateItem(Request $request, CartItem $cartItem): RedirectResponse|JsonResponse
    {
        $currencyInfo = $this->currencyResolver->resolve($request);
        $cart = $this->cart($request, $currencyInfo);

        if ((int) $cartItem->cart_id !== (int) $cart->id) {
            abort(403);
        }

        try {
            if ($cartItem->item_type === 'hosting') {
                $data = $request->validate([
                    'billing_cycle' => ['required', 'string'],
                ]);

                $price = $this->pricing->productPrice(
                    (int) $cartItem->api_pid,
                    $data['billing_cycle'],
                    $cart->currency
                );
                $product = $this->pricing->findProductByPid((int) $cartItem->api_pid, $cart->currency);
                $selectedConfig = $this->selectedConfigurationValues($cartItem);
                $config = $product
                    ? $this->pricing->calculateConfigurationPrice($product, $cart->currency, $data['billing_cycle'], $selectedConfig)
                    : ['total' => 0, 'selected' => []];
                $metadata = array_replace_recursive(
                    (array) $cartItem->metadata,
                    $price['metadata'],
                    [
                        'description' => $price['description'],
                        'config_options' => $config['selected'],
                    ]
                );
                $quantity = max((int) $cartItem->quantity, 1);

                $cartItem->update([
                    'billing_cycle' => $data['billing_cycle'],
                    'currency' => $cart->currency,
                    'unit_price' => $price['unit_price'],
                    'total' => round(($price['unit_price'] + $config['total']) * $quantity, 2),
                    'price_source' => 'api_live',
                    'price_checked_at' => now(),
                    'metadata' => $metadata,
                ]);

                $this->promotions->refresh($cart, Auth::user());

                if ($this->wantsJson($request)) {
                    return $this->cartResponse($cart, 'Billing cycle updated.');
                }

                return $this->redirectBackWithAnchor('cart-summary')->with('status', 'Billing cycle updated.');
            }

            if (in_array($cartItem->item_type, ['domain_register', 'domain_transfer'], true)) {
                $data = $request->validate([
                    'years' => ['required', 'integer', 'min:1', 'max:10'],
                ]);

                $price = $this->pricing->domainPrice(
                    $cartItem->domain_name,
                    $cartItem->operation ?: 'register',
                    (int) $data['years'],
                    $cart->currency
                );

                $cartItem->update([
                    'years' => (int) $data['years'],
                    'currency' => $cart->currency,
                    'base_usd_price' => $price['base_usd_price'],
                    'unit_price' => $price['unit_price'],
                    'total' => $price['unit_price'],
                    'price_source' => $price['price_source'],
                    'price_checked_at' => now(),
                ]);

                $this->promotions->refresh($cart, Auth::user());

                if ($this->wantsJson($request)) {
                    return $this->cartResponse($cart, 'Domain billing period updated.');
                }

                return $this->redirectBackWithAnchor('cart-summary')->with('status', 'Domain billing period updated.');
            }

            if ($this->wantsJson($request)) {
                return $this->cartResponse($cart, 'Nothing to update for this item.');
            }

            return $this->redirectBackWithAnchor('cart-summary')->with('status', 'Nothing to update for this item.');
        } catch (\Throwable $e) {
            if ($this->wantsJson($request)) {
                return response()->json(['ok' => false, 'message' => $e->getMessage()], 422);
            }

            return back()->withErrors(['cart' => $e->getMessage()]);
        }
    }

    public function removeItem(Request $request, CartItem $cartItem): RedirectResponse|JsonResponse
    {
        $currencyInfo = $this->currencyResolver->resolve($request);
        $cart = $this->cart($request, $currencyInfo);

        if ((int) $cartItem->cart_id !== (int) $cart->id) {
            abort(403);
        }

        $cartItem->delete();
        $this->promotions->refresh($cart, Auth::user());

        if ($this->wantsJson($request)) {
            return $this->cartResponse($cart, 'Cart item removed.');
        }

        return $this->redirectBackWithAnchor('cart-summary')->with('status', 'Cart item removed.');
    }

    public function applyPromotion(Request $request): RedirectResponse|JsonResponse
    {
        $data = $request->validate([
            'promo_code' => ['required', 'string', 'max:100'],
        ]);

        $cart = $this->cart($request, $this->currencyResolver->resolve($request));
        $cart->load('items');

        if ($cart->items->isEmpty()) {
            return $this->wantsJson($request)
                ? response()->json(['ok' => false, 'message' => 'Add a plan before applying a promo code.'], 422)
                : back()->withErrors(['promo_code' => 'Add a plan before applying a promo code.']);
        }

        try {
            $this->promotions->apply($cart, $data['promo_code'], Auth::user());
        } catch (\Throwable $e) {
            return $this->wantsJson($request)
                ? response()->json(['ok' => false, 'message' => $e->getMessage()], 422)
                : back()->withErrors(['promo_code' => $e->getMessage()]);
        }

        if ($this->wantsJson($request)) {
            return $this->cartResponse($cart, 'Promo code applied.');
        }

        return $this->redirectBackWithAnchor('cart-summary')->with('status', 'Promo code applied.');
    }

    public function removePromotion(Request $request): RedirectResponse|JsonResponse
    {
        $cart = $this->cart($request, $this->currencyResolver->resolve($request));
        $this->promotions->clear($cart);

        if ($this->wantsJson($request)) {
            return $this->cartResponse($cart, 'Promo code removed.');
        }

        return $this->redirectBackWithAnchor('cart-summary')->with('status', 'Promo code removed.');
    }

    public function saveCheckoutDetails(Request $request): RedirectResponse
    {
        $cart = $this->cart($request, $this->currencyResolver->resolve($request));
        $cart->load('items');

        if ($cart->items->isEmpty()) {
            return redirect()->to(route('checkout.index', ['step' => 'cart']).'#checkout-step')
                ->withErrors(['cart' => 'Your cart is empty.']);
        }

        if ($this->cartIsMissingRequiredDomain($cart)) {
            return redirect()->to(route('checkout.index', ['step' => 'cart']).'#domain-search')
                ->withErrors(['domain' => 'A domain is required for the selected hosting service. Register, transfer, or attach an existing domain first.']);
        }

        $requiresCompany = $cart->items->contains(fn ($item) => $item->item_type === 'domain_register');

        $data = $request->validate([
            'first_name' => Auth::check() ? ['nullable'] : ['required', 'string', 'max:255'],
            'last_name' => Auth::check() ? ['nullable'] : ['required', 'string', 'max:255'],
            'email' => Auth::check() ? ['nullable'] : ['required', 'email', 'max:255', 'unique:users,email'],
            'phone_country_code' => ['nullable', 'string', 'max:10'],
            'phone' => ['nullable', 'string', 'max:30'],
            'password' => Auth::check() ? ['nullable'] : ['required', 'confirmed', Rules\Password::defaults()],

            'company_name' => [$requiresCompany ? 'required' : 'nullable', 'string', 'max:255'],
            'street_address' => ['required', 'string', 'max:255'],
            'street_address_2' => ['nullable', 'string', 'max:255'],
            'city' => ['required', 'string', 'max:255'],
            'state' => ['required', 'string', 'max:255'],
            'postcode' => ['required', 'string', 'max:50'],
            'country' => ['required', 'string', 'max:2'],
        ]);

        $request->session()->put('checkout_details', $data);

        $accountCurrency = $this->currencyResolver->currencyForCountry($data['country']);
        if (Auth::check()) {
            Auth::user()->forceFill(['country' => strtoupper($data['country']), 'currency' => $accountCurrency, 'currency_source' => 'billing_country', 'currency_locked_at' => Auth::user()->currency_locked_at ?: now()])->save();
        } else {
            $request->session()->put('currency', $accountCurrency);
        }

        return redirect()->to(route('checkout.index', ['step' => 'payment']).'#checkout-step')
            ->with('status', 'Details saved. Choose payment method to place the order.');
    }

    public function complete(Request $request): RedirectResponse
    {
        $cart = $this->cart($request, $this->currencyResolver->resolve($request));
        $cart->load('items');

        if ($cart->items->isEmpty()) {
            return back()->withErrors(['cart' => 'Your cart is empty.']);
        }

        if ($this->cartIsMissingRequiredDomain($cart)) {
            return redirect()->to(route('checkout.index', ['step' => 'cart']).'#domain-search')
                ->withErrors(['domain' => 'A domain is required for the selected hosting service. Register, transfer, or attach an existing domain first.']);
        }

        if ($this->recheckLivePrices($cart)) {
            $this->promotions->refresh($cart, Auth::user());
            return back()->with('status', 'A price changed. Your cart has been updated. Please review it before placing the order.');
        }

        $this->promotions->refresh($cart, Auth::user());
        $cart->refresh()->load('items');

        $paymentMethodSlugs = $this->paymentMethods($cart->currency)->pluck('slug')->filter()->values()->all();
        if (empty($paymentMethodSlugs)) {
            return back()->withErrors([
                'payment_method' => 'Payment methods are not available right now. Please contact support.',
            ])->withInput();
        }

        $requiresCompany = $cart->items->contains(fn ($item) => $item->item_type === 'domain_register');
        $checkoutDetails = (array) $request->session()->get('checkout_details', []);

        if (empty($checkoutDetails)) {
            return redirect()->to(route('checkout.index', ['step' => 'account']).'#checkout-step')
                ->withErrors(['checkout' => 'Complete account and billing details before selecting payment.']);
        }

        foreach (['street_address', 'city', 'state', 'postcode', 'country'] as $requiredField) {
            if (empty($checkoutDetails[$requiredField])) {
                return redirect()->to(route('checkout.index', ['step' => 'account']).'#checkout-step')
                    ->withErrors(['checkout' => 'Billing address is incomplete.']);
            }
        }

        if ($requiresCompany && empty($checkoutDetails['company_name'])) {
            return redirect()->to(route('checkout.index', ['step' => 'account']).'#checkout-step')
                ->withErrors(['checkout' => 'Company name is required for domain registration.']);
        }

        $isBankTransfer = $this->isManualBankPaymentMethod((string) $request->input('payment_method'));
        $paymentData = $request->validate([
            'payment_method' => ['required', 'string', 'max:100', Rule::in($paymentMethodSlugs)],
            'payment_channel' => [$isBankTransfer ? 'required' : 'nullable', Rule::in(array_keys(config('zt.manual_payments.bank_channels', [])))],
            'payment_proof' => [$isBankTransfer ? 'required' : 'nullable', 'file', 'mimes:jpg,jpeg,png,webp,pdf', 'max:'.config('zt.manual_payments.max_proof_kb', 5120)],
            'payment_note' => ['nullable', 'string', 'max:1000'],
            'customer_note' => ['nullable', 'string', 'max:2000'],
            'terms' => ['accepted'],
        ]);

        $data = array_merge($checkoutDetails, $paymentData);

        try {
            $checkoutResult = DB::transaction(function () use ($request, $cart, $data) {
                $user = Auth::user() ?: $this->createCheckoutUser($data, $cart->currency);

                if (! Auth::check()) {
                    Auth::login($user);
                    $request->session()->regenerate();
                }

                $this->saveProfile($user, $data);
                $this->lockUserCurrencyForOrder($user, $cart);
                $cart->refresh()->load('items');

                $subtotal = (float) $cart->items->sum('total');
                $discount = min(max((float) $cart->discount, 0), $subtotal);
                $total = max(0, round($subtotal - $discount, 2));
                $order = Order::create([
                    'user_id' => $user->id,
                    'order_number' => 'ORD-'.now()->format('Ymd').'-'.strtoupper(Str::random(6)),
                    'currency' => $cart->currency,
                    'subtotal' => $subtotal,
                    'discount' => $discount,
                    'tax' => 0,
                    'total' => $total,
                    'status' => 'pending',
                    'payment_method' => $data['payment_method'],
                    'customer_note' => $data['customer_note'] ?? null,
                    'price_locked_until' => now()->addHours(48),
                ]);

                $invoice = Invoice::create([
                    'user_id' => $user->id,
                    'order_id' => $order->id,
                    'invoice_number' => 'INV-'.now()->format('Ymd').'-'.strtoupper(Str::random(6)),
                    'currency' => $cart->currency,
                    'subtotal' => $subtotal,
                    'discount' => $discount,
                    'tax' => 0,
                    'total' => $total,
                    'status' => 'unpaid',
                    'due_date' => now()->addDays(2)->toDateString(),
                    'price_locked_until' => now()->addHours(48),
                ]);

                foreach ($cart->items as $item) {
                    $order->items()->create($item->only([
                        'item_type', 'api_pid', 'name', 'domain_name', 'billing_cycle', 'years', 'quantity', 'unit_price', 'total', 'metadata',
                    ]));

                    $invoice->items()->create([
                        'description' => trim($item->name.' '.($item->domain_name ? ' - '.$item->domain_name : '')),
                        'quantity' => $item->quantity,
                        'unit_price' => $item->unit_price,
                        'total' => $item->total,
                    ]);

                    if ($item->item_type === 'hosting') {
                        ClientService::create([
                            'user_id' => $user->id,
                            'order_id' => $order->id,
                            'api_pid' => $item->api_pid,
                            'name' => $item->name,
                            'domain_name' => $cart->items->first(fn ($i) => str_starts_with($i->item_type, 'domain'))?->domain_name,
                            'service_type' => 'hosting',
                            'status' => 'pending',
                            'billing_cycle' => $item->billing_cycle,
                            'amount' => $item->unit_price,
                            'currency' => $item->currency,
                            'next_due_date' => now()->addYear()->toDateString(),
                        ]);
                    }

                    if (str_starts_with($item->item_type, 'domain_') && $item->item_type !== 'domain_existing') {
                        Domain::create([
                            'user_id' => $user->id,
                            'order_id' => $order->id,
                            'domain_name' => $item->domain_name,
                            'tld' => $item->tld,
                            'operation' => $item->operation,
                            'status' => 'pending',
                            'registration_period' => $item->years,
                            'amount' => $item->unit_price,
                            'currency' => $item->currency,
                            'epp_code' => $item->metadata['epp_code'] ?? null,
                        ]);
                    }
                }

                if ($discount > 0 && $cart->promo_code) {
                    $invoice->items()->create([
                        'description' => 'Promo code '.$cart->promo_code,
                        'quantity' => 1,
                        'unit_price' => -$discount,
                        'total' => -$discount,
                    ]);
                }

                $syncResult = $this->whmcsBilling->syncCheckout($user, $order, $invoice, $cart, $data);
                if (! ($syncResult['ok'] ?? false) && (bool) config('zt.whmcs.block_checkout_on_sync_failure', true)) {
                    throw new RuntimeException('Your order could not be completed right now. Please try again or contact support.');
                }

                $cart->items()->delete();
                $this->promotions->clear($cart);
                $request->session()->forget('checkout_details');

                $invoice->refresh();

                $message = ($syncResult['mode'] ?? null) === 'dry_run'
                    ? 'Order placed successfully.'
                    : 'Order placed successfully.';

                return [
                    'invoice_id' => $invoice->id,
                    'message' => $message,
                    'payment_method' => $data['payment_method'],
                    'whmcs_invoice_id' => (int) ($invoice->whmcs_invoice_id ?: ($syncResult['invoice_id'] ?? 0)),
                ];
            });
        } catch (RuntimeException $e) {
            report($e);
            return back()->withErrors(['checkout' => 'Your order could not be completed right now. Please try again or contact support.'])->withInput();
        }

        $invoice = Invoice::where('user_id', Auth::id())->find($checkoutResult['invoice_id'] ?? null);
        $message = $checkoutResult['message'] ?? 'Order placed successfully.';

        if (! $invoice) {
            return redirect()->route('client.invoices')
                ->with('status', $message)
                ->with('error', 'The local invoice could not be opened. Please select it from the invoice list.');
        }

        $selectedPaymentMethod = (string) ($checkoutResult['payment_method'] ?? '');

        if ($this->isManualBankPaymentMethod($selectedPaymentMethod) && $request->hasFile('payment_proof')) {
            $file = $request->file('payment_proof');
            try {
                $this->support->openPaymentTicket(Auth::user(), $invoice, $paymentData['payment_channel'], $file, $paymentData['payment_note'] ?? null);
                $message .= ' Payment proof sent to Billing.';
            } catch (RuntimeException $e) {
                return redirect()->route('client.invoices.show', $invoice)
                    ->with('status', $message)
                    ->with('error', 'Order created, but the payment proof could not be submitted. Please upload it from the invoice page or contact support.');
            }
        }

        if ($this->isPayFastPaymentMethod($selectedPaymentMethod)) {
            if ((int) ($checkoutResult['whmcs_invoice_id'] ?? 0) <= 0) {
                return redirect()->route('client.invoices.show', $invoice)
                    ->with('status', $message)
                    ->with('error', 'Online payment is not available for this invoice yet. Please refresh the invoice or contact support.');
            }

            return redirect()->route('client.invoices.payfast', $invoice)
                ->with('status', $message);
        }

        return redirect()->route('client.invoices.show', $invoice)
            ->with('status', $message);
    }


    protected function cart(Request $request, array $currencyInfo): Cart
    {
        $sessionId = $request->session()->getId();

        if (Auth::check()) {
            $guestCart = Cart::with('items')->where('session_id', $sessionId)->first();
            $cart = Cart::firstOrCreate(
                ['user_id' => Auth::id()],
                [
                    'currency' => $guestCart?->currency ?: $currencyInfo['currency'],
                    'detected_country' => $currencyInfo['country'],
                    'currency_source' => $currencyInfo['source'],
                ]
            );

            if ($guestCart && $guestCart->id !== $cart->id) {
                foreach ($guestCart->items as $item) {
                    $item->update(['cart_id' => $cart->id]);
                }
                if ($guestCart->promo_code && ! $cart->promo_code) {
                    $cart->update($guestCart->only([
                        'promo_code', 'promo_id', 'promo_type', 'promo_value', 'discount', 'promo_metadata',
                    ]));
                }
                $guestCart->delete();
            }
        } else {
            $cart = Cart::firstOrCreate(
                ['session_id' => $sessionId],
                [
                    'currency' => $currencyInfo['currency'],
                    'detected_country' => $currencyInfo['country'],
                    'currency_source' => $currencyInfo['source'],
                ]
            );
        }

        if ($cart->currency !== $currencyInfo['currency']) {
            $cart->update([
                'currency' => $currencyInfo['currency'],
                'detected_country' => $currencyInfo['country'],
                'currency_source' => $currencyInfo['source'],
            ]);

            $cart->load('items');
            $this->refreshCartItemPrices($cart);
            $this->promotions->refresh($cart, Auth::user());
        } else {
            $cart->update([
                'detected_country' => $currencyInfo['country'],
                'currency_source' => $currencyInfo['source'],
            ]);
        }

        return $cart;
    }

    protected function refreshCartItemPrices(Cart $cart): void
    {
        foreach ($cart->items as $item) {
            try {
                if ($item->item_type === 'hosting') {
                    $cycle = $item->billing_cycle ?: 'annually';
                    $price = $this->pricing->productPrice((int) $item->api_pid, $cycle, $cart->currency);
                    $product = $this->pricing->findProductByPid((int) $item->api_pid, $cart->currency);
                    $config = $product
                        ? $this->pricing->calculateConfigurationPrice($product, $cart->currency, $cycle, $this->selectedConfigurationValues($item))
                        : ['total' => 0, 'selected' => []];
                    $metadata = array_replace_recursive(
                        (array) $item->metadata,
                        $price['metadata'],
                        [
                            'description' => $price['description'],
                            'config_options' => $config['selected'],
                        ]
                    );
                    $quantity = max((int) $item->quantity, 1);

                    $item->update([
                        'currency' => $cart->currency,
                        'unit_price' => $price['unit_price'],
                        'total' => round(($price['unit_price'] + $config['total']) * $quantity, 2),
                        'price_source' => 'api_live',
                        'price_checked_at' => now(),
                        'metadata' => $metadata,
                    ]);
                }

                if (in_array($item->item_type, ['domain_register', 'domain_transfer'], true)) {
                    $price = $this->pricing->domainPrice($item->domain_name, $item->operation, (int) $item->years, $cart->currency);
                    $item->update([
                        'currency' => $cart->currency,
                        'base_usd_price' => $price['base_usd_price'],
                        'unit_price' => $price['unit_price'],
                        'total' => $price['unit_price'],
                        'price_source' => $price['price_source'],
                        'price_checked_at' => now(),
                    ]);
                }

                if ($item->item_type === 'domain_existing') {
                    $item->update([
                        'currency' => $cart->currency,
                        'unit_price' => 0,
                        'total' => 0,
                    ]);
                }
            } catch (\Throwable $e) {
                report($e);
            }
        }
    }

    protected function addProductToCart(Cart $cart, int $pid, string $cycle, array $input = []): void
    {
        $price = $this->pricing->productPrice($pid, $cycle, $cart->currency);
        $product = $this->pricing->findProductByPid($pid, $cart->currency);
        if (! $product) {
            throw new RuntimeException('Product not found.');
        }

        if ($this->pricing->productRequiresServerConfiguration($product)) {
            $hostname = strtolower(trim((string) ($input['hostname'] ?? '')));
            $rootPassword = (string) ($input['root_password'] ?? '');
            $ns1Prefix = strtolower(trim((string) ($input['ns1_prefix'] ?? '')));
            $ns2Prefix = strtolower(trim((string) ($input['ns2_prefix'] ?? '')));

            if (! preg_match('/^(?=.{1,253}$)(?:[a-z0-9](?:[a-z0-9-]{0,61}[a-z0-9])?\.)+[a-z]{2,63}$/i', $hostname)) {
                throw new RuntimeException('Enter a valid server hostname such as server1.example.com. An email address is not a hostname.');
            }
            if (strlen($rootPassword) < 8) {
                throw new RuntimeException('Root password must contain at least 8 characters.');
            }
            if (! preg_match('/^[a-z0-9][a-z0-9-]{0,62}$/i', $ns1Prefix) || ! preg_match('/^[a-z0-9][a-z0-9-]{0,62}$/i', $ns2Prefix)) {
                throw new RuntimeException('NS1 and NS2 prefixes are required and may contain only letters, numbers, and hyphens.');
            }
        }

        $configuration = $this->pricing->calculateConfigurationPrice($product, $cart->currency, $cycle, (array) ($input['config_options'] ?? []));
        $metadata = $price['metadata'] + [
            'description' => $price['description'],
            'requires_domain' => $this->pricing->productRequiresDomain($product),
            'config_options' => $configuration['selected'],
            'server' => array_filter([
                'hostname' => $input['hostname'] ?? null,
                'root_password' => $input['root_password'] ?? null,
                'ns1_prefix' => $input['ns1_prefix'] ?? null,
                'ns2_prefix' => $input['ns2_prefix'] ?? null,
            ]),
        ];
        $total = round($price['unit_price'] + $configuration['total'], 2);

        $cart->items()->updateOrCreate(
            ['api_pid' => $pid, 'item_type' => 'hosting'],
            [
                'name' => $price['name'], 'billing_cycle' => $cycle, 'quantity' => 1,
                'currency' => $cart->currency, 'unit_price' => $price['unit_price'], 'total' => $total,
                'price_source' => 'api_live', 'price_checked_at' => now(), 'metadata' => $metadata,
            ]
        );
    }

    protected function recheckLivePrices(Cart $cart): bool
    {
        $changed = false;

        foreach ($cart->items as $item) {
            if ($item->item_type === 'hosting') {
                $cycle = $item->billing_cycle ?: 'annually';
                $price = $this->pricing->productPrice((int) $item->api_pid, $cycle, $cart->currency);
                $product = $this->pricing->findProductByPid((int) $item->api_pid, $cart->currency);
                $config = $product
                    ? $this->pricing->calculateConfigurationPrice($product, $cart->currency, $cycle, $this->selectedConfigurationValues($item))
                    : ['total' => 0, 'selected' => []];
                $quantity = max((int) $item->quantity, 1);
                $newTotal = round(($price['unit_price'] + $config['total']) * $quantity, 2);
                if ((float) $item->unit_price !== (float) $price['unit_price'] || (float) $item->total !== (float) $newTotal) {
                    $changed = true;
                }

                $item->update([
                    'currency' => $cart->currency,
                    'unit_price' => $price['unit_price'],
                    'total' => $newTotal,
                    'price_checked_at' => now(),
                    'metadata' => array_replace_recursive(
                        (array) $item->metadata,
                        $price['metadata'],
                        [
                            'description' => $price['description'],
                            'config_options' => $config['selected'],
                        ]
                    ),
                ]);
            }

            if (in_array($item->item_type, ['domain_register', 'domain_transfer'], true)) {
                $price = $this->pricing->domainPrice($item->domain_name, $item->operation, (int) $item->years, $cart->currency);
                if ((float) $item->unit_price !== (float) $price['unit_price'] || (float) $item->total !== (float) $price['unit_price']) {
                    $changed = true;
                }

                $item->update([
                    'currency' => $cart->currency,
                    'base_usd_price' => $price['base_usd_price'],
                    'unit_price' => $price['unit_price'],
                    'total' => $price['unit_price'],
                    'price_source' => $price['price_source'],
                    'price_checked_at' => now(),
                ]);
            }
        }

        return $changed;
    }

    protected function cartIsMissingRequiredDomain(Cart $cart): bool
    {
        $cart->loadMissing('items');

        $requiresDomain = $cart->items->contains(
            fn ($item) => $item->item_type === 'hosting'
                && (bool) data_get($item->metadata, 'requires_domain', false)
        );
        $hasDomain = $cart->items->contains(
            fn ($item) => str_starts_with((string) $item->item_type, 'domain_')
                && filled($item->domain_name)
        );

        return $requiresDomain && ! $hasDomain;
    }

    protected function selectedConfigurationValues(CartItem $item): array
    {
        return collect((array) data_get($item->metadata, 'config_options', []))
            ->mapWithKeys(fn ($value, $key) => [
                $key => ($value['type'] ?? null) === 4
                    ? ($value['quantity'] ?? $value['value'] ?? 0)
                    : ($value['value'] ?? $value['option_id'] ?? null),
            ])
            ->all();
    }

    protected function lockUserCurrencyForOrder(User $user, Cart $cart): void
    {
        if (! config('zt.currency.lock_after_first_order', true)) {
            return;
        }

        $lockedCurrency = $this->lockedCurrencyForUser($user) ?: strtoupper($cart->currency);
        $source = $user->currency_locked_at ? ($user->currency_source ?: 'account_locked') : 'first_order';

        if ($cart->currency !== $lockedCurrency) {
            $this->forceCartCurrency($cart, $lockedCurrency, $user->country, 'account_locked');
        }

        if (! $user->currency_locked_at || strtoupper((string) $user->currency) !== $lockedCurrency) {
            $user->forceFill([
                'currency' => $lockedCurrency,
                'currency_locked_at' => $user->currency_locked_at ?: now(),
                'currency_source' => $source,
            ])->save();
        }
    }

    protected function lockedCurrencyForUser(User $user): ?string
    {
        $currency = strtoupper((string) $user->currency);
        if ($user->currency_locked_at && in_array($currency, ['USD', 'PKR'], true)) {
            return $currency;
        }

        $firstOrderCurrency = strtoupper((string) $user->orders()->orderBy('id')->value('currency'));
        if (in_array($firstOrderCurrency, ['USD', 'PKR'], true)) {
            $user->forceFill([
                'currency' => $firstOrderCurrency,
                'currency_locked_at' => now(),
                'currency_source' => 'first_order_existing',
            ])->save();

            return $firstOrderCurrency;
        }

        return null;
    }

    protected function forceCartCurrency(Cart $cart, string $currency, ?string $country = null, string $source = 'account_locked'): void
    {
        $cart->update([
            'currency' => strtoupper($currency),
            'detected_country' => $country,
            'currency_source' => $source,
        ]);

        $cart->load('items');
        $this->refreshCartItemPrices($cart);
    }

    protected function createCheckoutUser(array $data, string $currency): User
    {
        return User::create([
            'name' => trim($data['first_name'].' '.$data['last_name']),
            'first_name' => $data['first_name'],
            'last_name' => $data['last_name'],
            'email' => $data['email'],
            'phone_country_code' => $data['phone_country_code'] ?? null,
            'phone' => $data['phone'] ?? null,
            'currency' => strtoupper($currency),
            'currency_source' => 'checkout_detected',
            'country' => $data['country'],
            'password' => Hash::make($data['password']),
        ]);
    }

    protected function saveProfile(User $user, array $data): void
    {
        $user->update([
            'phone_country_code' => $data['phone_country_code'] ?? $user->phone_country_code,
            'phone' => $data['phone'] ?? $user->phone,
            'country' => $data['country'] ?? $user->country,
        ]);

        CustomerProfile::updateOrCreate(
            ['user_id' => $user->id],
            [
                'company_name' => $data['company_name'] ?? null,
                'street_address' => $data['street_address'] ?? null,
                'street_address_2' => $data['street_address_2'] ?? null,
                'city' => $data['city'] ?? null,
                'state' => $data['state'] ?? null,
                'postcode' => $data['postcode'] ?? null,
                'country' => $data['country'] ?? null,
            ]
        );
    }

    protected function normalizeDomain(string $domain): string
    {
        $domain = strtolower(trim($domain));
        $domain = preg_replace('#^https?://#', '', $domain);
        $domain = explode('/', $domain)[0];
        $domain = preg_replace('/\s+/', '', $domain);

        if (! str_contains($domain, '.')) {
            $domain .= '.com';
        }

        return $domain;
    }

    protected function cartResponse(Cart $cart, ?string $message = null): JsonResponse
    {
        $cart->refresh()->load('items');

        $checkoutStep = request()->headers->get('X-ZT-Checkout-Step', request('step', 'cart'));
        $selectedGroup = request()->headers->get('X-ZT-Checkout-Group', request('group', 'web-hosting'));

        return response()->json([
            'ok' => true,
            'message' => $message,
            'item_count' => $cart->items->count(),
            'subtotal' => (float) $cart->items->sum('total'),
            'discount' => (float) $cart->discount,
            'total' => $cart->total(),
            'cart_html' => view('checkout.partials.cart-summary', [
                'cart' => $cart,
                'pricing' => $this->pricing,
                'checkoutStep' => $checkoutStep,
            ])->render(),
            'cart_items_html' => view('checkout.partials.cart-items', [
                'cart' => $cart,
                'pricing' => $this->pricing,
            ])->render(),
            'domain_section_html' => view('checkout.partials.domain-section', [
                'cart' => $cart,
                'pricing' => $this->pricing,
                'domainResult' => null,
                'selectedGroup' => $selectedGroup,
            ])->render(),
        ]);
    }

    protected function domainResultResponse(array $domainResult, Cart $cart): JsonResponse
    {
        return response()->json([
            'ok' => empty($domainResult['error']),
            'domain' => $domainResult['domain'] ?? null,
            'availability' => $domainResult['availability'] ?? null,
            'suggestion_candidates' => $domainResult['suggestion_candidates'] ?? [],
            'result_html' => view('checkout.partials.domain-result', [
                'domainResult' => $domainResult,
                'cart' => $cart,
                'pricing' => $this->pricing,
            ])->render(),
        ], empty($domainResult['error']) ? 200 : 422);
    }

    protected function wantsJson(Request $request): bool
    {
        return $request->expectsJson()
            || $request->ajax()
            || str_contains(strtolower((string) $request->header('Accept')), 'application/json');
    }

    protected function redirectToCheckoutAnchor(string $anchor): RedirectResponse
    {
        return redirect()->to(route('checkout.index').'#'.$anchor);
    }

    protected function redirectBackWithAnchor(string $anchor): RedirectResponse
    {
        $url = url()->previous();
        $url = preg_replace('/#.*/', '', $url) ?: route('checkout.index');

        return redirect()->to($url.'#'.$anchor);
    }

    protected function paymentMethods(?string $currency = null)
    {
        $currency = strtoupper(trim((string) $currency));

        return collect($this->api->paymentMethods())
            ->map(fn ($method) => (object) $method)
            ->filter(function ($method) use ($currency) {
                $normalized = strtolower((string) preg_replace('/[^a-z0-9]+/i', '', (string) ($method->slug ?? '')));
                if (in_array($normalized, ['easypaisa', 'jazzcash'], true)) {
                    return false; // Both wallets are choices inside Bank Transfer.
                }
                if (! $this->isPayFastPaymentMethod($method->slug ?? null)) {
                    return true;
                }

                return $currency === 'PKR' && (bool) config('zt.payfast.enabled', false);
            })
            ->values();
    }

    protected function isPayFastPaymentMethod(?string $method): bool
    {
        $normalize = static fn ($value) => strtolower((string) preg_replace('/[^a-z0-9]+/i', '', trim((string) $value)));
        $selected = $normalize($method);

        if ($selected === '') {
            return false;
        }

        $aliases = array_merge(
            (array) config('zt.payfast.checkout_method_aliases', ['payfast', 'gopayfast']),
            [(string) config('zt.payfast.gateway_name', 'payfast')]
        );

        return collect($aliases)
            ->map($normalize)
            ->filter()
            ->contains($selected);
    }

    protected function isManualBankPaymentMethod(?string $method): bool
    {
        $normalized = strtolower((string) preg_replace('/[^a-z0-9]+/i', '', trim((string) $method)));
        return in_array($normalized, ['banktransfer', 'bank'], true);
    }
}
