<?php

namespace App\Modules\Registrar\Internetbs;

use App\Core\Registrar\AbstractRegistrar;
use Illuminate\Support\Facades\Http;
use Illuminate\Support\Facades\Log;

class Internetbs extends AbstractRegistrar
{
    /**
     * Live API base URL
     */
    protected const API_LIVE_URL = 'https://api.internet.bs';

    /**
     * Test/sandbox API base URL
     */
    protected const API_TEST_URL = 'https://testapi.internet.bs';

    // -------------------------------------------------------------------------
    // Configuration
    // -------------------------------------------------------------------------

    /**
     * Return the configuration fields rendered in the admin registrar settings form.
     */
    public function getConfigFields(): array
    {
        return [
            'api_key' => [
                'type' => 'text',
                'label' => __('admin/modules.internetbs_field_api_key_label'),
                'required' => true,
                'description' => __('admin/modules.internetbs_field_api_key_description'),
                'placeholder' => 'your-api-key',
                'value' => $this->config['api_key'] ?? '',
            ],
            'password' => [
                'type' => 'password',
                'label' => __('admin/modules.internetbs_field_password_label'),
                'required' => true,
                'description' => __('admin/modules.internetbs_field_password_description'),
                'placeholder' => '********',
                'value' => $this->config['password'] ?? '',
            ],
            'test_mode' => [
                'type' => 'checkbox',
                'label' => __('admin/modules.internetbs_field_test_mode_label'),
                'required' => false,
                'description' => __('admin/modules.internetbs_field_test_mode_description'),
                'default' => true,
                'value' => $this->config['test_mode'] ?? true,
            ],
            'sync_next_due_date' => [
                'type' => 'checkbox',
                'label' => __('admin/modules.internetbs_field_sync_next_due_date_label'),
                'required' => false,
                'description' => __('admin/modules.internetbs_field_sync_next_due_date_description'),
                'default' => false,
                'value' => $this->config['sync_next_due_date'] ?? false,
            ],
        ];
    }

    // -------------------------------------------------------------------------
    // Abstract method implementations (mandatory)
    // -------------------------------------------------------------------------

    /**
     * Register a new domain.
     */
    public function registerDomain(array $params)
    {
        $domain = $params['domain'] ?? '';
        $period = $params['period'] ?? 1;

        if (empty($domain)) {
            return $this->errorResponse(__('admin/modules.internetbs_error_domain_required'));
        }

        // Build nameserver list
        $nsList = $this->buildNameserverList($params);

        // PRC-969: source WHOIS contacts from the canonical, populated contact
        // helper (each group falls back to client_* when a dedicated group is
        // absent). Maps to WHMCS's registrant/technical/admin/billing payload.
        $data = ['domain' => $domain]
            + $this->buildContactData($this->contact($params, 'registrant'), 'registrant')
            + $this->buildContactData($this->contact($params, 'tech'), 'technical')
            + $this->buildContactData($this->contact($params, 'admin'), 'admin')
            + $this->buildContactData($this->contact($params, 'billing'), 'billing');

        // WHMCS internetbs.php:434 — the *_organization key is only sent when a
        // company is present; a blank organization is rejected by some registries.
        $data = $this->stripEmptyOrganizations($data);

        if (! empty($nsList)) {
            $data['ns_list'] = implode(',', $nsList);
        }

        if (! empty($params['id_protection'])) {
            $data['privateWhois'] = 'FULL';
        }

        if ($period > 0) {
            $data['period'] = $period.'Y';
        }

        // Handle TLD-specific additional fields
        $data = $this->applyTldSpecificFields($data, $params);

        $result = $this->callApi('Domain/Create', $data);

        if ($result === false) {
            return $this->errorResponse(__('admin/modules.internetbs_error_connection'));
        }

        if ($this->isFailure($result)) {
            return $this->errorResponse($this->extractErrorMessage($result));
        }

        Log::info('Internetbs: Domain registered', [
            'action' => 'registered',
            'domain' => $domain,
            'period' => $period,
        ]);

        // Prefer the registry-returned expiry when the Create response carries it
        // (Domain/Create may echo `expiration`); otherwise fall back to a computed
        // date (matches WHMCS, which does not parse it either).
        $expiresAt = $this->parseApiDate($result['expiration'] ?? $result['product_0_expiration'] ?? null)
            ?? now()->addYears($period)->toISOString();

        return [
            'status' => 'success',
            'message' => __('admin/modules.internetbs_register_success'),
            'data' => [
                'domain' => $domain,
                'period' => $period,
                'registrar' => 'internetbs',
                'registered_at' => now()->toISOString(),
                'expires_at' => $expiresAt,
            ],
        ];
    }

    /**
     * Initiate a domain transfer.
     */
    public function transferDomain(array $params)
    {
        $domain = $params['domain'] ?? '';
        $eppCode = $params['epp_code'] ?? '';

        if (empty($domain)) {
            return $this->errorResponse(__('admin/modules.internetbs_error_domain_required'));
        }

        if (empty($eppCode)) {
            return $this->errorResponse(__('admin/modules.internetbs_error_epp_required'));
        }

        $nsList = $this->buildNameserverList($params);

        // PRC-969: source WHOIS contacts from the canonical, populated contact
        // helper (see registerDomain). WHMCS internetbs.php:810-817 uses the same
        // 4-group payload as Create and, like Create, only sends *_organization
        // when a company is present (stripEmptyOrganizations below).
        $data = ['domain' => $domain, 'transferAuthInfo' => $eppCode]
            + $this->buildContactData($this->contact($params, 'registrant'), 'registrant')
            + $this->buildContactData($this->contact($params, 'tech'), 'technical')
            + $this->buildContactData($this->contact($params, 'admin'), 'admin')
            + $this->buildContactData($this->contact($params, 'billing'), 'billing');

        $data = $this->stripEmptyOrganizations($data);

        if (! empty($nsList)) {
            $data['ns_list'] = implode(',', $nsList);
        }

        if (! empty($params['id_protection'])) {
            $data['privateWhois'] = 'FULL';
        }

        // Handle TLD-specific additional fields
        $data = $this->applyTldSpecificFields($data, $params);

        $result = $this->callApi('Domain/Transfer/Initiate', $data);

        if ($result === false) {
            return $this->errorResponse(__('admin/modules.internetbs_error_connection'));
        }

        if ($this->isFailure($result)) {
            return $this->errorResponse($this->extractErrorMessage($result));
        }

        Log::info('Internetbs: Domain transfer initiated', [
            'action' => 'transfer',
            'domain' => $domain,
        ]);

        return [
            'status' => 'success',
            'message' => __('admin/modules.internetbs_transfer_success'),
            'data' => [
                'domain' => $domain,
                'registrar' => 'internetbs',
                // WiseCP InternetBS.php:174 persists TRANSACTID as the entity id
                // for later transfer polling; surface it when the API returns it.
                'transaction_id' => $result['transactid'] ?? $result['TRANSACTID'] ?? null,
                'transfer_started_at' => now()->toISOString(),
                'estimated_completion' => now()->addDays(5)->toISOString(),
            ],
        ];
    }

    /**
     * Renew a domain.
     */
    public function renewDomain(array $params)
    {
        $domain = $params['domain'] ?? '';
        $period = $params['period'] ?? 1;

        if (empty($domain)) {
            return $this->errorResponse(__('admin/modules.internetbs_error_domain_required'));
        }

        $data = [
            'domain' => $domain,
        ];

        if ($period > 0) {
            $data['period'] = $period.'Y';
        }

        $result = $this->callApi('Domain/Renew', $data);

        if ($result === false) {
            return $this->errorResponse(__('admin/modules.internetbs_error_connection'));
        }

        if ($this->isFailure($result)) {
            return $this->errorResponse($this->extractErrorMessage($result));
        }

        Log::info('Internetbs: Domain renewed', [
            'action' => 'renewed',
            'domain' => $domain,
            'period' => $period,
        ]);

        return [
            'status' => 'success',
            'message' => __('admin/modules.internetbs_renew_success'),
            'data' => [
                'domain' => $domain,
                'period' => $period,
                'registrar' => 'internetbs',
                'renewed_at' => now()->toISOString(),
            ],
        ];
    }

    /**
     * Get nameservers for a domain.
     */
    public function getNameservers(array $params)
    {
        $domain = $params['domain'] ?? '';

        if (empty($domain)) {
            return $this->errorResponse(__('admin/modules.internetbs_error_domain_required'));
        }

        $result = $this->callApi('Domain/Info', ['domain' => $domain]);

        if ($result === false) {
            return $this->errorResponse(__('admin/modules.internetbs_error_connection'));
        }

        if ($this->isFailure($result)) {
            return $this->errorResponse($this->extractErrorMessage($result));
        }

        $nameservers = [];
        for ($i = 0; isset($result['nameserver_'.$i]); $i++) {
            $nameservers[] = $result['nameserver_'.$i];
        }

        return [
            'status' => 'success',
            'message' => __('admin/modules.internetbs_get_nameservers_success'),
            'data' => [
                'domain' => $domain,
                'nameservers' => $nameservers,
            ],
        ];
    }

    /**
     * Set nameservers for a domain.
     */
    public function setNameservers(array $params)
    {
        $domain = $params['domain'] ?? '';

        if (empty($domain)) {
            return $this->errorResponse(__('admin/modules.internetbs_error_domain_required'));
        }

        $nameservers = $params['nameservers'] ?? [];

        if (empty($nameservers) || ! is_array($nameservers)) {
            return $this->errorResponse(__('admin/modules.internetbs_error_nameservers_required'));
        }

        $data = [
            'domain' => $domain,
            'ns_list' => implode(',', array_filter(array_map('trim', $nameservers))),
        ];

        $result = $this->callApi('Domain/Update', $data);

        if ($result === false) {
            return $this->errorResponse(__('admin/modules.internetbs_error_connection'));
        }

        if ($this->isFailure($result)) {
            return $this->errorResponse($this->extractErrorMessage($result));
        }

        return [
            'status' => 'success',
            'message' => __('admin/modules.internetbs_set_nameservers_success'),
            'data' => [
                'domain' => $domain,
                'nameservers' => $nameservers,
                'updated_at' => now()->toISOString(),
            ],
        ];
    }

    /**
     * Get contact details for a domain.
     */
    public function getContactDetails(array $params)
    {
        $domain = $params['domain'] ?? '';

        if (empty($domain)) {
            return $this->errorResponse(__('admin/modules.internetbs_error_domain_required'));
        }

        $result = $this->callApi('Domain/Info', ['domain' => $domain]);

        if ($result === false) {
            return $this->errorResponse(__('admin/modules.internetbs_error_connection'));
        }

        if ($this->isFailure($result)) {
            return $this->errorResponse($this->extractErrorMessage($result));
        }

        $groups = [
            'Registrant' => [
                'firstname' => $result['contacts_registrant_firstname'] ?? '',
                'lastname' => $result['contacts_registrant_lastname'] ?? '',
                'company' => $result['contacts_registrant_organization'] ?? '',
                'email' => $result['contacts_registrant_email'] ?? '',
                'phone' => $result['contacts_registrant_phonenumber'] ?? '',
                'address' => $result['contacts_registrant_street'] ?? '',
                'address2' => $result['contacts_registrant_street1'] ?? '',
                'postal_code' => $result['contacts_registrant_postalcode'] ?? '',
                'city' => $result['contacts_registrant_city'] ?? '',
                'country' => $result['contacts_registrant_countrycode'] ?? '',
            ],
            'Admin' => [
                'firstname' => $result['contacts_admin_firstname'] ?? '',
                'lastname' => $result['contacts_admin_lastname'] ?? '',
                'company' => $result['contacts_admin_organization'] ?? '',
                'email' => $result['contacts_admin_email'] ?? '',
                'phone' => $result['contacts_admin_phonenumber'] ?? '',
                'address' => $result['contacts_admin_street'] ?? '',
                'address2' => $result['contacts_admin_street1'] ?? '',
                'postal_code' => $result['contacts_admin_postalcode'] ?? '',
                'city' => $result['contacts_admin_city'] ?? '',
                'country' => $result['contacts_admin_countrycode'] ?? '',
            ],
        ];

        // Technical contact may not always be present
        if (isset($result['contacts_technical_email'])) {
            $groups['Technical'] = [
                'firstname' => $result['contacts_technical_firstname'] ?? '',
                'lastname' => $result['contacts_technical_lastname'] ?? '',
                'company' => $result['contacts_technical_organization'] ?? '',
                'email' => $result['contacts_technical_email'] ?? '',
                'phone' => $result['contacts_technical_phonenumber'] ?? '',
                'address' => $result['contacts_technical_street'] ?? '',
                'address2' => $result['contacts_technical_street1'] ?? '',
                'postal_code' => $result['contacts_technical_postalcode'] ?? '',
                'city' => $result['contacts_technical_city'] ?? '',
                'country' => $result['contacts_technical_countrycode'] ?? '',
            ];
        }

        return [
            'status' => 'success',
            'message' => __('admin/modules.internetbs_get_contacts_success'),
            'groups' => $groups,
        ];
    }

    /**
     * Update contact details for a domain.
     */
    public function setContactDetails(array $params)
    {
        $domain = $params['domain'] ?? '';

        if (empty($domain)) {
            return $this->errorResponse(__('admin/modules.internetbs_error_domain_required'));
        }

        if (! isset($params['groups']) || ! is_array($params['groups'])) {
            return $this->errorResponse(__('admin/modules.internetbs_error_contact_groups_required'));
        }

        $groups = $params['groups'];
        $data = ['domain' => $domain];

        // Map Registrant contact
        if (isset($groups['Registrant'])) {
            $r = $groups['Registrant'];
            $data['registrant_firstname'] = $r['firstname'] ?? '';
            $data['registrant_lastname'] = $r['lastname'] ?? '';
            $data['registrant_organization'] = $r['company'] ?? '';
            $data['registrant_street'] = $r['address'] ?? '';
            $data['registrant_street2'] = $r['address2'] ?? '';
            $data['registrant_city'] = $r['city'] ?? '';
            $data['registrant_countrycode'] = $r['country'] ?? '';
            $data['registrant_postalcode'] = $r['postal_code'] ?? '';
            $data['registrant_email'] = $r['email'] ?? '';
            $data['registrant_phonenumber'] = $this->formatPhone($r['phone'] ?? '', $r['country'] ?? '');
            // WHMCS internetbs.php:1157/1161 — Update requires the requester IP.
            $clientIp = $params['client_ip'] ?? $params['clientip'] ?? '';
            if ($clientIp !== '') {
                $data['registrant_clientip'] = $clientIp;
            }
        }

        // Map Admin contact
        if (isset($groups['Admin'])) {
            $a = $groups['Admin'];
            $data['admin_firstname'] = $a['firstname'] ?? '';
            $data['admin_lastname'] = $a['lastname'] ?? '';
            $data['admin_organization'] = $a['company'] ?? '';
            $data['admin_street'] = $a['address'] ?? '';
            $data['admin_street2'] = $a['address2'] ?? '';
            $data['admin_city'] = $a['city'] ?? '';
            $data['admin_countrycode'] = $a['country'] ?? '';
            $data['admin_postalcode'] = $a['postal_code'] ?? '';
            $data['admin_email'] = $a['email'] ?? '';
            $data['admin_phonenumber'] = $this->formatPhone($a['phone'] ?? '', $a['country'] ?? '');
        }

        // Map Technical contact
        if (isset($groups['Technical'])) {
            $t = $groups['Technical'];
            $data['technical_firstname'] = $t['firstname'] ?? '';
            $data['technical_lastname'] = $t['lastname'] ?? '';
            $data['technical_organization'] = $t['company'] ?? '';
            $data['technical_street'] = $t['address'] ?? '';
            $data['technical_street2'] = $t['address2'] ?? '';
            $data['technical_city'] = $t['city'] ?? '';
            $data['technical_countrycode'] = $t['country'] ?? '';
            $data['technical_postalcode'] = $t['postal_code'] ?? '';
            $data['technical_email'] = $t['email'] ?? '';
            $data['technical_phonenumber'] = $this->formatPhone($t['phone'] ?? '', $t['country'] ?? '');
        }

        // Map Billing contact (falls back to admin)
        if (isset($groups['Billing'])) {
            $b = $groups['Billing'];
            $data['billing_firstname'] = $b['firstname'] ?? '';
            $data['billing_lastname'] = $b['lastname'] ?? '';
            $data['billing_organization'] = $b['company'] ?? '';
            $data['billing_street'] = $b['address'] ?? '';
            $data['billing_street2'] = $b['address2'] ?? '';
            $data['billing_city'] = $b['city'] ?? '';
            $data['billing_countrycode'] = $b['country'] ?? '';
            $data['billing_postalcode'] = $b['postal_code'] ?? '';
            $data['billing_email'] = $b['email'] ?? '';
            $data['billing_phonenumber'] = $this->formatPhone($b['phone'] ?? '', $b['country'] ?? '');
        }

        // WHMCS internetbs.php:1162-1195 — several registries reject certain
        // registrant fields on Update; strip them per-TLD to avoid API errors.
        $data = $this->applyContactUpdateTldRules($data, $domain);

        $result = $this->callApi('Domain/Update', $data);

        if ($result === false) {
            return $this->errorResponse(__('admin/modules.internetbs_error_connection'));
        }

        if ($this->isFailure($result)) {
            return $this->errorResponse($this->extractErrorMessage($result));
        }

        return [
            'status' => 'success',
            'message' => __('admin/modules.internetbs_set_contacts_success'),
            'data' => [
                'domain' => $domain,
                'updated_at' => now()->toISOString(),
            ],
        ];
    }

    // -------------------------------------------------------------------------
    // Optional method overrides
    // -------------------------------------------------------------------------

    /**
     * Get domain details.
     */
    public function getDomainDetails(array $params)
    {
        $domain = $params['domain'] ?? '';

        if (empty($domain)) {
            return $this->errorResponse(__('admin/modules.internetbs_error_domain_required'));
        }

        $result = $this->callApi('Domain/Info', ['domain' => $domain]);

        if ($result === false) {
            return $this->errorResponse(__('admin/modules.internetbs_error_connection'));
        }

        if ($this->isFailure($result)) {
            return $this->errorResponse($this->extractErrorMessage($result));
        }

        $nameservers = [];
        for ($i = 0; isset($result['nameserver_'.$i]); $i++) {
            $nameservers[] = $result['nameserver_'.$i];
        }

        return [
            'status' => 'success',
            'message' => __('admin/modules.internetbs_get_domain_details_success'),
            'data' => [
                'domain' => $domain,
                'status' => $result['domainstatus'] ?? 'unknown',
                'registered_at' => $this->parseApiDate($result['registrationdate'] ?? null),
                'expires_at' => $this->parseApiDate($result['expirationdate'] ?? null),
                'nameservers' => $nameservers,
                'registrar' => 'Internet.bs',
                'auto_renew' => isset($result['autorenew']) && strtoupper($result['autorenew']) === 'YES',
                'privacy_enabled' => isset($result['privatewhois']) && strtoupper($result['privatewhois']) !== 'DISABLED',
                'epp_code_available' => isset($result['transferauthinfo']),
            ],
        ];
    }

    /**
     * Get registrar lock status.
     */
    public function getDomainLock(array $params)
    {
        $domain = $params['domain'] ?? '';

        if (empty($domain)) {
            return $this->errorResponse(__('admin/modules.internetbs_error_domain_required'));
        }

        $result = $this->callApi('Domain/RegistrarLock/Status', ['domain' => $domain]);

        if ($result === false) {
            return $this->errorResponse(__('admin/modules.internetbs_error_connection'));
        }

        if ($this->isFailure($result)) {
            return $this->errorResponse($this->extractErrorMessage($result));
        }

        $locked = isset($result['registrar_lock_status'])
            && strtoupper($result['registrar_lock_status']) === 'LOCKED';

        return [
            'status' => 'success',
            'message' => __('admin/modules.internetbs_get_lock_success'),
            'data' => [
                'domain' => $domain,
                'locked' => $locked,
            ],
        ];
    }

    /**
     * Enable or disable registrar lock.
     */
    public function updateDomainLock(array $params)
    {
        $domain = $params['domain'] ?? '';

        if (empty($domain)) {
            return $this->errorResponse(__('admin/modules.internetbs_error_domain_required'));
        }

        $lockEnabled = $params['lock_status'] ?? true;
        $endpoint = $lockEnabled
            ? 'Domain/RegistrarLock/Enable'
            : 'Domain/RegistrarLock/Disable';

        $result = $this->callApi($endpoint, ['domain' => $domain]);

        if ($result === false) {
            return $this->errorResponse(__('admin/modules.internetbs_error_connection'));
        }

        if ($this->isFailure($result)) {
            return $this->errorResponse($this->extractErrorMessage($result));
        }

        return [
            'status' => 'success',
            'message' => __('admin/modules.internetbs_set_lock_success'),
            'data' => [
                'domain' => $domain,
                'locked' => $lockEnabled,
                'updated_at' => now()->toISOString(),
            ],
        ];
    }

    /**
     * Get EPP / transfer auth code.
     */
    public function getEppCode(array $params)
    {
        $domain = $params['domain'] ?? '';

        if (empty($domain)) {
            return $this->errorResponse(__('admin/modules.internetbs_error_domain_required'));
        }

        $result = $this->callApi('Domain/Info', ['domain' => $domain]);

        if ($result === false) {
            return $this->errorResponse(__('admin/modules.internetbs_error_connection'));
        }

        if ($this->isFailure($result)) {
            return $this->errorResponse($this->extractErrorMessage($result));
        }

        return [
            'status' => 'success',
            'message' => __('admin/modules.internetbs_get_epp_success'),
            'data' => [
                'domain' => $domain,
                'epp_code' => $result['transferauthinfo'] ?? '',
            ],
        ];
    }

    /**
     * Get DNS records for a domain.
     */
    public function getDNSRecords(array $params)
    {
        $domain = $params['domain'] ?? '';

        if (empty($domain)) {
            return $this->errorResponse(__('admin/modules.internetbs_error_domain_required'));
        }

        $result = $this->callApi('Domain/DnsRecord/List', ['domain' => $domain]);

        if ($result === false) {
            return $this->errorResponse(__('admin/modules.internetbs_error_connection'));
        }

        if ($this->isFailure($result)) {
            return $this->errorResponse($this->extractErrorMessage($result));
        }

        $records = $this->parseDnsRecords($result, $domain);

        return [
            'status' => 'success',
            'message' => __('admin/modules.internetbs_get_dns_success'),
            'data' => [
                'domain' => $domain,
                'records' => $records,
            ],
        ];
    }

    /**
     * Add a DNS record.
     */
    public function addDNSRecord(array $params)
    {
        $domain = $params['domain'] ?? '';
        $type = $params['type'] ?? '';
        $name = $params['name'] ?? '';
        $value = $params['value'] ?? '';

        if (empty($domain) || empty($type) || empty($value)) {
            return $this->errorResponse(__('admin/modules.internetbs_error_dns_params_required'));
        }

        // Build the full record name
        $fullRecordName = $this->buildFullRecordName($name, $domain);

        $data = [
            'fullrecordname' => $fullRecordName,
            'type' => strtoupper($type),
            'value' => $value,
        ];

        if (isset($params['ttl'])) {
            $data['ttl'] = $params['ttl'];
        }

        if (isset($params['priority']) && in_array(strtoupper($type), ['MX', 'SRV'])) {
            $data['priority'] = $params['priority'];
        }

        $result = $this->callApi('Domain/DnsRecord/Add', $data);

        if ($result === false) {
            return $this->errorResponse(__('admin/modules.internetbs_error_connection'));
        }

        if ($this->isFailure($result)) {
            return $this->errorResponse($this->extractErrorMessage($result));
        }

        return [
            'status' => 'success',
            'message' => __('admin/modules.internetbs_add_dns_success'),
            'data' => [
                'domain' => $domain,
                'type' => strtoupper($type),
                'name' => $name,
                'value' => $value,
                'ttl' => $params['ttl'] ?? 3600,
            ],
        ];
    }

    /**
     * Update a DNS record (Internet.bs uses remove + add approach).
     */
    public function updateDNSRecord(array $params)
    {
        $domain = $params['domain'] ?? '';

        if (empty($domain)) {
            return $this->errorResponse(__('admin/modules.internetbs_error_domain_required'));
        }

        // Internet.bs does not have a direct "update" endpoint for DNS records.
        // The approach is: remove old record, then add the new record.

        // Remove old record if enough information is provided
        if (! empty($params['old_name']) && ! empty($params['old_type'])) {
            $oldFullName = $this->buildFullRecordName($params['old_name'], $domain);
            $this->callApi('Domain/DnsRecord/Remove', [
                'FullRecordName' => $oldFullName,
                'type' => strtoupper($params['old_type']),
            ]);
        }

        // Add the new/updated record
        return $this->addDNSRecord($params);
    }

    /**
     * Delete a DNS record.
     */
    public function deleteDNSRecord(array $params)
    {
        $domain = $params['domain'] ?? '';
        $type = $params['type'] ?? '';
        $name = $params['name'] ?? '';

        if (empty($domain) || empty($type)) {
            return $this->errorResponse(__('admin/modules.internetbs_error_dns_params_required'));
        }

        $fullRecordName = $this->buildFullRecordName($name, $domain);

        $data = [
            'FullRecordName' => $fullRecordName,
            'type' => strtoupper($type),
        ];

        // If a specific value is provided, include it to target the exact record
        if (! empty($params['value'])) {
            $data['value'] = $params['value'];
        }

        $result = $this->callApi('Domain/DnsRecord/Remove', $data);

        if ($result === false) {
            return $this->errorResponse(__('admin/modules.internetbs_error_connection'));
        }

        if ($this->isFailure($result)) {
            return $this->errorResponse($this->extractErrorMessage($result));
        }

        return [
            'status' => 'success',
            'message' => __('admin/modules.internetbs_delete_dns_success'),
            'data' => [
                'domain' => $domain,
                'type' => strtoupper($type),
                'name' => $name,
                'deleted_at' => now()->toISOString(),
            ],
        ];
    }

    /**
     * Get WHOIS privacy / ID protection status.
     */
    public function getPrivacySettings(array $params)
    {
        $domain = $params['domain'] ?? '';

        if (empty($domain)) {
            return $this->errorResponse(__('admin/modules.internetbs_error_domain_required'));
        }

        $result = $this->callApi('Domain/PrivateWhois/Status', ['domain' => $domain]);

        if ($result === false) {
            return $this->errorResponse(__('admin/modules.internetbs_error_connection'));
        }

        if ($this->isFailure($result)) {
            return $this->errorResponse($this->extractErrorMessage($result));
        }

        $privacyEnabled = isset($result['privatewhoisstatus'])
            && strtoupper($result['privatewhoisstatus']) !== 'DISABLED';

        return [
            'status' => 'success',
            'message' => __('admin/modules.internetbs_get_privacy_success'),
            'data' => [
                'domain' => $domain,
                'privacy_enabled' => $privacyEnabled,
                'available' => true,
            ],
        ];
    }

    /**
     * Enable or disable WHOIS privacy / ID protection.
     */
    public function updatePrivacySettings(array $params)
    {
        $domain = $params['domain'] ?? '';

        if (empty($domain)) {
            return $this->errorResponse(__('admin/modules.internetbs_error_domain_required'));
        }

        $privacyEnabled = $params['privacy_enabled'] ?? true;
        $endpoint = $privacyEnabled
            ? 'Domain/PrivateWhois/Enable'
            : 'Domain/PrivateWhois/Disable';

        $result = $this->callApi($endpoint, ['domain' => $domain]);

        if ($result === false) {
            return $this->errorResponse(__('admin/modules.internetbs_error_connection'));
        }

        if ($this->isFailure($result)) {
            return $this->errorResponse($this->extractErrorMessage($result));
        }

        return [
            'status' => 'success',
            'message' => __('admin/modules.internetbs_set_privacy_success'),
            'data' => [
                'domain' => $domain,
                'privacy_enabled' => $privacyEnabled,
                'updated_at' => now()->toISOString(),
            ],
        ];
    }

    /**
     * Check domain availability.
     */
    public function checkDomainAvailability(array $params)
    {
        $domain = $params['domain'] ?? '';

        if (empty($domain)) {
            return $this->errorResponse(__('admin/modules.internetbs_error_domain_required'));
        }

        $result = $this->callApi('Domain/Check', ['domain' => $domain]);

        if ($result === false) {
            return $this->errorResponse(__('admin/modules.internetbs_error_connection'));
        }

        // WiseCP InternetBS.php:101 — Domain/Check returns FAILURE for some
        // not-registered names; treat that as AVAILABLE rather than a hard error.
        $status = strtoupper($result['status'] ?? '');
        $available = in_array($status, ['AVAILABLE', 'FAILURE'], true);

        // Premium detection (WiseCP InternetBS.php:103-107 price.ispremium).
        $isPremium = strtoupper($result['price_ispremium'] ?? $result['ispremium'] ?? 'NO') === 'YES';
        $premiumPrice = null;
        if ($isPremium) {
            $premiumPrice = [
                'amount' => (float) ($result['price_registration'] ?? $result['price_registration_1'] ?? 0),
                'currency' => $result['price_currency'] ?? '',
            ];
        }

        return [
            'status' => 'success',
            'message' => __('admin/modules.internetbs_check_availability_success'),
            'data' => [
                'domain' => $domain,
                'available' => $available,
                'premium' => $isPremium,
                'premium_price' => $premiumPrice,
                'checked_at' => now()->toISOString(),
            ],
        ];
    }

    /**
     * Suggest alternative domains via the Internet.bs Domain/Search endpoint.
     */
    public function checkDomainSuggestions(array $params)
    {
        $domain = $params['domain'] ?? '';

        if (empty($domain)) {
            return $this->errorResponse(__('admin/modules.internetbs_error_domain_required'));
        }

        // Internet.bs Domain/Search takes the query in `Query` and optionally a
        // comma-separated `Tld_list`; TEXT response uses domain_N_* rows.
        $searchData = ['Query' => $domain];
        if (! empty($params['tlds'])) {
            $tlds = is_array($params['tlds']) ? implode(',', $params['tlds']) : $params['tlds'];
            $searchData['Tld_list'] = $tlds;
        }

        $result = $this->callApi('Domain/Search', $searchData);

        if ($result === false) {
            return $this->errorResponse(__('admin/modules.internetbs_error_connection'));
        }

        if ($this->isFailure($result)) {
            return $this->errorResponse($this->extractErrorMessage($result));
        }

        $suggestions = [];
        for ($i = 0; isset($result['domain_'.$i.'_name']) || isset($result['domain_'.$i]); $i++) {
            $name = $result['domain_'.$i.'_name'] ?? $result['domain_'.$i] ?? '';
            if ($name === '') {
                continue;
            }
            $status = strtoupper($result['domain_'.$i.'_status'] ?? '');
            $suggestions[] = [
                'domain' => $name,
                'available' => in_array($status, ['AVAILABLE', ''], true),
            ];
        }

        return [
            'status' => 'success',
            'message' => __('admin/modules.internetbs_check_suggestions_success'),
            'data' => [
                'domain' => $domain,
                'suggestions' => $suggestions,
            ],
        ];
    }

    /**
     * Synchronise domain status and expiry date from registrar.
     */
    public function syncDomain(array $params)
    {
        $domain = $params['domain'] ?? '';

        if (empty($domain)) {
            return $this->errorResponse(__('admin/modules.internetbs_error_domain_required'));
        }

        $result = $this->callApi('Domain/Info', ['domain' => $domain]);

        if ($result === false) {
            return $this->errorResponse(__('admin/modules.internetbs_error_connection'));
        }

        if ($this->isFailure($result)) {
            return $this->errorResponse($this->extractErrorMessage($result));
        }

        $domainStatus = $result['domainstatus'] ?? '';
        $expirationDate = $this->parseApiDate($result['expirationdate'] ?? null);

        // Map Internet.bs status values to our internal statuses
        $status = 'active';
        if (in_array(strtoupper($domainStatus), ['EXPIRED', 'REDEMPTION', 'PENDING DELETE'])) {
            $status = 'expired';
        } elseif (strtoupper($domainStatus) === 'SUSPENDED') {
            $status = 'suspended';
        } elseif (strtoupper($domainStatus) === 'INACTIVE') {
            $status = 'inactive';
        }

        // Determine lock status
        $locked = false;
        if (isset($result['registrar_lock_status'])) {
            $locked = strtoupper($result['registrar_lock_status']) === 'LOCKED';
        }

        $syncData = [
            'domain' => $domain,
            'status' => $status,
            'expires_at' => $expirationDate,
            'auto_renew' => isset($result['autorenew']) && strtoupper($result['autorenew']) === 'YES',
            'locked' => $locked,
            'privacy_enabled' => isset($result['privatewhois']) && strtoupper($result['privatewhois']) !== 'DISABLED',
            'synced_at' => now()->toISOString(),
        ];

        // Include next due date sync flag
        if (! empty($this->config['sync_next_due_date'])) {
            $syncData['sync_next_due_date'] = true;
        }

        Log::info('Internetbs: Domain synced', [
            'action' => 'sync',
            'domain' => $domain,
            'status' => $status,
            'expires_at' => $expirationDate,
        ]);

        return [
            'status' => 'success',
            'message' => __('admin/modules.internetbs_sync_success'),
            'data' => $syncData,
        ];
    }

    /**
     * Poll transfer progress. WiseCP InternetBS.php:472 reuses Domain/Info: once
     * the domain resolves in the account with an active status the transfer has
     * landed; a PENDINGTRANSFER-style status keeps it pending.
     */
    public function syncDomainTransfer(array $params)
    {
        $domain = $params['domain'] ?? '';

        if (empty($domain)) {
            return $this->errorResponse(__('admin/modules.internetbs_error_domain_required'));
        }

        $result = $this->callApi('Domain/Info', ['domain' => $domain]);

        if ($result === false) {
            return $this->errorResponse(__('admin/modules.internetbs_error_connection'));
        }

        if ($this->isFailure($result)) {
            return $this->errorResponse($this->extractErrorMessage($result));
        }

        $domainStatus = strtoupper($result['domainstatus'] ?? '');

        $transferStatus = 'pending';
        if (str_contains($domainStatus, 'PENDING') || str_contains($domainStatus, 'TRANSFER')) {
            $transferStatus = 'pending';
        } elseif (in_array($domainStatus, ['EXPIRED', 'CANCELLED', 'REJECTED', 'FAILED'], true)) {
            $transferStatus = 'failed';
        } elseif ($domainStatus !== '' && $domainStatus !== 'UNKNOWN') {
            // Domain resolves in the account (e.g. OK/ACTIVE) → completed.
            $transferStatus = 'completed';
        }

        return [
            'status' => 'success',
            'message' => __('admin/modules.internetbs_sync_transfer_success'),
            'data' => [
                'domain' => $domain,
                'transfer_status' => $transferStatus,
                'expires_at' => $this->parseApiDate($result['expirationdate'] ?? null),
                'synced_at' => now()->toISOString(),
            ],
        ];
    }

    /**
     * Get child nameserver (glue record) list.
     */
    public function getChildNameservers(array $params)
    {
        $domain = $params['domain'] ?? '';

        if (empty($domain)) {
            return $this->errorResponse(__('admin/modules.internetbs_error_domain_required'));
        }

        // WiseCP InternetBS.php:328 / HostBill class.internetbs.php:735 —
        // CompactList=no is mandatory, otherwise the TEXT response omits the
        // indexed host_N rows and the glue list comes back empty.
        $result = $this->callApi('Domain/Host/List', [
            'domain' => $domain,
            'CompactList' => 'no',
        ]);

        if ($result === false) {
            return $this->errorResponse(__('admin/modules.internetbs_error_connection'));
        }

        if ($this->isFailure($result)) {
            return $this->errorResponse($this->extractErrorMessage($result));
        }

        $nameservers = [];
        $totalHosts = (int) ($result['total_hosts'] ?? 0);

        for ($i = 0; $i < $totalHosts; $i++) {
            if (! isset($result['host_'.$i])) {
                continue;
            }

            $ips = $this->collectHostIps($result, $i);
            $nameservers[] = [
                'id' => (string) ($i + 1),
                'hostname' => $result['host_'.$i],
                'ip' => $ips[0] ?? '',
                'ips' => $ips,
            ];
        }

        return [
            'status' => 'success',
            'message' => __('admin/modules.internetbs_get_child_ns_success'),
            'data' => [
                'domain' => $domain,
                'nameservers' => $nameservers,
            ],
        ];
    }

    /**
     * Register a child nameserver (glue record).
     */
    public function addChildNameserver(array $params)
    {
        $domain = $params['domain'] ?? '';
        $hostname = $params['hostname'] ?? '';
        $ip = $params['ip'] ?? '';

        if (empty($domain) || empty($hostname) || empty($ip)) {
            return $this->errorResponse(__('admin/modules.internetbs_error_child_ns_params_required'));
        }

        // Ensure hostname includes the domain
        if (strpos($hostname, '.'.$domain) === false && $hostname !== $domain) {
            $hostname = $hostname.'.'.$domain;
        }

        $result = $this->callApi('Domain/Host/Create', [
            'host' => $hostname,
            'ip_list' => $ip,
        ]);

        if ($result === false) {
            return $this->errorResponse(__('admin/modules.internetbs_error_connection'));
        }

        if ($this->isFailure($result)) {
            return $this->errorResponse($this->extractErrorMessage($result));
        }

        return [
            'status' => 'success',
            'message' => __('admin/modules.internetbs_add_child_ns_success'),
            'data' => [
                'hostname' => $hostname,
                'ip' => $ip,
            ],
        ];
    }

    /**
     * Update a child nameserver IP.
     */
    public function updateChildNameserver(array $params)
    {
        $domain = $params['domain'] ?? '';
        $hostname = $params['hostname'] ?? '';
        $ip = $params['ip'] ?? '';

        if (empty($domain) || empty($hostname) || empty($ip)) {
            return $this->errorResponse(__('admin/modules.internetbs_error_child_ns_params_required'));
        }

        if (strpos($hostname, '.'.$domain) === false && $hostname !== $domain) {
            $hostname = $hostname.'.'.$domain;
        }

        // HostBill class.internetbs.php:772-787 — preserve the host's other glue
        // IPs: read the current list, swap the old IP for the new one (or append
        // when no old IP is supplied) and re-send the full ip_list.
        $oldIp = $params['old_ip'] ?? '';
        $existing = $this->fetchHostIps($domain, $hostname);

        if (! empty($existing)) {
            $merged = [];
            $replaced = false;
            foreach ($existing as $existingIp) {
                if ($oldIp !== '' && $existingIp === $oldIp) {
                    $merged[] = $ip;
                    $replaced = true;
                } else {
                    $merged[] = $existingIp;
                }
            }
            if (! $replaced && ! in_array($ip, $merged, true)) {
                $merged[] = $ip;
            }
            $ipList = implode(',', array_unique(array_filter($merged)));
        } else {
            $ipList = $ip;
        }

        $result = $this->callApi('Domain/Host/Update', [
            'host' => $hostname,
            'ip_list' => $ipList,
        ]);

        if ($result === false) {
            return $this->errorResponse(__('admin/modules.internetbs_error_connection'));
        }

        if ($this->isFailure($result)) {
            return $this->errorResponse($this->extractErrorMessage($result));
        }

        return [
            'status' => 'success',
            'message' => __('admin/modules.internetbs_update_child_ns_success'),
            'data' => [
                'hostname' => $hostname,
                'ip' => $ip,
            ],
        ];
    }

    /**
     * Delete a child nameserver.
     */
    public function deleteChildNameserver(array $params)
    {
        $domain = $params['domain'] ?? '';
        $hostname = $params['hostname'] ?? '';

        if (empty($domain) || empty($hostname)) {
            return $this->errorResponse(__('admin/modules.internetbs_error_child_ns_params_required'));
        }

        if (strpos($hostname, '.'.$domain) === false && $hostname !== $domain) {
            $hostname = $hostname.'.'.$domain;
        }

        // HostBill class.internetbs.php:796-815 — when a specific IP is supplied
        // and the host still has other IPs, remove only that IP via Host/Update;
        // only drop the whole host (Host/Delete) once the last IP is gone.
        $targetIp = $params['ip'] ?? '';
        if ($targetIp !== '') {
            $existing = $this->fetchHostIps($domain, $hostname);
            $remaining = array_values(array_filter($existing, fn ($ip) => $ip !== $targetIp));

            if (! empty($existing) && count($remaining) > 0) {
                $result = $this->callApi('Domain/Host/Update', [
                    'host' => $hostname,
                    'ip_list' => implode(',', $remaining),
                ]);

                if ($result === false) {
                    return $this->errorResponse(__('admin/modules.internetbs_error_connection'));
                }

                if ($this->isFailure($result)) {
                    return $this->errorResponse($this->extractErrorMessage($result));
                }

                return [
                    'status' => 'success',
                    'message' => __('admin/modules.internetbs_delete_child_ns_success'),
                    'data' => [
                        'hostname' => $hostname,
                        'ip' => $targetIp,
                        'deleted_at' => now()->toISOString(),
                    ],
                ];
            }
        }

        $result = $this->callApi('Domain/Host/Delete', [
            'host' => $hostname,
        ]);

        if ($result === false) {
            return $this->errorResponse(__('admin/modules.internetbs_error_connection'));
        }

        if ($this->isFailure($result)) {
            return $this->errorResponse($this->extractErrorMessage($result));
        }

        return [
            'status' => 'success',
            'message' => __('admin/modules.internetbs_delete_child_ns_success'),
            'data' => [
                'hostname' => $hostname,
                'deleted_at' => now()->toISOString(),
            ],
        ];
    }

    // -------------------------------------------------------------------------
    // API communication helpers
    // -------------------------------------------------------------------------

    /**
     * Execute an API call to the Internet.bs REST API.
     *
     * @param  string  $endpoint  API path e.g. "Domain/Create"
     * @param  array  $data  POST fields (apikey & password are injected automatically)
     * @return array|false  Parsed key-value result or false on connection failure
     */
    protected function callApi($endpoint, $method = 'GET', $data = [])
    {
        // Backward compatibility: if $method is an array, treat it as $data
        if (is_array($method)) {
            $data = $method;
            $method = 'POST';
        }

        $baseUrl = $this->getApiBaseUrl();
        $url = rtrim($baseUrl, '/').'/'.$endpoint;

        // Inject authentication
        $data['apikey'] = $this->config['api_key'] ?? '';
        $data['password'] = $this->config['password'] ?? '';
        $data['ResponseFormat'] = 'TEXT';

        try {
            $response = Http::timeout(30)
                ->withUserAgent('Internet.bs ProCP Module V1.0.0')
                ->asForm()
                ->post($url, $data);

            if (! $response->successful() && ! $response->body()) {
                Log::error('Internetbs API: HTTP error', [
                    'endpoint' => $endpoint,
                    'http_status' => $response->status(),
                ]);

                return false;
            }

            $parsed = $this->parseApiResponse($response->body());

            // Log the API call (mask sensitive fields)
            Log::debug('Internetbs API call', [
                'endpoint' => $endpoint,
                'response_status' => $parsed['status'] ?? 'unknown',
            ]);

            return $parsed;
        } catch (\Exception $e) {
            Log::error('Internetbs API: Connection error', [
                'endpoint' => $endpoint,
                'error' => $e->getMessage(),
            ]);

            return false;
        }
    }

    /**
     * Parse the Internet.bs TEXT format response into a key-value array.
     */
    protected function parseApiResponse(string $body): array
    {
        $result = [];
        $lines = explode("\n", $body);

        foreach ($lines as $line) {
            $line = trim($line);
            if (empty($line)) {
                continue;
            }

            $parts = explode('=', $line, 2);
            if (count($parts) === 2) {
                $result[trim($parts[0])] = trim($parts[1]);
            }
        }

        return $result;
    }

    /**
     * Normalise an Internet.bs Domain/Info date value.
     *
     * The API returns `expirationdate` / `registrationdate` (no underscore) in
     * `YYYY/MM/DD` form; convert slashes to dashes so downstream `expires_at`
     * parsing gets an ISO-friendly date. Empty/null values pass through as null.
     */
    protected function parseApiDate(?string $value): ?string
    {
        $value = trim((string) $value);

        if ($value === '') {
            return null;
        }

        return str_replace('/', '-', $value);
    }

    /**
     * Determine the correct API base URL (live vs test).
     */
    protected function getApiBaseUrl(): string
    {
        $testMode = $this->config['test_mode'] ?? false;

        // Handle various truthy representations from config
        if ($testMode === true || $testMode === 'on' || $testMode === '1' || $testMode === 1) {
            return self::API_TEST_URL;
        }

        return self::API_LIVE_URL;
    }

    // -------------------------------------------------------------------------
    // Response helpers
    // -------------------------------------------------------------------------

    /**
     * Build a standardised error response.
     */
    protected function errorResponse(string $message, array $data = []): array
    {
        return [
            'status' => 'error',
            'message' => $message,
        ];
    }

    /**
     * Check whether an API result indicates failure.
     */
    protected function isFailure(array $result): bool
    {
        if (isset($result['status']) && strtoupper($result['status']) === 'FAILURE') {
            return true;
        }

        // Some operations return product-level status
        if (isset($result['product_0_status']) && strtoupper($result['product_0_status']) === 'FAILURE') {
            return true;
        }

        return false;
    }

    /**
     * Extract a human-readable error message from the API result.
     */
    protected function extractErrorMessage(array $result): string
    {
        $messages = [];

        if (! empty($result['message'])) {
            $messages[] = $result['message'];
        }

        if (! empty($result['product_0_message'])) {
            $messages[] = $result['product_0_message'];
        }

        return ! empty($messages)
            ? implode(' ', $messages)
            : __('admin/modules.internetbs_error_unknown');
    }

    // -------------------------------------------------------------------------
    // Domain-level helpers
    // -------------------------------------------------------------------------

    /**
     * Collect nameservers from params into a flat list.
     */
    protected function buildNameserverList(array $params): array
    {
        // First check for a nameservers array
        if (! empty($params['nameservers']) && is_array($params['nameservers'])) {
            return array_filter($params['nameservers']);
        }

        // Fall back to individual ns1..ns5 keys
        $nsList = [];
        for ($i = 1; $i <= 5; $i++) {
            if (! empty($params['ns'.$i])) {
                $nsList[] = $params['ns'.$i];
            }
        }

        return $nsList;
    }

    /**
     * Map a canonical (populated) contact — as returned by
     * AbstractRegistrar::contact() — to the Internet.bs "{prefix}_*" WHOIS
     * param names (registrant|admin|technical|billing).
     *
     * Canonical keys: first_name/last_name/company/email/phone/address1/
     * address2/city/state/postcode/country_code (PRC-969).
     *
     * @param  array<string,string>  $c  Canonical contact
     * @param  string  $prefix  registrant|admin|technical|billing
     * @return array<string,string>
     */
    protected function buildContactData(array $contact, string $prefix): array
    {
        return [
            $prefix.'_firstname' => $contact['first_name'] ?? '',
            $prefix.'_lastname' => $contact['last_name'] ?? '',
            $prefix.'_organization' => $contact['company'] ?? '',
            $prefix.'_street' => $contact['address1'] ?? '',
            $prefix.'_street2' => $contact['address2'] ?? '',
            $prefix.'_city' => $contact['city'] ?? '',
            $prefix.'_countrycode' => $contact['country_code'] ?? '',
            $prefix.'_postalcode' => $contact['postcode'] ?? '',
            $prefix.'_email' => $contact['email'] ?? '',
            $prefix.'_phonenumber' => $this->formatPhone(
                $contact['phone'] ?? '',
                $contact['country_code'] ?? ''
            ),
        ];
    }

    /**
     * Remove blank `{group}_organization` keys. Internet.bs (and WHMCS
     * internetbs.php:434) only sends an organization when a company is present;
     * an empty value is rejected by some registries.
     *
     * @param  array<string,mixed>  $data
     * @return array<string,mixed>
     */
    protected function stripEmptyOrganizations(array $data): array
    {
        foreach (['registrant', 'admin', 'technical', 'billing'] as $group) {
            $key = $group.'_organization';
            if (isset($data[$key]) && trim((string) $data[$key]) === '') {
                unset($data[$key]);
            }
        }

        return $data;
    }

    /**
     * Collect all IPs for host index $i from a Domain/Host/List TEXT response.
     * The official API emits `host_N_ipList_M`; some responses use `host_N_ip_M`
     * — read whichever is present.
     *
     * @param  array<string,mixed>  $result
     * @return array<int,string>
     */
    protected function collectHostIps(array $result, int $i): array
    {
        $ips = [];
        foreach (['ipList', 'ip'] as $sub) {
            for ($j = 0; isset($result['host_'.$i.'_'.$sub.'_'.$j]); $j++) {
                $ip = trim((string) $result['host_'.$i.'_'.$sub.'_'.$j]);
                if ($ip !== '') {
                    $ips[] = $ip;
                }
            }
            if (! empty($ips)) {
                break;
            }
        }

        return array_values(array_unique($ips));
    }

    /**
     * Fetch the current glue IPs for a single host (CompactList=no) so update /
     * delete can preserve the host's other IPs. Returns [] on any failure so the
     * caller can fall back to a single-IP operation.
     *
     * @return array<int,string>
     */
    protected function fetchHostIps(string $domain, string $hostname): array
    {
        $result = $this->callApi('Domain/Host/List', [
            'domain' => $domain,
            'CompactList' => 'no',
        ]);

        if ($result === false || $this->isFailure($result)) {
            return [];
        }

        $totalHosts = (int) ($result['total_hosts'] ?? 0);
        for ($i = 0; $i < $totalHosts; $i++) {
            if (isset($result['host_'.$i]) && strcasecmp((string) $result['host_'.$i], $hostname) === 0) {
                return $this->collectHostIps($result, $i);
            }
        }

        return [];
    }

    /**
     * Strip registrant fields the registry rejects on Domain/Update for certain
     * TLDs (WHMCS internetbs.php:1162-1195).
     *
     * @param  array<string,mixed>  $data
     * @return array<string,mixed>
     */
    protected function applyContactUpdateTldRules(array $data, string $domain): array
    {
        $parts = explode('.', $domain, 2);
        $tld = strtolower($parts[1] ?? '');
        $extArr = explode('.', $tld);
        $ext = end($extArr);

        if ($ext === 'it') {
            foreach (['registrant_countrycode', 'registrant_organization', 'registrant_country'] as $key) {
                unset($data[$key]);
            }
        }

        if ($ext === 'eu' || $ext === 'be') {
            if (! isset($data['registrant_organization']) || trim((string) $data['registrant_organization']) === '') {
                unset($data['registrant_firstname'], $data['registrant_lastname']);
            }
            unset($data['registrant_organization']);
        }

        if ($ext === 'uk' || in_array($tld, ['co.uk', 'org.uk', 'me.uk'], true)) {
            unset($data['registrant_firstname'], $data['registrant_lastname']);
        }

        if ($ext === 'fr' || $ext === 're') {
            unset($data['registrant_firstname'], $data['registrant_lastname'], $data['registrant_countrycode']);
        }

        return $data;
    }

    /**
     * Format a phone number to Internet.bs +CC.Number format.
     */
    protected function formatPhone(string $phone, string $countryCode = ''): string
    {
        $phone = trim($phone);

        if (empty($phone)) {
            $cc = $this->getCountryPhoneCode($countryCode);

            return '+'.$cc.'.0000000';
        }

        // Already correctly formatted
        if (preg_match('/^\+\d{1,4}\.\d+$/', $phone)) {
            return $phone;
        }

        // Strip non-digits except leading +
        $hasPlus = str_starts_with($phone, '+');
        $digits = preg_replace('/\D/', '', $phone);

        if (empty($digits)) {
            $cc = $this->getCountryPhoneCode($countryCode);

            return '+'.$cc.'.0000000';
        }

        $cc = $this->getCountryPhoneCode($countryCode);

        // If starts with + try to extract country code
        if ($hasPlus) {
            $ccStr = (string) $cc;
            $ccLen = strlen($ccStr);

            if (substr($digits, 0, $ccLen) === $ccStr) {
                return '+'.$ccStr.'.'.substr($digits, $ccLen);
            }

            // Could not match country code, just prefix
            return '+'.$cc.'.'.$digits;
        }

        // If starts with 00, strip it
        if (str_starts_with($digits, '00')) {
            $digits = substr($digits, 2);
        }

        return '+'.$cc.'.'.$digits;
    }

    /**
     * Map ISO country code to phone calling code.
     */
    protected function getCountryPhoneCode(string $countryCode): int
    {
        $map = [
            'US' => 1, 'CA' => 1, 'GB' => 44, 'AU' => 61, 'DE' => 49, 'FR' => 33,
            'IT' => 39, 'ES' => 34, 'NL' => 31, 'BE' => 32, 'CH' => 41, 'AT' => 43,
            'DK' => 45, 'SE' => 46, 'NO' => 47, 'FI' => 358, 'IE' => 353, 'PT' => 351,
            'GR' => 30, 'PL' => 48, 'CZ' => 420, 'HU' => 36, 'RO' => 40, 'BG' => 359,
            'HR' => 385, 'SK' => 421, 'SI' => 386, 'LT' => 370, 'LV' => 371, 'EE' => 372,
            'CY' => 357, 'MT' => 356, 'LU' => 352, 'IS' => 354, 'TR' => 90, 'RU' => 7,
            'UA' => 380, 'BR' => 55, 'MX' => 52, 'AR' => 54, 'CL' => 56, 'CO' => 57,
            'PE' => 51, 'VE' => 58, 'IN' => 91, 'CN' => 86, 'JP' => 81, 'KR' => 82,
            'TW' => 886, 'HK' => 852, 'SG' => 65, 'MY' => 60, 'TH' => 66, 'ID' => 62,
            'PH' => 63, 'VN' => 84, 'NZ' => 64, 'ZA' => 27, 'EG' => 20, 'NG' => 234,
            'KE' => 254, 'IL' => 972, 'AE' => 971, 'SA' => 966, 'QA' => 974, 'KW' => 965,
            'BH' => 973, 'OM' => 968, 'JO' => 962, 'LB' => 961, 'PK' => 92, 'BD' => 880,
            'LK' => 94, 'MM' => 95, 'KH' => 855, 'LA' => 856, 'MN' => 976, 'NP' => 977,
        ];

        return $map[strtoupper($countryCode)] ?? 1;
    }

    /**
     * Build a fully-qualified DNS record name from a short name and domain.
     */
    protected function buildFullRecordName(string $name, string $domain): string
    {
        $name = trim($name);
        $domain = trim($domain);

        if (empty($name) || $name === '@') {
            return $domain;
        }

        // Already fully qualified
        if ($name === $domain || str_ends_with($name, '.'.$domain)) {
            return $name;
        }

        return $name.'.'.$domain;
    }

    /**
     * Parse Internet.bs DNS record list response into structured records.
     */
    protected function parseDnsRecords(array $result, string $domain): array
    {
        $records = [];

        // Determine the highest record index
        $maxIndex = 0;
        foreach (array_keys($result) as $key) {
            if (preg_match('/^records_(\d+)_/', $key, $matches)) {
                $idx = (int) $matches[1];
                if ($idx > $maxIndex) {
                    $maxIndex = $idx;
                }
            }
        }

        for ($i = 0; $i <= $maxIndex; $i++) {
            if (! isset($result['records_'.$i.'_type'])) {
                continue;
            }

            $type = trim($result['records_'.$i.'_type']);
            $fullName = $result['records_'.$i.'_name'] ?? '';
            $value = $result['records_'.$i.'_value'] ?? '';
            $ttl = $result['records_'.$i.'_ttl'] ?? 3600;
            $priority = $result['records_'.$i.'_priority'] ?? null;

            // Strip domain suffix to get the short hostname
            $shortName = $fullName;
            $suffix = '.'.$domain;
            if (str_ends_with($fullName, $suffix)) {
                $shortName = substr($fullName, 0, -strlen($suffix));
            } elseif ($fullName === $domain) {
                $shortName = '@';
            }

            $record = [
                'id' => (string) ($i + 1),
                'type' => strtoupper($type),
                'name' => $shortName ?: '@',
                'value' => $value,
                'ttl' => (int) $ttl,
                'priority' => $priority !== null ? (int) $priority : null,
            ];

            $records[] = $record;
        }

        return $records;
    }

    /**
     * Apply TLD-specific extra fields to the API data array.
     *
     * Supports .eu, .be, .uk, .us, .asia, .fr, .de, .it, .tel extensions
     * based on the WHMCS Internet.bs module logic.
     */
    protected function applyTldSpecificFields(array $data, array $params): array
    {
        $domain = $params['domain'] ?? '';
        $parts = explode('.', $domain, 2);
        $tld = $parts[1] ?? '';
        $ext = strtolower(substr($tld, strrpos($tld, '.') !== false ? strrpos($tld, '.') + 1 : 0));

        $additionalFields = $params['additional_fields'] ?? [];

        // .eu / .be  -- registrant language
        if (in_array($tld, ['eu', 'be']) || $ext === 'uk') {
            $data['registrant_language'] = $additionalFields['language'] ?? 'en';
        }

        // .us  -- nexus category & purpose
        if ($tld === 'us') {
            $data['registrant_usnexuscategory'] = $additionalFields['nexus_category'] ?? '';
            $data['registrant_uspurpose'] = $additionalFields['us_purpose'] ?? 'P2';
            $data['registrant_usnexuscountry'] = $additionalFields['nexus_country'] ?? '';
        }

        // .de  -- roles & zone contact
        if ($tld === 'de') {
            $data['admin_role'] = 'PERSON';
            $data['technical_role'] = 'PERSON';
            $data['zone_role'] = 'PERSON';
            $data['registrant_role'] = ! empty($data['registrant_organization']) ? 'ORG' : 'PERSON';

            // Zone contact mirrors the admin contact
            foreach (['firstname', 'lastname', 'street', 'street2', 'city', 'countrycode', 'postalcode', 'email', 'phonenumber'] as $field) {
                if (isset($data['admin_'.$field])) {
                    $data['zone_'.$field] = $data['admin_'.$field];
                }
            }

            // .de does not use billing contact
            foreach (['firstname', 'lastname', 'street', 'street2', 'city', 'countrycode', 'postalcode', 'email', 'phonenumber'] as $field) {
                unset($data['billing_'.$field]);
            }

            $data['tosagree'] = ! empty($additionalFields['agree_de_terms']) ? 'YES' : 'NO';
        }

        // .it  -- entity type, nationality, province, reg code
        if ($tld === 'it') {
            $data['registrant_dotitentitytype'] = $additionalFields['it_entity_type'] ?? 1;
            $data['registrant_dotitnationality'] = $additionalFields['it_nationality'] ?? ($data['registrant_countrycode'] ?? 'IT') ?: 'IT';
            $data['registrant_dotitregcode'] = $additionalFields['it_tax_id'] ?? '';
            $data['registrant_dotitterm1'] = 'yes';
            $data['registrant_dotitterm2'] = 'yes';
            $data['registrant_dotitterm3'] = 'yes';
            $data['registrant_dotitterm4'] = 'yes';
        }

        // WHOIS privacy via id_protection flag
        if (! empty($params['id_protection'])) {
            $data['privateWhois'] = 'FULL';
        }

        return $data;
    }
}
