<?php

namespace App\Services;

use App\Models\CustomerProfile;
use App\Models\User;
use Illuminate\Support\Arr;
use Illuminate\Support\Facades\DB;
use Illuminate\Support\Str;

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

    public function loginAndMirror(string $email, string $password): array
    {
        $login = $this->validateCredentials($email, $password);

        if (! ($login['ok'] ?? false)) {
            return $login;
        }

        $client = $this->clientDetailsByEmail($email);
        $user = $this->mirrorUser($email, $password, $client, $login['response'] ?? []);

        return [
            'ok' => true,
            'user' => $user,
            'client' => $client,
            'response' => $login['response'] ?? [],
        ];
    }

    public function validateCredentials(string $email, string $password): array
    {
        $email = $this->normalizeEmail($email);

        if ($email === '' || $password === '') {
            return ['ok' => false, 'message' => trans('auth.failed')];
        }

        $payload = [
            'action' => 'ValidateLogin',
            'email' => $email,
            'password2' => $password,
        ];

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

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

        if (filter_var(Arr::get($response, 'twoFactorEnabled'), FILTER_VALIDATE_BOOLEAN)) {
            return [
                'ok' => false,
                'message' => 'This WHMCS account has two-factor authentication enabled. Login through the WHMCS client area or add WHMCS 2FA handling before using this portal login.',
                'response' => $response,
            ];
        }

        return [
            'ok' => true,
            'whmcs_user_id' => $this->intOrNull(Arr::get($response, 'userid')),
            'response' => $response,
        ];
    }

    public function registerAndMirror(array $data, string $currency): array
    {
        $email = $this->normalizeEmail((string) Arr::get($data, 'email'));
        $password = (string) Arr::get($data, 'password');

        if ($email === '' || $password === '') {
            return ['ok' => false, 'message' => 'Email and password are required.'];
        }

        $mode = strtolower((string) config('zt.whmcs.sync_mode', 'dry_run'));
        if ($mode !== 'live' || ! (bool) config('zt.whmcs.create_missing_clients', false)) {
            return [
                'ok' => false,
                'message' => 'WHMCS live client creation is disabled. Set WHMCS_SYNC_MODE=live and WHMCS_CREATE_MISSING_CLIENTS=true before allowing portal registration.',
            ];
        }

        if ($this->clientDetailsByEmail($email)) {
            return [
                'ok' => false,
                'message' => 'A WHMCS account already exists with this email. Please login using the WHMCS password or reset the WHMCS password.',
            ];
        }

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

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

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

        $client = $this->clientDetailsByEmail($email) ?: [
            'client_id' => Arr::get($response, 'clientid'),
            'id' => Arr::get($response, 'clientid'),
            'owner_user_id' => Arr::get($response, 'owner_user_id'),
            'firstname' => Arr::get($data, 'first_name'),
            'lastname' => Arr::get($data, 'last_name'),
            'email' => $email,
            'companyname' => Arr::get($data, 'company_name'),
            'address1' => Arr::get($data, 'street_address'),
            'address2' => Arr::get($data, 'street_address_2'),
            'city' => Arr::get($data, 'city'),
            'state' => Arr::get($data, 'state'),
            'postcode' => Arr::get($data, 'postcode'),
            'country' => strtoupper((string) Arr::get($data, 'country')),
            'countrycode' => strtoupper((string) Arr::get($data, 'country')),
            'phonenumber' => $this->phoneFromData($data),
            'currency_code' => strtoupper($currency),
        ];

        $login = $this->validateCredentials($email, $password);
        $user = $this->mirrorUser($email, $password, $client, $login['response'] ?? []);

        return [
            'ok' => true,
            'user' => $user,
            'client' => $client,
            'response' => $response,
        ];
    }


    public function sendPasswordReset(string $email): array
    {
        $email = $this->normalizeEmail($email);
        if ($email === '') {
            return ['ok' => false, 'message' => 'Please provide a valid email address.'];
        }

        $response = $this->api->mutation([
            'action' => 'ResetPassword',
            'email' => $email,
        ]);

        $success = strtolower((string) Arr::get($response, 'result')) === 'success';

        return [
            'ok' => $success,
            'message' => $success
                ? 'If the email exists in WHMCS, the WHMCS password reset instructions have been sent.'
                : (Arr::get($response, 'message') ?: 'Unable to send WHMCS password reset instructions.'),
            'response' => $response,
        ];
    }

    public function clientDetailsByEmail(string $email): ?array
    {
        $email = $this->normalizeEmail($email);
        if ($email === '') {
            return null;
        }

        $response = $this->api->request([
            'action' => 'GetClientsDetails',
            'email' => $email,
            'stats' => false,
        ], 60);

        if (strtolower((string) Arr::get($response, 'result')) === 'success') {
            $client = Arr::get($response, 'client');
            if (is_array($client) && $this->clientId($client)) {
                return $client;
            }

            if ($this->clientId($response)) {
                return $response;
            }
        }

        return $this->findClientByEmail($email);
    }

    public function mirrorUser(string $email, string $password, ?array $client = null, ?array $loginResponse = null): User
    {
        $email = $this->normalizeEmail($email);
        $client = is_array($client) ? $client : [];
        $loginResponse = is_array($loginResponse) ? $loginResponse : [];

        return DB::transaction(function () use ($email, $password, $client, $loginResponse) {
            $firstName = trim((string) (Arr::get($client, 'firstname') ?: Str::before($email, '@')));
            $lastName = trim((string) Arr::get($client, 'lastname')) ?: '-';
            $country = strtoupper((string) (Arr::get($client, 'countrycode') ?: Arr::get($client, 'country') ?: 'US'));
            $currency = strtoupper((string) (Arr::get($client, 'currency_code') ?: config('zt.currency.default', 'USD')));

            if (! in_array($currency, ['USD', 'PKR'], true)) {
                $currency = $country === strtoupper((string) config('zt.currency.local_country_code', 'PK'))
                    ? (string) config('zt.currency.local_currency', 'PKR')
                    : (string) config('zt.currency.default', 'USD');
            }

            $phone = $this->splitPhone((string) (Arr::get($client, 'telephoneNumber') ?: Arr::get($client, 'phonenumberformatted') ?: Arr::get($client, 'phonenumber')));
            $whmcsClientId = $this->clientId($client);
            $whmcsUserId = $this->intOrNull(Arr::get($loginResponse, 'userid'))
                ?: $this->intOrNull(Arr::get($client, 'owner_user_id'))
                ?: $this->ownerUserIdFromUsersList((array) Arr::get($client, 'users.user', []));

            $user = User::firstOrNew(['email' => $email]);
            $user->forceFill([
                'name' => trim($firstName.' '.$lastName) ?: $email,
                'first_name' => $firstName,
                'last_name' => $lastName,
                'email' => $email,
                'phone_country_code' => $phone['code'],
                'phone' => $phone['number'],
                'country' => $country,
                'currency' => $currency,
                'currency_source' => $user->exists ? 'whmcs_login' : 'registration',
                'currency_locked_at' => $user->currency_locked_at ?: now(),
                'password' => $password,
                'email_verified_at' => Arr::get($client, 'email_verified') ? now() : $user->email_verified_at,
                'whmcs_client_id' => $whmcsClientId,
                'whmcs_user_id' => $whmcsUserId,
                'whmcs_email' => $email,
                'whmcs_sync_status' => 'authenticated',
                'whmcs_synced_at' => now(),
                'whmcs_payload' => $this->limitedPayload($client),
            ])->save();

            CustomerProfile::updateOrCreate(
                ['user_id' => $user->id],
                [
                    'company_name' => Arr::get($client, 'companyname') ?: null,
                    'street_address' => Arr::get($client, 'address1') ?: null,
                    'street_address_2' => Arr::get($client, 'address2') ?: null,
                    'city' => Arr::get($client, 'city') ?: null,
                    'state' => Arr::get($client, 'state') ?: null,
                    'postcode' => Arr::get($client, 'postcode') ?: null,
                    'country' => $country,
                ]
            );

            return $user;
        });
    }

    public function updateUserMirrorFromClient(User $user, array $client, ?array $loginResponse = null): User
    {
        $email = $this->normalizeEmail((string) (Arr::get($client, 'email') ?: $user->email));

        return DB::transaction(function () use ($user, $client, $loginResponse, $email) {
            $phone = $this->splitPhone((string) (Arr::get($client, 'telephoneNumber') ?: Arr::get($client, 'phonenumberformatted') ?: Arr::get($client, 'phonenumber')));
            $country = strtoupper((string) (Arr::get($client, 'countrycode') ?: Arr::get($client, 'country') ?: $user->country ?: 'US'));
            $currency = strtoupper((string) (Arr::get($client, 'currency_code') ?: $user->currency ?: config('zt.currency.default', 'USD')));
            if (! in_array($currency, ['USD', 'PKR'], true)) {
                $currency = $user->currency ?: (string) config('zt.currency.default', 'USD');
            }

            $firstName = trim((string) (Arr::get($client, 'firstname') ?: $user->first_name ?: Str::before($email, '@')));
            $lastName = trim((string) (Arr::get($client, 'lastname') ?: $user->last_name ?: '-'));

            $user->forceFill([
                'name' => trim($firstName.' '.$lastName) ?: $user->name,
                'first_name' => $firstName,
                'last_name' => $lastName,
                'email' => $email,
                'phone_country_code' => $phone['code'] ?: $user->phone_country_code,
                'phone' => $phone['number'] ?: $user->phone,
                'country' => $country,
                'currency' => $currency,
                'currency_source' => 'whmcs_client',
                'currency_locked_at' => $user->currency_locked_at ?: now(),
                'whmcs_client_id' => $this->clientId($client) ?: $user->whmcs_client_id,
                'whmcs_user_id' => $this->intOrNull(Arr::get($loginResponse, 'userid'))
                    ?: $this->intOrNull(Arr::get($client, 'owner_user_id'))
                    ?: $user->whmcs_user_id,
                'whmcs_email' => $email,
                'whmcs_sync_status' => 'linked_existing',
                'whmcs_synced_at' => now(),
                'whmcs_payload' => $this->limitedPayload($client),
            ])->save();

            CustomerProfile::updateOrCreate(
                ['user_id' => $user->id],
                [
                    'company_name' => Arr::get($client, 'companyname') ?: null,
                    'street_address' => Arr::get($client, 'address1') ?: null,
                    'street_address_2' => Arr::get($client, 'address2') ?: null,
                    'city' => Arr::get($client, 'city') ?: null,
                    'state' => Arr::get($client, 'state') ?: null,
                    'postcode' => Arr::get($client, 'postcode') ?: null,
                    'country' => $country,
                ]
            );

            return $user;
        });
    }

    protected function buildAddClientPayload(array $data, string $currency): array
    {
        $currency = strtoupper(trim($currency));
        $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' => Arr::get($data, 'first_name'),
            'lastname' => Arr::get($data, 'last_name') ?: '-',
            'companyname' => Arr::get($data, 'company_name'),
            'email' => $this->normalizeEmail((string) Arr::get($data, 'email')),
            'address1' => Arr::get($data, 'street_address') ?: 'Not Provided',
            'address2' => Arr::get($data, 'street_address_2'),
            'city' => Arr::get($data, 'city') ?: 'Not Provided',
            'state' => Arr::get($data, 'state') ?: 'Not Provided',
            'postcode' => Arr::get($data, 'postcode') ?: '00000',
            'country' => strtoupper((string) (Arr::get($data, 'country') ?: 'US')),
            'phonenumber' => $this->phoneFromData($data) ?: '+0000000000',
            // WHMCS AddClient expects the numeric currency id, not USD/PKR text.
            'currency' => $currencyId,
            'password2' => Arr::get($data, 'password'),
            'noemail' => true,
        ], fn ($value) => $value !== null && $value !== '');
    }

    protected function findClientByEmail(string $email): ?array
    {
        $response = $this->api->request([
            'action' => 'GetClients',
            'search' => $email,
            'limitnum' => 10,
        ], 60);

        if (strtolower((string) Arr::get($response, 'result')) !== 'success') {
            return null;
        }

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

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

        foreach ($clients as $client) {
            if (! is_array($client)) {
                continue;
            }

            $clientEmail = $this->normalizeEmail((string) Arr::get($client, 'email'));
            if ($clientEmail === $email && $this->clientId($client)) {
                $details = $this->clientDetailsById((int) $this->clientId($client));

                return $details ?: $client;
            }
        }

        return null;
    }

    protected function clientDetailsById(int $clientId): ?array
    {
        if ($clientId <= 0) {
            return null;
        }

        $response = $this->api->request([
            'action' => 'GetClientsDetails',
            'clientid' => $clientId,
            'stats' => false,
        ], 60);

        if (strtolower((string) Arr::get($response, 'result')) !== 'success') {
            return null;
        }

        $client = Arr::get($response, 'client');
        if (is_array($client) && $this->clientId($client)) {
            return $client;
        }

        return $this->clientId($response) ? $response : null;
    }

    protected function clientId(array $client): ?int
    {
        return $this->intOrNull(Arr::get($client, 'client_id'))
            ?: $this->intOrNull(Arr::get($client, 'id'))
            ?: $this->intOrNull(Arr::get($client, 'userid'));
    }

    protected function ownerUserIdFromUsersList(array $users): ?int
    {
        if (isset($users['id'])) {
            $users = [$users];
        }

        foreach ($users as $user) {
            if (! is_array($user)) {
                continue;
            }

            if (filter_var(Arr::get($user, 'is_owner'), FILTER_VALIDATE_BOOLEAN)) {
                return $this->intOrNull(Arr::get($user, 'id'));
            }
        }

        return null;
    }

    protected function phoneFromData(array $data): string
    {
        $countryCode = trim((string) Arr::get($data, 'phone_country_code'));
        $phone = trim((string) Arr::get($data, 'phone'));

        return trim($countryCode.' '.$phone);
    }

    protected function splitPhone(string $phone): array
    {
        $phone = trim($phone);
        if ($phone === '') {
            return ['code' => null, 'number' => null];
        }

        $normalized = preg_replace('/[^0-9+]/', '', $phone) ?: $phone;
        if (str_starts_with($normalized, '+')) {
            if (preg_match('/^(\+\d{1,4})(\d+)$/', $normalized, $matches)) {
                return ['code' => $matches[1], 'number' => $matches[2]];
            }
        }

        return ['code' => null, 'number' => $normalized];
    }

    protected function normalizeEmail(string $email): string
    {
        return strtolower(trim($email));
    }

    protected function intOrNull(mixed $value): ?int
    {
        if ($value === null || $value === '' || ! is_numeric($value)) {
            return null;
        }

        $value = (int) $value;

        return $value > 0 ? $value : null;
    }

    protected function limitedPayload(array $payload): array
    {
        unset($payload['password'], $payload['password2'], $payload['passwordhash']);

        return $payload;
    }
}
