<?php

namespace App\Services;

use App\Models\Cart;
use App\Models\Invoice;
use App\Models\Order;
use App\Models\User;
use App\Models\WhmcsSyncLog;
use App\Models\WhmcsSyncOutbox;
use Illuminate\Support\Arr;
use Illuminate\Support\Facades\DB;
use RuntimeException;

class WhmcsBillingService
{
    public function __construct(
        private readonly ZtApiService $api,
        private readonly WhmcsAuthService $whmcsAuth
    ) {}

    public function syncCheckout(User $user, Order $order, Invoice $invoice, Cart $cart, array $checkoutData): array
    {
        $mode = $this->mode();

        if ($mode === 'disabled') {
            $this->markOrder($order, $invoice, 'disabled', 'WHMCS sync disabled by WHMCS_SYNC_MODE.');

            return ['ok' => true, 'mode' => $mode, 'message' => 'WHMCS sync disabled.'];
        }

        $clientResult = $this->resolveClient($user, $checkoutData, $order, $invoice);

        if (! $clientResult['ok']) {
            $this->markOrder($order, $invoice, 'client_sync_failed', $clientResult['message']);

            return $clientResult + ['mode' => $mode];
        }

        $payload = $this->buildAddOrderPayload($clientResult['client_id'], $cart, $checkoutData);

        if ($mode !== 'live' || ! (bool) config('zt.whmcs.create_orders', false)) {
            $this->outbox($user, $order, $invoice, 'AddOrder', $mode, 'pending', $payload, null, 'Order payload prepared only. No live AddOrder call was sent.');
            $this->log($user, $order, $invoice, 'AddOrder', $mode, 'dry_run', $payload, null, 'Order payload prepared only. No live AddOrder call was sent.');
            $this->markOrder($order, $invoice, 'whmcs_dry_run', 'WHMCS order payload logged but not sent.');

            return [
                'ok' => true,
                'mode' => $mode,
                'client_id' => $clientResult['client_id'],
                'message' => 'WHMCS dry run completed.',
            ];
        }

        $response = $this->api->mutation($payload);
        $success = strtolower((string) Arr::get($response, 'result')) === 'success';

        $this->outbox($user, $order, $invoice, 'AddOrder', $mode, $success ? 'sent' : 'failed', $payload, $response, Arr::get($response, 'message'));
        $this->log($user, $order, $invoice, 'AddOrder', $mode, $success ? 'success' : 'failed', $payload, $response, Arr::get($response, 'message'));

        if (! $success) {
            $message = Arr::get($response, 'message', 'WHMCS AddOrder failed.');
            $this->markOrder($order, $invoice, 'order_sync_failed', $message);

            return ['ok' => false, 'mode' => $mode, 'message' => $message, 'response' => $response];
        }

        $orderId = Arr::get($response, 'orderid');
        $invoiceId = Arr::get($response, 'invoiceid');

        $order->update([
            'whmcs_client_id' => $clientResult['client_id'],
            'whmcs_order_id' => $orderId,
            'whmcs_invoice_id' => $invoiceId,
            'whmcs_status' => 'created',
            'whmcs_sync_status' => 'synced',
            'whmcs_synced_at' => now(),
            'whmcs_payload' => $this->limitedPayload($response),
        ]);

        $invoice->update([
            'whmcs_client_id' => $clientResult['client_id'],
            'whmcs_invoice_id' => $invoiceId,
            'whmcs_status' => $invoiceId ? 'unpaid' : null,
            'whmcs_sync_status' => 'synced',
            'whmcs_synced_at' => now(),
            'whmcs_payload' => $this->limitedPayload($response),
        ]);

        $this->storeReturnedServiceIds($order, $response);
        $this->storeReturnedDomainIds($order, $response);

        if ((bool) config('zt.whmcs.auto_accept_orders', false) && $orderId) {
            $this->acceptOrder($user, $order, $invoice, (int) $orderId);
        }

        return [
            'ok' => true,
            'mode' => $mode,
            'client_id' => $clientResult['client_id'],
            'order_id' => $orderId,
            'invoice_id' => $invoiceId,
            'message' => 'WHMCS order created.',
        ];
    }

    protected function resolveClient(User $user, array $checkoutData, Order $order, Invoice $invoice): array
    {
        if ($user->whmcs_client_id) {
            return ['ok' => true, 'client_id' => (int) $user->whmcs_client_id, 'source' => 'local_link'];
        }

        $email = strtolower(trim((string) ($user->email ?: Arr::get($checkoutData, 'email'))));
        if ($email === '') {
            return ['ok' => false, 'message' => 'Cannot resolve WHMCS client because email is missing.'];
        }

        $existing = $this->findClientByEmail($email, $user, $order, $invoice);
        if ($existing) {
            $loginResponse = null;
            if (! empty($checkoutData['password'])) {
                $login = $this->whmcsAuth->validateCredentials($email, (string) $checkoutData['password']);

                if (! ($login['ok'] ?? false)) {
                    return [
                        'ok' => false,
                        'message' => 'A WHMCS account already exists with '.$email.', but the password entered in checkout is not valid for that WHMCS account. Please login first or reset the WHMCS password.',
                    ];
                }

                $loginResponse = $login['response'] ?? null;
            }

            $this->whmcsAuth->updateUserMirrorFromClient($user, $existing, $loginResponse);

            // Intentional safety rule: do not call UpdateClient for existing live clients.
            return ['ok' => true, 'client_id' => (int) ($existing['client_id'] ?? $existing['id']), 'source' => 'matched_existing_email'];
        }

        try {
            $payload = $this->buildAddClientPayload($user, $checkoutData);
        } catch (RuntimeException $e) {
            return ['ok' => false, 'message' => $e->getMessage()];
        }
        $mode = $this->mode();

        if (empty($payload['password2'])) {
            return [
                'ok' => false,
                'message' => 'Cannot create a WHMCS client without the customer password. Please logout and login again through WHMCS, or complete checkout as a new customer with signup password.',
            ];
        }

        if ($mode !== 'live') {
            $this->outbox($user, $order, $invoice, 'AddClient', $mode, 'pending', $payload, null, 'No exact WHMCS client found. AddClient was not sent.');
            $this->log($user, $order, $invoice, 'AddClient', $mode, 'dry_run', $payload, null, 'No exact WHMCS client found. AddClient was not sent.');

            return [
                'ok' => true,
                'client_id' => 0,
                'source' => 'dry_run_missing_client',
                'message' => 'No exact WHMCS client found. Dry-run client payload logged only.',
            ];
        }

        if (! (bool) config('zt.whmcs.create_missing_clients', false)) {
            $this->outbox($user, $order, $invoice, 'AddClient', $mode, 'blocked', $payload, null, 'No exact WHMCS client found. Live AddClient is disabled.');
            $this->log($user, $order, $invoice, 'AddClient', $mode, 'blocked', $payload, null, 'No exact WHMCS client found. Live AddClient is disabled.');

            return [
                'ok' => false,
                'message' => 'No exact WHMCS client found for '.$email.'. Live client creation is disabled for safety.',
            ];
        }

        $response = $this->api->mutation($payload);
        $success = strtolower((string) Arr::get($response, 'result')) === 'success';

        $this->outbox($user, $order, $invoice, 'AddClient', $mode, $success ? 'sent' : 'failed', $payload, $response, Arr::get($response, 'message'));
        $this->log($user, $order, $invoice, 'AddClient', $mode, $success ? 'success' : 'failed', $payload, $response, Arr::get($response, 'message'));

        if (! $success) {
            return ['ok' => false, 'message' => Arr::get($response, 'message', 'WHMCS AddClient failed.'), 'response' => $response];
        }

        $clientId = (int) Arr::get($response, 'clientid');
        $client = $this->whmcsAuth->clientDetailsByEmail($email);

        if ($client) {
            $this->whmcsAuth->updateUserMirrorFromClient($user, $client);
        } else {
            $user->forceFill([
                'whmcs_client_id' => $clientId,
                'whmcs_email' => $email,
                'whmcs_sync_status' => 'created',
                'whmcs_synced_at' => now(),
                'whmcs_payload' => $this->limitedPayload($response),
            ])->save();
        }

        return ['ok' => true, 'client_id' => $clientId, 'source' => 'created'];
    }

    protected function findClientByEmail(string $email, User $user, Order $order, Invoice $invoice): ?array
    {
        $payload = [
            'action' => 'GetClients',
            'search' => $email,
            'limitnum' => 10,
        ];

        $response = $this->api->request($payload, 60);
        $success = strtolower((string) Arr::get($response, 'result')) === 'success';

        $this->log($user, $order, $invoice, 'GetClients', $this->mode(), $success ? 'success' : 'failed', $payload, $response, Arr::get($response, 'message'));

        if (! $success) {
            return null;
        }

        $clients = Arr::get($response, 'clients.client', []);
        if (! is_array($clients)) {
            return null;
        }

        if (isset($clients['id'])) {
            $clients = [$clients];
        }

        foreach ($clients as $client) {
            $clientEmail = strtolower(trim((string) Arr::get($client, 'email')));
            if ($clientEmail === $email && (int) Arr::get($client, 'id') > 0) {
                return $client;
            }
        }

        return null;
    }

    protected function buildAddClientPayload(User $user, array $data): array
    {
        $phone = trim((string) (($data['phone_country_code'] ?? $user->phone_country_code).' '.($data['phone'] ?? $user->phone)));
        $currency = strtoupper((string) ($user->currency ?: config('zt.currency.default', 'USD')));
        $currencyId = $this->api->currencyId($currency);

        if (! $currencyId) {
            throw new RuntimeException('WHMCS currency '.$currency.' is not configured or GetCurrencies is unavailable.');
        }

        return array_filter([
            'action' => 'AddClient',
            'firstname' => $data['first_name'] ?? $user->first_name ?? $user->name,
            'lastname' => $data['last_name'] ?? $user->last_name ?? '-',
            'companyname' => $data['company_name'] ?? null,
            'email' => $data['email'] ?? $user->email,
            'address1' => $data['street_address'] ?? null,
            'address2' => $data['street_address_2'] ?? null,
            'city' => $data['city'] ?? null,
            'state' => $data['state'] ?? null,
            'postcode' => $data['postcode'] ?? null,
            'country' => strtoupper((string) ($data['country'] ?? $user->country ?? 'US')),
            'phonenumber' => $phone ?: null,
            'currency' => $currencyId,
            'password2' => $data['password'] ?? null,
            'noemail' => true,
        ], fn ($value) => $value !== null && $value !== '');
    }

    protected function buildAddOrderPayload(int|string $clientId, Cart $cart, array $checkoutData): array
    {
        $cart->loadMissing('items');
        $payload = [
            'action' => 'AddOrder',
            'clientid' => $clientId,
            'paymentmethod' => $this->paymentMethod($checkoutData['payment_method'] ?? null),
        ];

        if ((bool) config('zt.whmcs.add_order_no_email', true)) {
            $payload['noemail'] = true;
        }

        if ($cart->promo_code) {
            $payload['promocode'] = (string) $cart->promo_code;
        }

        $hostingItems = $cart->items->where('item_type', 'hosting')->values();
        $domainItems = $cart->items->filter(fn ($item) => str_starts_with((string) $item->item_type, 'domain_'))->values();
        $primaryDomain = $domainItems->first();

        $index = 0;
        foreach ($hostingItems as $hosting) {
            $payload['pid['.$index.']'] = (int) $hosting->api_pid;
            $payload['billingcycle['.$index.']'] = $hosting->billing_cycle ?: 'annually';

            $metadata = (array) $hosting->metadata;
            $configValues = [];
            foreach ((array) Arr::get($metadata, 'config_options', []) as $configId => $selected) {
                $type = (int) ($selected['type'] ?? 1);
                if ($type === 4) {
                    $configValues[(int) $configId] = [
                        'optionid' => (int) ($selected['option_id'] ?? 0),
                        'qty' => max(0, (int) ($selected['quantity'] ?? $selected['value'] ?? 0)),
                    ];
                    continue;
                }

                $configValues[(int) $configId] = $selected['value'] ?? $selected['option_id'] ?? null;
            }
            if ($configValues !== []) {
                $payload['configoptions['.$index.']'] = base64_encode(serialize($configValues));
            }

            $serverFieldMap = [
                'hostname' => 'hostname',
                'root_password' => 'rootpw',
                'ns1_prefix' => 'ns1prefix',
                'ns2_prefix' => 'ns2prefix',
            ];
            foreach ($serverFieldMap as $localField => $whmcsField) {
                $value = Arr::get($metadata, 'server.'.$localField);
                if ($value !== null && $value !== '') {
                    $payload[$whmcsField.'['.$index.']'] = $value;
                }
            }

            if ($primaryDomain?->domain_name) {
                $payload['domain['.$index.']'] = $primaryDomain->domain_name;
                $isExistingDomain = $primaryDomain->item_type === 'domain_existing'
                    || $primaryDomain->operation === 'existing';

                // WHMCS AddOrder only accepts register or transfer for
                // domaintype. For "use my existing domain", send only the
                // product's domain and omit all domain-order fields.
                if (! $isExistingDomain) {
                    $payload['domaintype['.$index.']'] = $this->whmcsDomainType($primaryDomain->item_type, $primaryDomain->operation);

                    if ($primaryDomain->years) {
                        $payload['regperiod['.$index.']'] = (int) $primaryDomain->years;
                    }

                    $eppCode = Arr::get((array) $primaryDomain->metadata, 'epp_code');
                    if ($eppCode) {
                        $payload['eppcode['.$index.']'] = $eppCode;
                    }
                }
            }

            $index++;
        }

        // Domain-only order support. If a domain is already attached to a hosting
        // product above, do not send it again as a separate domain-only row.
        if ($hostingItems->isEmpty()) {
            foreach ($domainItems as $domain) {
                if ($domain->item_type === 'domain_existing') {
                    continue;
                }

                $payload['domain['.$index.']'] = $domain->domain_name;
                $payload['domaintype['.$index.']'] = $this->whmcsDomainType($domain->item_type, $domain->operation);
                $payload['regperiod['.$index.']'] = (int) ($domain->years ?: 1);

                $eppCode = Arr::get((array) $domain->metadata, 'epp_code');
                if ($eppCode) {
                    $payload['eppcode['.$index.']'] = $eppCode;
                }

                $index++;
            }
        }

        return $payload;
    }

    protected function whmcsDomainType(?string $itemType, ?string $operation): string
    {
        if ($operation === 'transfer' || $itemType === 'domain_transfer') {
            return 'transfer';
        }

        return 'register';
    }

    protected function paymentMethod(?string $localMethod): string
    {
        $localMethod = trim((string) $localMethod);
        $map = config('zt.whmcs.payment_method_map', []);

        if ($localMethod !== '' && isset($map[$localMethod])) {
            return (string) $map[$localMethod];
        }

        return $localMethod !== ''
            ? $localMethod
            : (string) config('zt.whmcs.default_payment_method', 'banktransfer');
    }

    protected function acceptOrder(User $user, Order $order, Invoice $invoice, int $whmcsOrderId): void
    {
        $payload = [
            'action' => 'AcceptOrder',
            'orderid' => $whmcsOrderId,
        ];

        $response = $this->api->mutation($payload);
        $success = strtolower((string) Arr::get($response, 'result')) === 'success';

        $this->log($user, $order, $invoice, 'AcceptOrder', $this->mode(), $success ? 'success' : 'failed', $payload, $response, Arr::get($response, 'message'));

        $order->update([
            'whmcs_status' => $success ? 'accepted' : 'accept_failed',
            'whmcs_sync_status' => $success ? 'synced' : 'accept_failed',
            'whmcs_synced_at' => now(),
        ]);
    }

    protected function storeReturnedServiceIds(Order $order, array $response): void
    {
        $serviceIds = $this->idsFromResponse($response, 'serviceids');
        if (empty($serviceIds)) {
            return;
        }

        $services = $order->services()->orderBy('id')->get();
        foreach ($services as $index => $service) {
            if (! isset($serviceIds[$index])) {
                continue;
            }

            $service->update([
                'api_service_id' => $serviceIds[$index],
                'whmcs_service_id' => $serviceIds[$index],
                'whmcs_sync_status' => 'synced',
                'whmcs_synced_at' => now(),
            ]);
        }
    }

    protected function storeReturnedDomainIds(Order $order, array $response): void
    {
        $domainIds = $this->idsFromResponse($response, 'domainids');
        if (empty($domainIds)) {
            return;
        }

        $domains = $order->domains()->orderBy('id')->get();
        foreach ($domains as $index => $domain) {
            if (! isset($domainIds[$index])) {
                continue;
            }

            $domain->update([
                'api_domain_id' => $domainIds[$index],
                'whmcs_domain_id' => $domainIds[$index],
                'whmcs_sync_status' => 'synced',
                'whmcs_synced_at' => now(),
            ]);
        }
    }

    protected function idsFromResponse(array $response, string $key): array
    {
        $value = Arr::get($response, $key);

        if (is_array($value)) {
            return array_values(array_filter($value));
        }

        if (is_string($value)) {
            return array_values(array_filter(array_map('trim', explode(',', $value))));
        }

        return [];
    }

    protected function markOrder(Order $order, Invoice $invoice, string $status, ?string $message = null): void
    {
        $orderUpdates = [
            'whmcs_sync_status' => $status,
            'whmcs_sync_message' => $message,
            'whmcs_synced_at' => now(),
        ];

        $invoiceUpdates = [
            'whmcs_sync_status' => $status,
            'whmcs_sync_message' => $message,
            'whmcs_synced_at' => now(),
        ];

        if (str_contains($status, 'failed')) {
            $orderUpdates['status'] = 'sync_failed';
            $invoiceUpdates['status'] = 'sync_failed';
        }

        $order->update($orderUpdates);
        $invoice->update($invoiceUpdates);
    }

    protected function outbox(User $user, ?Order $order, ?Invoice $invoice, string $action, string $mode, string $status, array $requestPayload, ?array $responsePayload = null, ?string $message = null): void
    {
        try {
            WhmcsSyncOutbox::create([
                'user_id' => $user->id,
                'order_id' => $order?->id,
                'invoice_id' => $invoice?->id,
                'action' => $action,
                'mode' => $mode,
                'status' => $status,
                'request_payload' => $this->scrubPayload($requestPayload),
                'response_payload' => $this->limitedPayload($responsePayload),
                'attempt_count' => $status === 'sent' || $status === 'failed' ? 1 : 0,
                'next_attempt_at' => $status === 'pending' ? now()->addMinutes(5) : null,
                'sent_at' => $status === 'sent' ? now() : null,
                'message' => $message,
            ]);
        } catch (\Throwable $e) {
            report($e);
        }
    }

    protected function log(User $user, ?Order $order, ?Invoice $invoice, string $action, string $mode, string $status, array $requestPayload, ?array $responsePayload, ?string $message = null): void
    {
        try {
            WhmcsSyncLog::create([
                'user_id' => $user->id,
                'order_id' => $order?->id,
                'invoice_id' => $invoice?->id,
                'action' => $action,
                'mode' => $mode,
                'status' => $status,
                'whmcs_id' => $this->whmcsIdFromResponse($responsePayload),
                'request_payload' => $this->scrubPayload($requestPayload),
                'response_payload' => $this->limitedPayload($responsePayload),
                'message' => $message,
                'attempted_at' => now(),
            ]);
        } catch (\Throwable $e) {
            report($e);
        }
    }

    protected function whmcsIdFromResponse(?array $payload): ?string
    {
        if (! is_array($payload)) {
            return null;
        }

        foreach (['clientid', 'orderid', 'invoiceid', 'serviceid', 'domainid'] as $key) {
            if (! empty($payload[$key])) {
                return (string) $payload[$key];
            }
        }

        return null;
    }

    protected function scrubPayload(array $payload): array
    {
        foreach (['username', 'password', 'accesskey', 'secret', 'password2'] as $key) {
            if (array_key_exists($key, $payload)) {
                $payload[$key] = '[redacted]';
            }
        }

        return $payload;
    }

    protected function limitedPayload(?array $payload): ?array
    {
        if (! is_array($payload)) {
            return null;
        }

        return $this->scrubPayload($payload);
    }

    protected function mode(): string
    {
        $mode = strtolower((string) config('zt.whmcs.sync_mode', 'dry_run'));

        return in_array($mode, ['disabled', 'dry_run', 'live'], true) ? $mode : 'dry_run';
    }
}
