<?php

namespace App\Modules\Registrar\ResellerClub;

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

class ResellerClub extends AbstractRegistrar
{
    /**
     * LogicBoxes HTTP API endpoint
     * Subclasses (ResellerCamp, Stargate, NetEarthOne) override this.
     */
    protected $apiEndpoint = 'https://httpapi.com/api/';

    /**
     * Test API endpoint
     */
    protected $testApiEndpoint = 'https://test.httpapi.com/api/';

    /**
     * Domain availability check endpoint
     */
    protected $availabilityEndpoint = 'https://domaincheck.httpapi.com/api/';

    // -------------------------------------------------------------------------
    //  Constructor
    // -------------------------------------------------------------------------

    public function __construct(array $config = [], ?Registrar $registrarModel = null)
    {
        parent::__construct($config, $registrarModel);

        // Switch endpoint when test mode is enabled
        if ($this->isTestMode()) {
            $this->apiEndpoint = $this->testApiEndpoint;
        }
    }

    // -------------------------------------------------------------------------
    //  Module Meta
    // -------------------------------------------------------------------------

    public function getModuleInfo(): array
    {
        $defaultInfo = parent::getModuleInfo();

        return array_merge($defaultInfo, [
            'label' => __('admin/modules.resellerclub_module_label'),
            'description' => __('admin/modules.resellerclub_module_description'),
            'features' => [
                'domain_register' => true,
                'domain_transfer' => true,
                'domain_renew' => true,
                'domain_nameservers' => true,
                'domain_contacts' => true,
                'domain_epp' => true,
                'domain_privacy' => true,
                'dns_management' => true,
                'domain_lock' => true,
                'child_nameservers' => true,
                'domain_sync' => true,
                'domain_availability' => true,
                'domain_suggestions' => true,
            ],
        ]);
    }

    public function getConfigFields(): array
    {
        return [
            'reseller_id' => [
                'type' => 'text',
                'label' => __('admin/modules.resellerclub_field_reseller_id_label'),
                'required' => true,
                'description' => __('admin/modules.resellerclub_field_reseller_id_description'),
                'placeholder' => '123456',
                'value' => $this->config['reseller_id'] ?? '',
            ],
            'api_key' => [
                'type' => 'password',
                'label' => __('admin/modules.resellerclub_field_api_key_label'),
                'required' => true,
                'description' => __('admin/modules.resellerclub_field_api_key_description'),
                'placeholder' => '********',
                'value' => $this->config['api_key'] ?? '',
            ],
            'test_mode' => [
                'type' => 'checkbox',
                'label' => __('admin/modules.resellerclub_field_test_mode_label'),
                'required' => false,
                'description' => __('admin/modules.resellerclub_field_test_mode_description'),
                'value' => $this->config['test_mode'] ?? false,
            ],
            'enable_premium_domains' => [
                'type' => 'checkbox',
                'label' => __('admin/modules.resellerclub_field_premium_domains_label'),
                'required' => false,
                'description' => __('admin/modules.resellerclub_field_premium_domains_description'),
                'value' => $this->config['enable_premium_domains'] ?? false,
            ],
        ];
    }

    // -------------------------------------------------------------------------
    //  Config Validation
    // -------------------------------------------------------------------------

    public function validateConfig()
    {
        if (empty($this->config['reseller_id'])) {
            throw new \Exception(__('admin/modules.resellerclub_config_reseller_id_missing'));
        }

        if (empty($this->config['api_key'])) {
            throw new \Exception(__('admin/modules.resellerclub_config_api_key_missing'));
        }

        return true;
    }

    // -------------------------------------------------------------------------
    //  Domain Registration
    // -------------------------------------------------------------------------

    public function registerDomain(array $params)
    {
        $domain = $params['domain'] ?? '';
        if (empty($domain)) {
            return $this->errorResponse(__('admin/modules.resellerclub_domain_required'));
        }

        try {
            $this->validateConfig();

            // 1. Get or create customer
            $customerId = $this->getOrCreateCustomer($params);
            if (is_array($customerId) && ($customerId['status'] ?? '') === 'error') {
                return $customerId;
            }

            // 2. Create the four contacts (registrant + admin/tech/billing).
            //    WiseCP creates 4 distinct contacts; WHMCS falls back Admin ?:
            //    Registrant. We honour the PRC-969 groups, falling back to the
            //    registrant contact id per group when the source is empty/errors.
            $contactIds = $this->createDomainContacts($params, $customerId);
            if (is_array($contactIds) && ($contactIds['status'] ?? '') === 'error') {
                return $contactIds;
            }

            // 3. Build nameserver string
            $nsList = $this->buildNameserverList($params);

            // Some TLDs (.uk/.eu/.nz/.ru + ca/nl/london billing) reject
            // admin/tech/billing and require contact-id "-1" on register too
            // (WHMCS resellerclub_DomainTLDSpecificFields runs on both paths).
            $contactless = $this->contactlessFlags($domain);

            // 4. Register domain
            $requestData = [
                'domain-name' => $domain,
                'years' => $params['period'] ?? $params['years'] ?? 1,
                'ns' => $nsList,
                'customer-id' => $customerId,
                'reg-contact-id' => $contactIds['registrant'],
                'admin-contact-id' => $contactless['admin'] ? '-1' : $contactIds['admin'],
                'tech-contact-id' => $contactless['tech'] ? '-1' : $contactIds['tech'],
                'billing-contact-id' => $contactless['billing'] ? '-1' : $contactIds['billing'],
                'invoice-option' => 'NoInvoice',
                'purchase-privacy' => (!empty($params['id_protection']) || !empty($params['privacy'])) ? 'true' : 'false',
            ];

            // TLD-specific additional_fields + idnLanguageCode + premium (attr-name{N}).
            $requestData = array_merge($requestData, $this->buildTldExtraAttributes($params, $domain));

            $result = $this->sendCommand('register', 'domains', $requestData, 'POST');

            if ($this->isApiError($result)) {
                return $this->errorResponse($this->getApiErrorMessage($result));
            }

            if (isset($result['actionstatus']) && $result['actionstatus'] === 'Failed') {
                return $this->errorResponse($result['actionstatusdesc'] ?? __('admin/modules.resellerclub_register_failed'));
            }

            Log::info('ResellerClub: Domain registered', [
                'domain' => $domain,
                'order_id' => $result['entityid'] ?? null,
            ]);

            return $this->successResponse(__('admin/modules.resellerclub_register_success'), [
                'domain' => $domain,
                'order_id' => $result['entityid'] ?? null,
                'period' => $params['period'] ?? $params['years'] ?? 1,
            ]);
        } catch (\Exception $e) {
            Log::error('ResellerClub: Register domain error', ['domain' => $domain, 'error' => $e->getMessage()]);

            return $this->errorResponse($e->getMessage());
        }
    }

    // -------------------------------------------------------------------------
    //  Domain Transfer
    // -------------------------------------------------------------------------

    public function transferDomain(array $params)
    {
        $domain = $params['domain'] ?? '';
        if (empty($domain)) {
            return $this->errorResponse(__('admin/modules.resellerclub_domain_required'));
        }

        try {
            $this->validateConfig();

            $customerId = $this->getOrCreateCustomer($params);
            if (is_array($customerId) && ($customerId['status'] ?? '') === 'error') {
                return $customerId;
            }

            $contactIds = $this->createDomainContacts($params, $customerId);
            if (is_array($contactIds) && ($contactIds['status'] ?? '') === 'error') {
                return $contactIds;
            }

            // Some TLDs (.uk/.eu/.nz/.ru + ca/nl/london billing) reject
            // admin/tech/billing and require contact-id "-1"
            // (WHMCS resellerclub_DomainTLDSpecificFields).
            $contactless = $this->contactlessFlags($domain);

            $requestData = [
                'domain-name' => $domain,
                'years' => $params['period'] ?? $params['years'] ?? 1,
                'customer-id' => $customerId,
                'reg-contact-id' => $contactIds['registrant'],
                'admin-contact-id' => $contactless['admin'] ? '-1' : $contactIds['admin'],
                'tech-contact-id' => $contactless['tech'] ? '-1' : $contactIds['tech'],
                'billing-contact-id' => $contactless['billing'] ? '-1' : $contactIds['billing'],
                'invoice-option' => 'NoInvoice',
                'purchase-privacy' => (!empty($params['id_protection']) || !empty($params['privacy'])) ? 'true' : 'false',
            ];

            if (!empty($params['epp_code']) || !empty($params['authcode'])) {
                $requestData['auth-code'] = $params['epp_code'] ?? $params['authcode'];
            }

            // TLD-specific additional_fields + idnLanguageCode + premium (attr-name{N}).
            $requestData = array_merge($requestData, $this->buildTldExtraAttributes($params, $domain));

            $result = $this->sendCommand('transfer', 'domains', $requestData, 'POST');

            if ($this->isApiError($result)) {
                return $this->errorResponse($this->getApiErrorMessage($result));
            }

            if (isset($result['actionstatus']) && $result['actionstatus'] === 'Failed') {
                return $this->errorResponse($result['actionstatusdesc'] ?? __('admin/modules.resellerclub_transfer_failed'));
            }

            Log::info('ResellerClub: Domain transfer initiated', [
                'domain' => $domain,
                'order_id' => $result['entityid'] ?? null,
            ]);

            return $this->successResponse(__('admin/modules.resellerclub_transfer_success'), [
                'domain' => $domain,
                'order_id' => $result['entityid'] ?? null,
            ]);
        } catch (\Exception $e) {
            Log::error('ResellerClub: Transfer domain error', ['domain' => $domain, 'error' => $e->getMessage()]);

            return $this->errorResponse($e->getMessage());
        }
    }

    // -------------------------------------------------------------------------
    //  Domain Renewal
    // -------------------------------------------------------------------------

    public function renewDomain(array $params)
    {
        $domain = $params['domain'] ?? '';
        if (empty($domain)) {
            return $this->errorResponse(__('admin/modules.resellerclub_domain_required'));
        }

        try {
            $this->validateConfig();

            $orderId = $this->getOrderId($domain);
            if (!is_numeric($orderId)) {
                return $this->errorResponse($orderId);
            }

            // Get current expiry date
            $details = $this->sendCommand('details', 'domains', [
                'order-id' => $orderId,
                'options' => 'OrderDetails',
            ], 'GET');

            if ($this->isApiError($details)) {
                return $this->errorResponse($this->getApiErrorMessage($details));
            }

            $expiry = $details['endtime'] ?? null;
            if (!$expiry) {
                return $this->errorResponse(__('admin/modules.resellerclub_expiry_not_found'));
            }

            $requestData = [
                'order-id' => $orderId,
                'years' => $params['period'] ?? $params['years'] ?? 1,
                'exp-date' => $expiry,
                'invoice-option' => 'NoInvoice',
                'purchase-privacy' => (!empty($params['id_protection']) || !empty($params['privacy'])) ? 'true' : 'false',
            ];

            if (!empty($params['is_premium']) && !empty($this->config['enable_premium_domains'])) {
                $requestData['attr-name1'] = 'premium';
                $requestData['attr-value1'] = 'true';
            }

            // Redemption grace period restore
            if (!empty($params['is_redemption'])) {
                $result = $this->sendCommand('restore', 'domains', [
                    'order-id' => $orderId,
                    'invoice-option' => 'NoInvoice',
                ], 'POST');
            } else {
                $result = $this->sendCommand('renew', 'domains', $requestData, 'POST');
            }

            if ($this->isApiError($result)) {
                return $this->errorResponse($this->getApiErrorMessage($result));
            }

            if (isset($result['actionstatus']) && $result['actionstatus'] === 'Failed') {
                return $this->errorResponse($result['actionstatusdesc'] ?? __('admin/modules.resellerclub_renew_failed'));
            }

            // After a redemption restore, LogicBoxes drops privacy — re-purchase
            // it if the domain had privacy (WHMCS renew: restore then purchase-privacy).
            if (!empty($params['is_redemption'])
                && (!empty($params['id_protection']) || !empty($params['privacy']))) {
                $this->sendCommand('purchase-privacy', 'domains', [
                    'order-id' => $orderId,
                    'invoice-option' => 'NoInvoice',
                ], 'POST');
            }

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

            return $this->successResponse(__('admin/modules.resellerclub_renew_success'), [
                'domain' => $domain,
                'order_id' => $orderId,
            ]);
        } catch (\Exception $e) {
            Log::error('ResellerClub: Renew domain error', ['domain' => $domain, 'error' => $e->getMessage()]);

            return $this->errorResponse($e->getMessage());
        }
    }

    // -------------------------------------------------------------------------
    //  Nameservers
    // -------------------------------------------------------------------------

    public function getNameservers(array $params)
    {
        $domain = $params['domain'] ?? '';
        if (empty($domain)) {
            return $this->errorResponse(__('admin/modules.resellerclub_domain_required'));
        }

        try {
            $this->validateConfig();

            $orderId = $this->getOrderId($domain);
            if (!is_numeric($orderId)) {
                return $this->errorResponse($orderId);
            }

            $result = $this->sendCommand('details', 'domains', [
                'order-id' => $orderId,
                'options' => 'NsDetails',
            ], 'GET');

            if ($this->isApiError($result)) {
                return $this->errorResponse($this->getApiErrorMessage($result));
            }

            $nameservers = [];
            for ($i = 1; $i <= 5; $i++) {
                if (!empty($result['ns'.$i])) {
                    $nameservers[] = $result['ns'.$i];
                }
            }

            return $this->successResponse(__('admin/modules.resellerclub_nameservers_retrieved'), [
                'domain' => $domain,
                'nameservers' => $nameservers,
            ]);
        } catch (\Exception $e) {
            return $this->errorResponse($e->getMessage());
        }
    }

    public function setNameservers(array $params)
    {
        $domain = $params['domain'] ?? '';
        if (empty($domain)) {
            return $this->errorResponse(__('admin/modules.resellerclub_domain_required'));
        }

        try {
            $this->validateConfig();

            $orderId = $this->getOrderId($domain);
            if (!is_numeric($orderId)) {
                return $this->errorResponse($orderId);
            }

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

            $result = $this->sendCommand('modify-ns', 'domains', [
                'order-id' => $orderId,
                'ns' => $nsList,
            ], 'POST');

            if ($this->isApiError($result)) {
                return $this->errorResponse($this->getApiErrorMessage($result));
            }

            return $this->successResponse(__('admin/modules.resellerclub_nameservers_updated'), [
                'domain' => $domain,
            ]);
        } catch (\Exception $e) {
            return $this->errorResponse($e->getMessage());
        }
    }

    // -------------------------------------------------------------------------
    //  Contact Details
    // -------------------------------------------------------------------------

    public function getContactDetails(array $params)
    {
        $domain = $params['domain'] ?? '';
        if (empty($domain)) {
            return $this->errorResponse(__('admin/modules.resellerclub_domain_required'));
        }

        try {
            $this->validateConfig();

            $orderId = $this->getOrderId($domain);
            if (!is_numeric($orderId)) {
                return $this->errorResponse($orderId);
            }

            $result = $this->sendCommand('details', 'domains', [
                'order-id' => $orderId,
                'options' => 'ContactIds',
            ], 'GET');

            if ($this->isApiError($result)) {
                return $this->errorResponse($this->getApiErrorMessage($result));
            }

            $contactTypes = [
                'registrantcontactid' => 'Registrant',
                'admincontactid' => 'Admin Contact',
                'techcontactid' => 'Technical Contact',
                'billingcontactid' => 'Billing Contact',
            ];

            $groups = [];
            $cachedContacts = [];

            foreach ($contactTypes as $idKey => $groupName) {
                $contactId = $result[$idKey] ?? null;
                if (!$contactId || $contactId == -1) {
                    continue;
                }

                // Cache contact details to avoid duplicate API calls
                if (!isset($cachedContacts[$contactId])) {
                    $contactResult = $this->sendCommand('details', 'contacts', [
                        'contact-id' => $contactId,
                    ], 'GET');

                    if (!$this->isApiError($contactResult)) {
                        $cachedContacts[$contactId] = $contactResult;
                    }
                }

                if (isset($cachedContacts[$contactId])) {
                    $c = $cachedContacts[$contactId];
                    $groups[$groupName] = [
                        'firstname' => $c['name'] ?? '',
                        'company' => $c['company'] ?? '',
                        'email' => $c['emailaddr'] ?? '',
                        'phone' => ($c['telnocc'] ?? '').($c['telno'] ?? ''),
                        'address' => $c['address1'] ?? '',
                        'address2' => $c['address2'] ?? '',
                        'city' => $c['city'] ?? '',
                        'state' => $c['state'] ?? '',
                        'postal_code' => $c['zip'] ?? '',
                        'country' => $c['country'] ?? '',
                    ];
                }
            }

            return [
                'status' => 'success',
                'message' => __('admin/modules.resellerclub_contacts_retrieved'),
                'groups' => $groups,
            ];
        } catch (\Exception $e) {
            return $this->errorResponse($e->getMessage());
        }
    }

    public function setContactDetails(array $params)
    {
        $domain = $params['domain'] ?? '';
        if (empty($domain) || !isset($params['groups'])) {
            return $this->errorResponse(__('admin/modules.resellerclub_domain_and_groups_required'));
        }

        try {
            $this->validateConfig();

            $orderId = $this->getOrderId($domain);
            if (!is_numeric($orderId)) {
                return $this->errorResponse($orderId);
            }

            // Get domain details to find customer ID and contact IDs
            $details = $this->sendCommand('details', 'domains', [
                'order-id' => $orderId,
                'options' => ['ContactIds', 'OrderDetails'],
            ], 'GET');

            if ($this->isApiError($details)) {
                return $this->errorResponse($this->getApiErrorMessage($details));
            }

            $customerId = $details['customerid'] ?? null;
            if (!$customerId) {
                return $this->errorResponse(__('admin/modules.resellerclub_customer_not_found'));
            }

            $contactMap = [
                'Registrant' => 'registrantcontactid',
                'Admin Contact' => 'admincontactid',
                'Technical Contact' => 'techcontactid',
                'Billing Contact' => 'billingcontactid',
            ];

            $newContacts = [];

            foreach ($params['groups'] as $groupName => $fields) {
                // Parse phone country code
                $phone = $fields['phone'] ?? '';
                $phonecc = '1';
                $phonenumber = preg_replace('/[^0-9]/', '', $phone);
                if (preg_match('/^\+(\d{1,3})(.+)$/', $phone, $phoneMatch)) {
                    $phonecc = $phoneMatch[1];
                    $phonenumber = preg_replace('/[^0-9]/', '', $phoneMatch[2]);
                }

                $contactData = [
                    'customer-id' => $customerId,
                    'email' => $fields['email'] ?? '',
                    'name' => trim(($fields['firstname'] ?? '').' '.($fields['lastname'] ?? '')),
                    'company' => $fields['company'] ?: 'N/A',
                    'address-line-1' => substr($fields['address'] ?? '', 0, 64),
                    'address-line-2' => substr($fields['address2'] ?? '', 0, 64),
                    'city' => $fields['city'] ?? '',
                    'state' => $fields['state'] ?? '',
                    'zipcode' => $fields['postal_code'] ?? '',
                    'country' => $fields['country'] ?? '',
                    'phone-cc' => $phonecc,
                    'phone' => $phonenumber,
                    'type' => 'Contact',
                ];

                $contactResult = $this->sendCommand('add', 'contacts', $contactData, 'POST');

                if ($this->isApiError($contactResult)) {
                    return $this->errorResponse($this->getApiErrorMessage($contactResult));
                }

                $apiKey = $contactMap[$groupName] ?? null;
                if ($apiKey) {
                    $paramKey = str_replace('contactid', '-contact-id', $apiKey);
                    $paramKey = str_replace('registrant-contact-id', 'reg-contact-id', $paramKey);
                    $newContacts[$paramKey] = $contactResult;
                }
            }

            // Update domain contacts
            if (!empty($newContacts)) {
                $updateData = array_merge(['order-id' => $orderId], $newContacts);

                // IRTP (gTLD registrant change): forward the designated-agent /
                // 60-day-lock opt-out flags when the registrant group changed
                // (WHMCS modify-contact designated-agent / sixty-day-lock-optout).
                $registrantChanged = array_key_exists('reg-contact-id', $newContacts);
                if ($registrantChanged) {
                    if (!empty($params['designated_agent'])) {
                        $updateData['designated-agent'] = 'true';
                    } elseif (array_key_exists('irtp_opt_out', $params)) {
                        $updateData['sixty-day-lock-optout'] = (bool) (int) $params['irtp_opt_out'] ? 'true' : 'false';
                    }
                }

                $result = $this->sendCommand('modify-contact', 'domains', $updateData, 'POST');

                if ($this->isApiError($result)) {
                    return $this->errorResponse($this->getApiErrorMessage($result));
                }
            }

            return $this->successResponse(__('admin/modules.resellerclub_contacts_updated'), [
                'domain' => $domain,
            ]);
        } catch (\Exception $e) {
            return $this->errorResponse($e->getMessage());
        }
    }

    // -------------------------------------------------------------------------
    //  Domain Details
    // -------------------------------------------------------------------------

    public function getDomainDetails(array $params)
    {
        $domain = $params['domain'] ?? '';
        if (empty($domain)) {
            return $this->errorResponse(__('admin/modules.resellerclub_domain_required'));
        }

        try {
            $this->validateConfig();

            $orderId = $this->getOrderId($domain);
            if (!is_numeric($orderId)) {
                return $this->errorResponse($orderId);
            }

            $result = $this->sendCommand('details', 'domains', [
                'order-id' => $orderId,
                'options' => 'All',
            ], 'GET');

            if ($this->isApiError($result)) {
                return $this->errorResponse($this->getApiErrorMessage($result));
            }

            $nameservers = [];
            for ($i = 1; $i <= 5; $i++) {
                if (!empty($result['ns'.$i])) {
                    $nameservers[] = $result['ns'.$i];
                }
            }

            $expiresAt = isset($result['endtime'])
                ? date('Y-m-d H:i:s', $result['endtime'])
                : null;

            $createdAt = isset($result['creationtime'])
                ? date('Y-m-d H:i:s', $result['creationtime'])
                : null;

            return $this->successResponse(__('admin/modules.resellerclub_domain_details_retrieved'), [
                'domain' => $domain,
                'order_id' => $orderId,
                'status' => $result['currentstatus'] ?? 'Unknown',
                // Transfer lock: LogicBoxes ships orderstatus as an ARRAY that
                // contains "transferlock" when locked (WHMCS in_array; WiseCP
                // orderstatus[0]). isOrderSuspendedUponExpiry is unrelated.
                'locked' => is_array($result['orderstatus'] ?? null)
                    && in_array('transferlock', $result['orderstatus'], true),
                'privacy_enabled' => !empty($result['isprivacyprotected']),
                // LogicBoxes domains/details has no auto-renew field
                // (isImmediateReseller is a reseller-tier flag, not auto-renew).
                'auto_renew' => null,
                'created_at' => $createdAt,
                'expires_at' => $expiresAt,
                'nameservers' => $nameservers,
                'customer_id' => $result['customerid'] ?? null,
                'epp_code' => $result['domsecret'] ?? null,
            ]);
        } catch (\Exception $e) {
            return $this->errorResponse($e->getMessage());
        }
    }

    // -------------------------------------------------------------------------
    //  Domain Lock
    // -------------------------------------------------------------------------

    public function getDomainLock(array $params)
    {
        $domain = $params['domain'] ?? '';
        if (empty($domain)) {
            return $this->errorResponse(__('admin/modules.resellerclub_domain_required'));
        }

        try {
            $this->validateConfig();

            $orderId = $this->getOrderId($domain);
            if (!is_numeric($orderId)) {
                return $this->errorResponse($orderId);
            }

            $result = $this->sendCommand('locks', 'domains', [
                'order-id' => $orderId,
            ], 'GET');

            $locked = false;
            if (isset($result['transferlock']) && $result['transferlock'] == '1') {
                $locked = true;
            }

            return $this->successResponse(__('admin/modules.resellerclub_lock_status_retrieved'), [
                'domain' => $domain,
                'locked' => $locked,
            ]);
        } catch (\Exception $e) {
            return $this->errorResponse($e->getMessage());
        }
    }

    public function updateDomainLock(array $params)
    {
        $domain = $params['domain'] ?? '';
        if (empty($domain)) {
            return $this->errorResponse(__('admin/modules.resellerclub_domain_required'));
        }

        try {
            $this->validateConfig();

            $orderId = $this->getOrderId($domain);
            if (!is_numeric($orderId)) {
                return $this->errorResponse($orderId);
            }

            $lockEnabled = $params['lock_status'] ?? $params['locked'] ?? true;

            $command = $lockEnabled ? 'enable-theft-protection' : 'disable-theft-protection';
            $result = $this->sendCommand($command, 'domains', [
                'order-id' => $orderId,
            ], 'POST');

            if ($this->isApiError($result)) {
                return $this->errorResponse($this->getApiErrorMessage($result));
            }

            return $this->successResponse(__('admin/modules.resellerclub_lock_updated'), [
                'domain' => $domain,
                'locked' => (bool) $lockEnabled,
            ]);
        } catch (\Exception $e) {
            return $this->errorResponse($e->getMessage());
        }
    }

    // -------------------------------------------------------------------------
    //  Privacy / ID Protection
    // -------------------------------------------------------------------------

    public function getPrivacySettings(array $params)
    {
        $domain = $params['domain'] ?? '';
        if (empty($domain)) {
            return $this->errorResponse(__('admin/modules.resellerclub_domain_required'));
        }

        try {
            $this->validateConfig();

            $orderId = $this->getOrderId($domain);
            if (!is_numeric($orderId)) {
                return $this->errorResponse($orderId);
            }

            $result = $this->sendCommand('details', 'domains', [
                'order-id' => $orderId,
                'options' => 'OrderDetails',
            ], 'GET');

            if ($this->isApiError($result)) {
                return $this->errorResponse($this->getApiErrorMessage($result));
            }

            $privacyEnabled = !empty($result['isprivacyprotected']);
            $privacyAvailable = !empty($result['privacyprotectedallowed']);

            return $this->successResponse(__('admin/modules.resellerclub_privacy_retrieved'), [
                'domain' => $domain,
                'privacy_enabled' => $privacyEnabled,
                'available' => $privacyAvailable,
            ]);
        } catch (\Exception $e) {
            return $this->errorResponse($e->getMessage());
        }
    }

    public function updatePrivacySettings(array $params)
    {
        $domain = $params['domain'] ?? '';
        if (empty($domain)) {
            return $this->errorResponse(__('admin/modules.resellerclub_domain_required'));
        }

        try {
            $this->validateConfig();

            $orderId = $this->getOrderId($domain);
            if (!is_numeric($orderId)) {
                return $this->errorResponse($orderId);
            }

            $privacyEnabled = $params['privacy_enabled'] ?? true;

            if ($privacyEnabled) {
                // Check if privacy was previously purchased
                $details = $this->sendCommand('details', 'domains', [
                    'order-id' => $orderId,
                    'options' => 'OrderDetails',
                ], 'GET');

                $privacyEndTime = $details['privacyprotectendtime'] ?? 0;

                if ($privacyEndTime > 0) {
                    // Re-enable existing privacy
                    $result = $this->sendCommand('modify-privacy-protection', 'domains', [
                        'order-id' => $orderId,
                        'protect-privacy' => 'true',
                        'reason' => 'Customer Request',
                    ], 'POST');
                } else {
                    // Purchase new privacy
                    $result = $this->sendCommand('purchase-privacy', 'domains', [
                        'order-id' => $orderId,
                        'invoice-option' => 'NoInvoice',
                    ], 'POST');
                }
            } else {
                // Disable privacy
                $result = $this->sendCommand('modify-privacy-protection', 'domains', [
                    'order-id' => $orderId,
                    'protect-privacy' => 'false',
                    'reason' => 'Customer Request',
                ], 'POST');
            }

            if ($this->isApiError($result)) {
                return $this->errorResponse($this->getApiErrorMessage($result));
            }

            return $this->successResponse(__('admin/modules.resellerclub_privacy_updated'), [
                'domain' => $domain,
                'privacy_enabled' => (bool) $privacyEnabled,
            ]);
        } catch (\Exception $e) {
            return $this->errorResponse($e->getMessage());
        }
    }

    // -------------------------------------------------------------------------
    //  EPP / Auth Code
    // -------------------------------------------------------------------------

    public function getEppCode(array $params)
    {
        $domain = $params['domain'] ?? '';
        if (empty($domain)) {
            return $this->errorResponse(__('admin/modules.resellerclub_domain_required'));
        }

        try {
            $this->validateConfig();

            $orderId = $this->getOrderId($domain);
            if (!is_numeric($orderId)) {
                return $this->errorResponse($orderId);
            }

            $result = $this->sendCommand('details', 'domains', [
                'order-id' => $orderId,
                'options' => 'OrderDetails',
            ], 'GET');

            if ($this->isApiError($result)) {
                return $this->errorResponse($this->getApiErrorMessage($result));
            }

            $eppCode = $result['domsecret'] ?? null;
            if (!$eppCode) {
                return $this->errorResponse(__('admin/modules.resellerclub_epp_not_found'));
            }

            return $this->successResponse(__('admin/modules.resellerclub_epp_retrieved'), [
                'domain' => $domain,
                'epp_code' => $eppCode,
            ]);
        } catch (\Exception $e) {
            return $this->errorResponse($e->getMessage());
        }
    }

    public function sendEppCode(array $params)
    {
        // LogicBoxes returns EPP directly from domain details; there is no
        // separate "send epp to email" command.  We return the code so the
        // system can email it.
        return $this->getEppCode($params);
    }

    // -------------------------------------------------------------------------
    //  DNS Management
    // -------------------------------------------------------------------------

    public function getDNSRecords(array $params)
    {
        $domain = $params['domain'] ?? '';
        if (empty($domain)) {
            return $this->errorResponse(__('admin/modules.resellerclub_domain_required'));
        }

        try {
            $this->validateConfig();

            $orderId = $this->getOrderId($domain);
            if (!is_numeric($orderId)) {
                return $this->errorResponse($orderId);
            }

            // Activate DNS service first
            $this->sendCommand('activate', 'dns', ['order-id' => $orderId], 'POST');

            $recordTypes = ['A', 'MX', 'CNAME', 'TXT', 'AAAA'];
            $records = [];

            foreach ($recordTypes as $type) {
                $result = $this->sendCommand('search-records', 'dns/manage', [
                    'domain-name' => $domain,
                    'type' => $type,
                    'no-of-records' => 50,
                    'page-no' => 1,
                ], 'GET');

                if ($this->isApiError($result)) {
                    continue;
                }

                foreach ($result as $key => $value) {
                    if (!is_array($value)) {
                        continue;
                    }

                    $records[] = [
                        'id' => $key,
                        'type' => $type,
                        'name' => $value['host'] ?? '',
                        'value' => $value['value'] ?? '',
                        'ttl' => (int) ($value['ttl'] ?? 3600),
                        'priority' => $type === 'MX' ? (int) ($value['priority'] ?? 0) : null,
                    ];
                }
            }

            return $this->successResponse(__('admin/modules.resellerclub_dns_records_retrieved'), [
                'domain' => $domain,
                'records' => $records,
            ]);
        } catch (\Exception $e) {
            return $this->errorResponse($e->getMessage());
        }
    }

    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.resellerclub_dns_params_required'));
        }

        try {
            $this->validateConfig();

            $ltype = strtolower($type);
            $typeMap = [
                'a' => 'ipv4',
                'aaaa' => 'ipv6',
                'cname' => 'cname',
                'mx' => 'mx',
                'txt' => 'txt',
                'ns' => 'ns',
                'srv' => 'srv',
            ];

            $apiType = $typeMap[$ltype] ?? $ltype;

            $requestData = [
                'domain-name' => $domain,
                'host' => $name,
                'value' => $value,
            ];

            if ($ltype === 'mx') {
                $requestData['priority'] = $params['priority'] ?? 10;
            }

            $result = $this->sendCommand('add-'.$apiType.'-record', 'dns/manage', $requestData, 'POST');

            if ($this->isApiError($result)) {
                return $this->errorResponse($this->getApiErrorMessage($result));
            }

            return $this->successResponse(__('admin/modules.resellerclub_dns_record_added'), [
                'domain' => $domain,
                'type' => $type,
                'name' => $name,
                'value' => $value,
            ]);
        } catch (\Exception $e) {
            return $this->errorResponse($e->getMessage());
        }
    }

    public function updateDNSRecord(array $params)
    {
        $domain = $params['domain'] ?? '';
        $type = $params['type'] ?? '';
        $name = $params['name'] ?? '';
        $oldValue = $params['old_value'] ?? $params['current_value'] ?? '';
        $newValue = $params['value'] ?? $params['new_value'] ?? '';

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

        try {
            $this->validateConfig();

            $ltype = strtolower($type);
            $typeMap = [
                'a' => 'ipv4',
                'aaaa' => 'ipv6',
                'cname' => 'cname',
                'mx' => 'mx',
                'txt' => 'txt',
                'ns' => 'ns',
                'srv' => 'srv',
            ];

            $apiType = $typeMap[$ltype] ?? $ltype;

            $requestData = [
                'domain-name' => $domain,
                'host' => $name,
                'current-value' => $oldValue,
                'new-value' => $newValue,
            ];

            if ($ltype === 'mx') {
                $requestData['priority'] = $params['priority'] ?? 10;
            }

            $result = $this->sendCommand('update-'.$apiType.'-record', 'dns/manage', $requestData, 'POST');

            if ($this->isApiError($result)) {
                return $this->errorResponse($this->getApiErrorMessage($result));
            }

            return $this->successResponse(__('admin/modules.resellerclub_dns_record_updated'), [
                'domain' => $domain,
                'type' => $type,
                'name' => $name,
                'value' => $newValue,
            ]);
        } catch (\Exception $e) {
            return $this->errorResponse($e->getMessage());
        }
    }

    public function deleteDNSRecord(array $params)
    {
        $domain = $params['domain'] ?? '';
        $name = $params['name'] ?? $params['host'] ?? '';
        $value = $params['value'] ?? '';

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

        try {
            $this->validateConfig();

            $result = $this->sendCommand('delete-record', 'dns/manage', [
                'domain-name' => $domain,
                'host' => $name,
                'value' => $value,
            ], 'POST');

            if ($this->isApiError($result)) {
                return $this->errorResponse($this->getApiErrorMessage($result));
            }

            return $this->successResponse(__('admin/modules.resellerclub_dns_record_deleted'), [
                'domain' => $domain,
            ]);
        } catch (\Exception $e) {
            return $this->errorResponse($e->getMessage());
        }
    }

    // -------------------------------------------------------------------------
    //  Child Nameservers (Glue Records)
    // -------------------------------------------------------------------------

    public function getChildNameservers(array $params)
    {
        $domain = $params['domain'] ?? '';
        if (empty($domain)) {
            return $this->errorResponse(__('admin/modules.resellerclub_domain_required'));
        }

        try {
            $this->validateConfig();

            $orderId = $this->getOrderId($domain);
            if (!is_numeric($orderId)) {
                return $this->errorResponse($orderId);
            }

            // 'All' reliably includes the cns block (WiseCP CNSList uses 'All').
            $result = $this->sendCommand('details', 'domains', [
                'order-id' => $orderId,
                'options' => 'All',
            ], 'GET');

            if ($this->isApiError($result)) {
                return $this->errorResponse($this->getApiErrorMessage($result));
            }

            $childNs = [];
            if (isset($result['cns']) && is_array($result['cns'])) {
                foreach ($result['cns'] as $hostname => $ips) {
                    $childNs[] = [
                        'hostname' => $hostname,
                        'ip' => is_array($ips) ? $ips : [$ips],
                    ];
                }
            }

            return $this->successResponse(__('admin/modules.resellerclub_child_ns_retrieved'), [
                'domain' => $domain,
                'nameservers' => $childNs,
            ]);
        } catch (\Exception $e) {
            return $this->errorResponse($e->getMessage());
        }
    }

    public function addChildNameserver(array $params)
    {
        $domain = $params['domain'] ?? '';
        $hostname = $params['hostname'] ?? $params['nameserver'] ?? '';
        $ip = $params['ip'] ?? $params['ipaddress'] ?? '';

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

        try {
            $this->validateConfig();

            $orderId = $this->getOrderId($domain);
            if (!is_numeric($orderId)) {
                return $this->errorResponse($orderId);
            }

            $result = $this->sendCommand('add-cns', 'domains', [
                'order-id' => $orderId,
                'cns' => $hostname,
                'ip' => $ip,
            ], 'POST');

            if ($this->isApiError($result)) {
                return $this->errorResponse($this->getApiErrorMessage($result));
            }

            return $this->successResponse(__('admin/modules.resellerclub_child_ns_added'), [
                'domain' => $domain,
                'hostname' => $hostname,
                'ip' => $ip,
            ]);
        } catch (\Exception $e) {
            return $this->errorResponse($e->getMessage());
        }
    }

    public function updateChildNameserver(array $params)
    {
        $domain = $params['domain'] ?? '';
        $hostname = $params['hostname'] ?? $params['nameserver'] ?? '';
        $oldIp = $params['old_ip'] ?? $params['currentipaddress'] ?? '';
        $newIp = $params['new_ip'] ?? $params['ip'] ?? $params['newipaddress'] ?? '';
        // Optional glue-record rename (WiseCP ModifyCNS -> modify-cns-name).
        $newHostname = $params['new_hostname'] ?? $params['new_nameserver'] ?? '';

        $ipChange = !empty($oldIp) && !empty($newIp);
        $nameChange = !empty($newHostname) && $newHostname !== $hostname;

        if (empty($domain) || empty($hostname) || (! $ipChange && ! $nameChange)) {
            return $this->errorResponse(__('admin/modules.resellerclub_child_ns_params_required'));
        }

        try {
            $this->validateConfig();

            $orderId = $this->getOrderId($domain);
            if (!is_numeric($orderId)) {
                return $this->errorResponse($orderId);
            }

            // Rename the glue host first (LogicBoxes modify-cns-name).
            if ($nameChange) {
                $renameResult = $this->sendCommand('modify-cns-name', 'domains', [
                    'order-id' => $orderId,
                    'old-cns' => $hostname,
                    'new-cns' => $newHostname,
                ], 'POST');

                if ($this->isApiError($renameResult)) {
                    return $this->errorResponse($this->getApiErrorMessage($renameResult));
                }

                // Subsequent IP change targets the renamed host.
                $hostname = $newHostname;
            }

            if ($ipChange) {
                $result = $this->sendCommand('modify-cns-ip', 'domains', [
                    'order-id' => $orderId,
                    'cns' => $hostname,
                    'old-ip' => $oldIp,
                    'new-ip' => $newIp,
                ], 'POST');

                if ($this->isApiError($result)) {
                    return $this->errorResponse($this->getApiErrorMessage($result));
                }
            }

            return $this->successResponse(__('admin/modules.resellerclub_child_ns_updated'), [
                'domain' => $domain,
                'hostname' => $hostname,
                'ip' => $newIp ?: $oldIp,
            ]);
        } catch (\Exception $e) {
            return $this->errorResponse($e->getMessage());
        }
    }

    public function deleteChildNameserver(array $params)
    {
        $domain = $params['domain'] ?? '';
        $hostname = $params['hostname'] ?? $params['nameserver'] ?? '';
        $ip = $params['ip'] ?? '';

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

        try {
            $this->validateConfig();

            $orderId = $this->getOrderId($domain);
            if (!is_numeric($orderId)) {
                return $this->errorResponse($orderId);
            }

            $result = $this->sendCommand('delete-cns-ip', 'domains', [
                'order-id' => $orderId,
                'cns' => $hostname,
                'ip' => $ip,
            ], 'POST');

            if ($this->isApiError($result)) {
                return $this->errorResponse($this->getApiErrorMessage($result));
            }

            return $this->successResponse(__('admin/modules.resellerclub_child_ns_deleted'), [
                'domain' => $domain,
                'hostname' => $hostname,
            ]);
        } catch (\Exception $e) {
            return $this->errorResponse($e->getMessage());
        }
    }

    // -------------------------------------------------------------------------
    //  Domain Sync
    // -------------------------------------------------------------------------

    public function syncDomain(array $params)
    {
        $domain = $params['domain'] ?? '';
        if (empty($domain)) {
            return $this->errorResponse(__('admin/modules.resellerclub_domain_required'));
        }

        try {
            $this->validateConfig();

            $orderId = $this->getOrderId($domain);
            if (!is_numeric($orderId)) {
                // "Website doesn't exist for" means transferred away
                if (is_string($orderId) && stripos($orderId, "Website doesn't exist for") === 0) {
                    return $this->successResponse(__('admin/modules.resellerclub_domain_transferred_away'), [
                        'domain' => $domain,
                        'status' => 'transferred_away',
                        'transferred_away' => true,
                    ]);
                }

                return $this->errorResponse($orderId);
            }

            $result = $this->sendCommand('details', 'domains', [
                'order-id' => $orderId,
                'options' => 'All',
            ], 'GET');

            if ($this->isApiError($result)) {
                return $this->errorResponse($this->getApiErrorMessage($result));
            }

            $expiryTime = $result['endtime'] ?? null;
            $currentStatus = $result['currentstatus'] ?? '';

            if (!$expiryTime) {
                return $this->errorResponse(__('admin/modules.resellerclub_expiry_not_found'));
            }

            $status = 'unknown';
            if ($currentStatus === 'Active') {
                $status = 'active';
            } elseif ($currentStatus === 'Expired') {
                $status = 'expired';
            } elseif ($currentStatus === 'InActive') {
                $status = 'inactive';
            }

            return $this->successResponse(__('admin/modules.resellerclub_sync_success'), [
                'domain' => $domain,
                'status' => $status,
                'expires_at' => date('Y-m-d', $expiryTime),
                'synced_at' => now()->toISOString(),
            ]);
        } catch (\Exception $e) {
            return $this->errorResponse($e->getMessage());
        }
    }

    public function syncDomainTransfer(array $params)
    {
        $domain = $params['domain'] ?? '';
        if (empty($domain)) {
            return $this->errorResponse(__('admin/modules.resellerclub_domain_required'));
        }

        try {
            $this->validateConfig();

            $orderId = $this->getOrderId($domain);
            if (!is_numeric($orderId)) {
                return $this->errorResponse($orderId);
            }

            $result = $this->sendCommand('details', 'domains', [
                'order-id' => $orderId,
                'options' => 'All',
            ], 'GET');

            if ($this->isApiError($result)) {
                return $this->errorResponse($this->getApiErrorMessage($result));
            }

            $currentStatus = $result['currentstatus'] ?? '';

            if ($currentStatus === 'InActive') {
                return $this->successResponse(__('admin/modules.resellerclub_transfer_in_progress'), [
                    'domain' => $domain,
                    'status' => 'in_progress',
                    'in_progress' => true,
                ]);
            }

            $expiryTime = $result['endtime'] ?? null;
            if (!$expiryTime) {
                return $this->errorResponse(__('admin/modules.resellerclub_expiry_not_found'));
            }

            $status = 'unknown';
            if ($currentStatus === 'Active') {
                $status = 'active';
            } elseif ($currentStatus === 'Expired') {
                $status = 'expired';
            }

            return $this->successResponse(__('admin/modules.resellerclub_transfer_sync_success'), [
                'domain' => $domain,
                'status' => $status,
                'expires_at' => date('Y-m-d', $expiryTime),
                'completed' => $currentStatus === 'Active',
            ]);
        } catch (\Exception $e) {
            return $this->errorResponse($e->getMessage());
        }
    }

    // -------------------------------------------------------------------------
    //  Domain Availability
    // -------------------------------------------------------------------------

    public function checkDomainAvailability(array $params)
    {
        $domain = $params['domain'] ?? '';
        if (empty($domain)) {
            return $this->errorResponse(__('admin/modules.resellerclub_domain_required'));
        }

        try {
            $this->validateConfig();

            // Split domain into SLD and TLD
            $parts = explode('.', $domain, 2);
            $sld = $parts[0] ?? '';
            $tld = $parts[1] ?? '';

            if (empty($sld) || empty($tld)) {
                return $this->errorResponse(__('admin/modules.resellerclub_invalid_domain'));
            }

            // Handle multiple TLDs if provided. LogicBoxes wants the tlds
            // WITHOUT a leading dot (WHMCS array_walk substr($v,1)); strip it so
            // a caller-supplied '.com' still matches.
            $tlds = $params['tlds'] ?? [$tld];
            $tlds = array_values(array_filter(array_map(
                static fn ($t) => ltrim((string) $t, '.'),
                (array) $tlds
            )));

            $requestData = [
                'domain-name' => $sld,
                'tlds' => $tlds,
            ];

            // Use the special domain check endpoint
            $originalEndpoint = $this->apiEndpoint;
            $this->apiEndpoint = $this->availabilityEndpoint;

            $result = $this->sendCommand('available', 'domains', $requestData, 'GET');

            $this->apiEndpoint = $originalEndpoint;

            if ($this->isApiError($result)) {
                return $this->errorResponse($this->getApiErrorMessage($result));
            }

            // Parse results for single domain check
            $domainKey = $sld.'.'.$tld;
            $available = false;
            $status = 'unknown';

            if (isset($result[$domainKey])) {
                $domainData = $result[$domainKey];
                if (($domainData['status'] ?? '') === 'available') {
                    $available = true;
                    $status = 'available';
                } elseif (($domainData['status'] ?? '') === 'regthroughus') {
                    $status = 'registered_through_us';
                } elseif (($domainData['status'] ?? '') === 'regthroughothers') {
                    $status = 'registered';
                } else {
                    $status = $domainData['status'] ?? 'unknown';
                }
            }

            return [
                'status' => 'success',
                'message' => __('admin/modules.resellerclub_availability_checked'),
                'data' => [
                    'domain' => $domain,
                    'available' => $available,
                    'domain_status' => $status,
                    'registrar' => 'resellerclub',
                ],
                // Keep backward compatibility with old format
                'success' => true,
                'available' => $available,
                'domain' => $domain,
                'registrar' => 'resellerclub',
            ];
        } catch (\Exception $e) {
            return $this->errorResponse($e->getMessage());
        }
    }

    public function checkDomainSuggestions(array $params)
    {
        $keyword = $params['keyword'] ?? $params['search_term'] ?? '';
        if (empty($keyword)) {
            return $this->errorResponse(__('admin/modules.resellerclub_keyword_required'));
        }

        try {
            $this->validateConfig();

            $requestData = [
                'keyword' => $keyword,
            ];

            if (!empty($params['tlds'])) {
                // suggest-names expects tld-only WITHOUT a leading dot
                // (WHMCS tldsToInclude is dotless); strip so '.com' matches.
                $requestData['tld-only'] = array_values(array_filter(array_map(
                    static fn ($t) => ltrim((string) $t, '.'),
                    (array) $params['tlds']
                )));
            }

            $result = $this->sendCommand('suggest-names', 'domains/v5', $requestData, 'GET');

            if ($this->isApiError($result)) {
                return $this->errorResponse($this->getApiErrorMessage($result));
            }

            $suggestions = [];
            foreach ($result as $domainName => $domainData) {
                if (!is_array($domainData)) {
                    continue;
                }

                $suggestions[] = [
                    'domain' => $domainName,
                    'available' => ($domainData['status'] ?? '') === 'available',
                    'score' => $domainData['score'] ?? 0,
                ];
            }

            return $this->successResponse(__('admin/modules.resellerclub_suggestions_retrieved'), [
                'keyword' => $keyword,
                'suggestions' => $suggestions,
            ]);
        } catch (\Exception $e) {
            return $this->errorResponse($e->getMessage());
        }
    }

    // -------------------------------------------------------------------------
    //  Domain List & Pricing
    // -------------------------------------------------------------------------

    public function getDomainList(array $params)
    {
        try {
            $this->validateConfig();

            $requestData = [
                'no-of-records' => $params['limit'] ?? 25,
                'page-no' => $params['page'] ?? 1,
            ];

            if (!empty($params['status'])) {
                $requestData['status'] = $params['status'];
            }

            $result = $this->sendCommand('search', 'domains', $requestData, 'GET');

            if ($this->isApiError($result)) {
                return $this->errorResponse($this->getApiErrorMessage($result));
            }

            return $this->successResponse(__('admin/modules.resellerclub_domain_list_retrieved'), [
                'domains' => $result,
                'total' => $result['recsindb'] ?? 0,
            ]);
        } catch (\Exception $e) {
            return $this->errorResponse($e->getMessage());
        }
    }

    public function getDomainPricings(array $params)
    {
        try {
            $this->validateConfig();

            $result = $this->sendCommand('reseller-cost-price', 'products', [
                'customer-id' => $params['customer_id'] ?? $this->config['reseller_id'] ?? '',
            ], 'GET');

            if ($this->isApiError($result)) {
                return $this->errorResponse($this->getApiErrorMessage($result));
            }

            return $this->successResponse(__('admin/modules.resellerclub_pricing_retrieved'), [
                'pricings' => $result,
            ]);
        } catch (\Exception $e) {
            return $this->errorResponse($e->getMessage());
        }
    }

    // -------------------------------------------------------------------------
    //  Admin / Client Output
    // -------------------------------------------------------------------------

    public function adminOutput(array $params)
    {
        $domain = $params['domain'] ?? 'N/A';
        $orderId = $params['order_id'] ?? 'N/A';
        $status = $params['status'] ?? 'N/A';
        $expiresAt = $params['expires_at'] ?? 'N/A';

        return '<div class="card border-0 shadow-sm">'
            .'<div class="card-header"><h5 class="mb-0">'.__('admin/modules.resellerclub_admin_domain_info').'</h5></div>'
            .'<div class="card-body">'
            .'<table class="table table-sm">'
            .'<tbody>'
            .'<tr><th width="30%">'.__('admin/modules.resellerclub_domain').':</th><td>'.e($domain).'</td></tr>'
            .'<tr><th>'.__('admin/modules.resellerclub_order_id').':</th><td>'.e($orderId).'</td></tr>'
            .'<tr><th>'.__('admin/modules.resellerclub_status').':</th><td>'.e($status).'</td></tr>'
            .'<tr><th>'.__('admin/modules.resellerclub_expires').':</th><td>'.e($expiresAt).'</td></tr>'
            .'<tr><th>'.__('admin/modules.resellerclub_registrar').':</th><td>ResellerClub (LogicBoxes)</td></tr>'
            .'</tbody>'
            .'</table>'
            .'</div>'
            .'</div>';
    }

    public function clientOutput(array $params)
    {
        $domain = $params['domain'] ?? 'N/A';
        $expiresAt = $params['expires_at'] ?? 'N/A';
        $status = $params['status'] ?? 'N/A';

        return '<div class="card border-0 shadow-sm mb-4">'
            .'<div class="card-header"><h5 class="mb-0"><i class="fas fa-globe me-2"></i>'.__('admin/modules.resellerclub_client_domain_info').'</h5></div>'
            .'<div class="card-body">'
            .'<table class="table table-sm table-borderless">'
            .'<tbody>'
            .'<tr><td class="text-muted" width="40%"><i class="fas fa-globe me-2"></i>'.__('admin/modules.resellerclub_domain').':</td><td><strong>'.e($domain).'</strong></td></tr>'
            .'<tr><td class="text-muted"><i class="fas fa-info-circle me-2"></i>'.__('admin/modules.resellerclub_status').':</td><td>'.e($status).'</td></tr>'
            .'<tr><td class="text-muted"><i class="fas fa-calendar me-2"></i>'.__('admin/modules.resellerclub_expires').':</td><td>'.e($expiresAt).'</td></tr>'
            .'</tbody>'
            .'</table>'
            .'</div>'
            .'</div>';
    }

    // =========================================================================
    //  PROTECTED / PRIVATE HELPERS
    // =========================================================================

    /**
     * Send a command to the LogicBoxes API
     */
    protected function sendCommand(string $command, string $type, array $data, string $method = 'GET'): mixed
    {
        $data['auth-userid'] = $this->config['reseller_id'] ?? '';
        $data['api-key'] = $this->config['api_key'] ?? '';

        $url = $this->apiEndpoint.$type.'/'.$command.'.json';

        $ch = curl_init();

        if ($method === 'GET') {
            $queryString = $this->buildQueryString($data);
            $url .= '?'.$queryString;
            curl_setopt($ch, CURLOPT_URL, $url);
        } else {
            curl_setopt($ch, CURLOPT_URL, $url);
            curl_setopt($ch, CURLOPT_POST, 1);
            curl_setopt($ch, CURLOPT_POSTFIELDS, $this->buildQueryString($data));
        }

        curl_setopt($ch, CURLOPT_RETURNTRANSFER, true);
        curl_setopt($ch, CURLOPT_SSL_VERIFYPEER, true);
        curl_setopt($ch, CURLOPT_SSL_VERIFYHOST, 2);
        curl_setopt($ch, CURLOPT_TIMEOUT, 60);
        curl_setopt($ch, CURLOPT_HTTPHEADER, [
            'Content-Type: application/x-www-form-urlencoded',
            'Accept: application/json',
        ]);

        $response = curl_exec($ch);
        $httpCode = curl_getinfo($ch, CURLINFO_HTTP_CODE);
        $curlError = curl_error($ch);

        curl_close($ch);

        if ($curlError) {
            Log::error('ResellerClub API curl error', [
                'url' => $this->redactUrl($url),
                'error' => $curlError,
                'command' => $type.'/'.$command,
            ]);

            return [
                'status' => 'ERROR',
                'message' => __('admin/modules.resellerclub_curl_error', ['error' => $curlError]),
            ];
        }

        // Handle non-JSON numeric responses (e.g. order IDs)
        if (is_numeric($response)) {
            return $response;
        }

        $responseData = json_decode($response, true);

        if ($httpCode >= 400 || $responseData === null) {
            Log::error('ResellerClub API error', [
                'command' => $type.'/'.$command,
                'http_code' => $httpCode,
                'response' => $response,
            ]);

            return [
                'status' => 'ERROR',
                'message' => __('admin/modules.resellerclub_api_call_failed', ['code' => $httpCode]),
                'http_code' => $httpCode,
            ];
        }

        return $responseData;
    }

    /**
     * Build a query string that supports array parameters
     * (LogicBoxes uses repeated params like ns=ns1&ns=ns2)
     */
    protected function buildQueryString(array $data): string
    {
        $parts = [];
        foreach ($data as $key => $value) {
            if (is_array($value)) {
                foreach ($value as $subValue) {
                    $parts[] = urlencode($key).'='.urlencode($subValue);
                }
            } else {
                $parts[] = urlencode($key).'='.urlencode((string) $value);
            }
        }

        return implode('&', $parts);
    }

    /**
     * Redact credentials from a URL before logging (PRC-49: module_config /
     * decrypted creds must never reach a log). Strips the query string and
     * redacts any `api-key` / `auth-userid` / `reseller_id` values.
     */
    protected function redactUrl(string $url): string
    {
        // Drop the entire query string — it carries api-key + auth-userid on GET.
        $base = strtok($url, '?');

        // Defensive redaction in case creds ever appear elsewhere in the URL.
        $base = preg_replace(
            '/(api-key|auth-userid|reseller_id)=[^&]*/i',
            '$1=REDACTED',
            (string) $base
        );

        return $base;
    }

    /**
     * Get the LogicBoxes order ID for a domain
     */
    protected function getOrderId(string $domain): mixed
    {
        $result = $this->sendCommand('orderid', 'domains', [
            'domain-name' => $domain,
        ], 'GET');

        if (is_numeric($result)) {
            return $result;
        }

        if (is_array($result)) {
            if (isset($result['response']['status']) && strtoupper($result['response']['status']) === 'ERROR') {
                return $result['response']['message'] ?? __('admin/modules.resellerclub_order_id_error');
            }
            if ($this->isApiError($result)) {
                return $this->getApiErrorMessage($result);
            }
        }

        return __('admin/modules.resellerclub_order_id_error');
    }

    /**
     * Check whether the API result is an error
     */
    protected function isApiError($result): bool
    {
        if (!is_array($result)) {
            return false;
        }

        $status = strtoupper($result['status'] ?? '');
        if ($status === 'ERROR') {
            return true;
        }

        if (isset($result['response']['status']) && strtoupper($result['response']['status']) === 'ERROR') {
            return true;
        }

        return false;
    }

    /**
     * Extract a human-readable error message from an API result
     */
    protected function getApiErrorMessage($result): string
    {
        if (!is_array($result)) {
            return (string) $result;
        }

        return $result['message']
            ?? $result['error']
            ?? $result['response']['message'] ?? $result['response']['error']
            ?? __('admin/modules.resellerclub_unknown_error');
    }

    /**
     * Split a canonical phone string into LogicBoxes [phone-cc, phone].
     *
     * The PRC-49 canonical `phone` is a single free-form string that may carry
     * a leading dialing code (e.g. "+90 555 111 2233"). LogicBoxes wants the
     * dialing code (`phone-cc`, digits only) SEPARATE from the national number
     * (`phone`, digits only). The split is NON-LOSSY: phone-cc . phone always
     * reconstructs every digit of the original number.
     *
     * Mirrors the WHMCS resellerclub_addCustomer/addContact "+1 NANP" collapse
     * (phone-cc "1NNN" → cc "1" + area code prepended to the number).
     *
     * @return array{0:string,1:string}  [phone-cc, phone]
     */
    protected function splitPhoneCc(string $phone): array
    {
        $trimmed = trim($phone);
        $hasPlus = str_starts_with($trimmed, '+');
        $digits = preg_replace('/[^0-9]/', '', $trimmed);

        if ($digits === '') {
            return ['', ''];
        }

        // Only treat a leading segment as a dialing code when the source was
        // explicitly prefixed with "+" — otherwise we cannot reliably tell a
        // country code from an area code, so keep the whole number local.
        if (! $hasPlus) {
            return ['', $digits];
        }

        // WHMCS NANP collapse: "1NNN" style cc → cc "1", area code stays local.
        if (preg_match('/^1(\d{3})/', $digits, $m) && strlen($digits) > 4) {
            return ['1', substr($digits, 1)];
        }

        // Otherwise take a 1–3 digit dialing prefix (E.164 max is 3).
        $ccLen = min(3, strlen($digits) - 1);
        $ccLen = max(1, $ccLen);
        // Prefer common lengths without a country map: 2 digits (e.g. 90) is
        // the most typical non-NANP case; fall back to 1 if that is all we have.
        if (strlen($digits) <= 2) {
            $ccLen = 1;
        } else {
            $ccLen = 2;
        }

        return [substr($digits, 0, $ccLen), substr($digits, $ccLen)];
    }

    /**
     * Build the LogicBoxes customer-signup field array from the canonical
     * PRC-49 registrant contact. Pure — no network. VERIFIED against WHMCS
     * resellerclub_addCustomer (username/passwd/name/company/address-line-1/2/
     * city/state/zipcode/country/phone-cc/phone/lang-pref).
     *
     * @return array<string,string>
     */
    protected function buildCustomerParams(array $params): array
    {
        $c = $this->contact($params, 'registrant');

        [$phoneCc, $phone] = $this->splitPhoneCc($c['phone'] ?? '');

        return [
            'username'       => $c['email'] ?? '',
            'passwd'         => $this->generateRandomPassword(),
            'name'           => trim(($c['first_name'] ?? '').' '.($c['last_name'] ?? '')) ?: 'Domain Owner',
            'company'        => ($c['company'] ?? '') !== '' ? $c['company'] : 'N/A',
            'address-line-1' => substr($c['address1'] ?? '', 0, 64),
            'address-line-2' => substr($c['address2'] ?? '', 0, 64),
            'city'           => $c['city'] ?? '',
            'state'          => $c['state'] ?? '',
            'zipcode'        => $c['postcode'] ?? '',
            'country'        => $c['country_code'] ?? '',
            'phone-cc'       => $phoneCc,
            'phone'          => $phone,
            'lang-pref'      => $params['client_language'] ?? $params['language'] ?? 'en',
        ];
    }

    /**
     * Build the LogicBoxes contact-add field array for a given group from the
     * canonical PRC-49 contact. Pure — no network. VERIFIED against WHMCS
     * resellerclub_addContact (customer-id/email/name/company/address-line-1/2/
     * city/state/zipcode/country/phone-cc/phone/type).
     *
     * @param  string  $group  registrant|admin|tech|billing
     * @return array<string,string>
     */
    protected function buildContactParams(array $params, string $group, $customerId): array
    {
        $c = $this->contact($params, $group);

        [$phoneCc, $phone] = $this->splitPhoneCc($c['phone'] ?? '');

        return [
            'customer-id'    => (string) $customerId,
            'email'          => $c['email'] ?? '',
            'name'           => trim(($c['first_name'] ?? '').' '.($c['last_name'] ?? '')) ?: 'Domain Owner',
            'company'        => ($c['company'] ?? '') !== '' ? $c['company'] : 'N/A',
            'address-line-1' => substr($c['address1'] ?? '', 0, 64),
            'address-line-2' => substr($c['address2'] ?? '', 0, 64),
            'city'           => $c['city'] ?? '',
            'state'          => $c['state'] ?? '',
            'zipcode'        => $c['postcode'] ?? '',
            'country'        => $c['country_code'] ?? '',
            'phone-cc'       => $phoneCc,
            'phone'          => $phone,
            'type'           => 'Contact',
        ];
    }

    /**
     * Get or create a customer in the LogicBoxes system
     */
    protected function getOrCreateCustomer(array $params): mixed
    {
        $createData = $this->buildCustomerParams($params);
        $email = $createData['username'];

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

        // Try to find existing customer
        $result = $this->sendCommand('details', 'customers', [
            'username' => $email,
        ], 'GET');

        if (!$this->isApiError($result) && !empty($result['customerid'])) {
            return $result['customerid'];
        }

        $result = $this->sendCommand('signup', 'customers', $createData, 'POST');

        if ($this->isApiError($result)) {
            return $this->errorResponse($this->getApiErrorMessage($result));
        }

        if (is_numeric($result)) {
            return $result;
        }

        return $result['customerid'] ?? $this->errorResponse(__('admin/modules.resellerclub_customer_create_failed'));
    }

    /**
     * Get or create a contact in the LogicBoxes system
     */
    protected function getOrCreateContact(array $params, $customerId, string $group = 'registrant'): mixed
    {
        $contactData = $this->buildContactParams($params, $group, $customerId);

        $result = $this->sendCommand('add', 'contacts', $contactData, 'POST');

        if ($this->isApiError($result)) {
            return $this->errorResponse($this->getApiErrorMessage($result));
        }

        if (is_numeric($result)) {
            return $result;
        }

        return $result['contactid'] ?? $this->errorResponse(__('admin/modules.resellerclub_contact_create_failed'));
    }

    /**
     * Create the four LogicBoxes contacts for a register/transfer and return
     * their ids keyed registrant/admin/tech/billing. Each non-registrant group
     * is created from its own PRC-969 contact; on empty/error it falls back to
     * the registrant contact id (WHMCS "Admin ?: Registrant" behaviour). The
     * registrant contact is created once and reused for identical groups.
     *
     * @return array{registrant:mixed,admin:mixed,tech:mixed,billing:mixed}|array{status:string,message:string,data:array}
     */
    protected function createDomainContacts(array $params, $customerId): mixed
    {
        $registrantId = $this->getOrCreateContact($params, $customerId, 'registrant');
        if (is_array($registrantId) && ($registrantId['status'] ?? '') === 'error') {
            return $registrantId;
        }

        $ids = ['registrant' => $registrantId];

        foreach (['admin', 'tech', 'billing'] as $group) {
            // Explicit override contact id from the caller wins.
            $override = $params[$group.'_contact_id'] ?? null;
            if (!empty($override)) {
                $ids[$group] = $override;
                continue;
            }

            $c = $this->contact($params, $group);
            // No usable data for this group → reuse registrant (WHMCS fallback).
            if (empty($c['email']) && empty($c['first_name'])) {
                $ids[$group] = $registrantId;
                continue;
            }

            $gid = $this->getOrCreateContact($params, $customerId, $group);
            // On error for a secondary group, degrade to registrant rather than fail.
            $ids[$group] = (is_array($gid) && ($gid['status'] ?? '') === 'error')
                ? $registrantId
                : $gid;
        }

        return $ids;
    }

    /**
     * TLDs that reject some/all of admin/tech/billing contacts on
     * register AND transfer — LogicBoxes requires contact-id "-1" for these.
     * Mirrors WHMCS resellerclub_DomainTLDSpecificFields (keyed on
     * getLastTLDSegment): uk/eu/nz/ru drop admin+tech+billing; ca/nl/london
     * drop billing only. Applied to both register and transfer via
     * array_merge (WHMCS calls DomainTLDSpecificFields in both paths).
     *
     * @return array{admin:bool,tech:bool,billing:bool}
     */
    protected function contactlessFlags(string $domain): array
    {
        $tld = strtolower(explode('.', $domain, 2)[1] ?? '');
        $lastSegment = strtolower(substr(strrchr('.'.$domain, '.'), 1));

        // WHMCS getLastTLDSegment: uk/eu/nz/ru → admin+tech+billing "-1".
        $noAdminTechBilling = ['uk', 'eu', 'nz', 'ru'];
        // WHMCS getLastTLDSegment: ca/nl/london → billing "-1" only.
        $noBillingOnly = ['ca', 'nl', 'london'];

        $match = fn (array $list) => in_array($tld, $list, true) || in_array($lastSegment, $list, true);

        $dropAll = $match($noAdminTechBilling);

        return [
            'admin' => $dropAll,
            'tech' => $dropAll,
            'billing' => $dropAll || $match($noBillingOnly),
        ];
    }

    /**
     * Backward-compat alias. Subclasses (Netearthone) still call the old name
     * from their overridden transferDomain(); keep it delegating so renaming
     * the canonical helper does not break them.
     *
     * @return array{admin:bool,tech:bool,billing:bool}
     */
    protected function transferContactlessFlags(string $domain): array
    {
        return $this->contactlessFlags($domain);
    }

    /**
     * Build the nameserver list string for LogicBoxes API
     * (uses &ns= concatenation)
     */
    protected function buildNameserverList(array $params): string
    {
        $nameservers = $params['nameservers'] ?? [];

        // Also check ns1..ns5 format
        if (empty($nameservers)) {
            for ($i = 1; $i <= 5; $i++) {
                if (!empty($params['ns'.$i])) {
                    $nameservers[] = $params['ns'.$i];
                }
            }
        }

        if (empty($nameservers)) {
            $nameservers = ['ns1.example.com', 'ns2.example.com'];
        }

        return implode('&ns=', $nameservers);
    }

    /**
     * Build LogicBoxes attr-name{N}/attr-value{N} pairs for a register/transfer
     * payload. Mirrors WHMCS resellerclub_DomainTLDSpecificFields + WiseCP
     * register() attr loop: every TLD-specific field ($params['additional_fields'],
     * emitted by DomainModuleParamsBuilder from client_domain_fields) plus an
     * optional idnLanguageCode and premium flag is forwarded as a sequentially
     * numbered attr pair. Without this .us/.ca/.eu/.es/.au + IDN TLDs are rejected.
     *
     * @return array<string,string>
     */
    protected function buildTldExtraAttributes(array $params, string $domain): array
    {
        $attrs = [];

        // TLD-specific fields forwarded verbatim (attr-name = field key).
        $additional = $params['additional_fields'] ?? [];
        if (is_array($additional)) {
            foreach ($additional as $k => $v) {
                if ($k === '' || $v === null || $v === '') {
                    continue;
                }
                $attrs[(string) $k] = is_array($v) ? implode('|', $v) : (string) $v;
            }
        }

        // IDN language code (LogicBoxes requires it for IDN registrations).
        $idnLang = $params['idn_language_code'] ?? $params['idnLanguageCode'] ?? '';
        if ($idnLang !== '' && !isset($attrs['idnLanguageCode'])) {
            $attrs['idnLanguageCode'] = (string) $idnLang;
        }

        // Premium flag (kept as an attr pair per WHMCS).
        if (!empty($params['is_premium']) && !empty($this->config['enable_premium_domains'])) {
            $attrs['premium'] = 'true';
        }

        // Renumber into attr-name{N}/attr-value{N}.
        $out = [];
        $i = 0;
        foreach ($attrs as $name => $value) {
            $i++;
            $out['attr-name'.$i] = $name;
            $out['attr-value'.$i] = $value;
        }

        return $out;
    }

    /**
     * Whether test mode is enabled
     */
    protected function isTestMode(): bool
    {
        $testMode = $this->config['test_mode'] ?? false;

        return filter_var($testMode, FILTER_VALIDATE_BOOLEAN);
    }

    /**
     * Generate a random password for LogicBoxes customer creation
     */
    protected function generateRandomPassword(): string
    {
        $lowercase = 'abcdefghijklmnopqrstuvwxyz';
        $uppercase = 'ABCDEFGHIJKLMNOPQRSTUVWXYZ';
        $numbers = '0123456789';
        $symbols = '!@#$%^&*';

        $password = '';
        for ($i = 0; $i < 4; $i++) {
            $password .= $lowercase[random_int(0, strlen($lowercase) - 1)];
            $password .= $uppercase[random_int(0, strlen($uppercase) - 1)];
            $password .= $numbers[random_int(0, strlen($numbers) - 1)];
        }
        $password .= $symbols[random_int(0, strlen($symbols) - 1)];

        // Shuffle
        return str_shuffle($password);
    }

    /**
     * Standard success response
     */
    protected function successResponse(string $message, array $data = []): array
    {
        return [
            'status' => 'success',
            'message' => $message,
            'data' => $data,
        ];
    }

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

    /**
     * Override callApi from AbstractRegistrar to use our sendCommand
     */
    protected function callApi($endpoint, $method = 'GET', $data = [])
    {
        // Parse endpoint: e.g. "domains/register.json" -> command="register", type="domains"
        $endpoint = str_replace('.json', '', $endpoint);
        $parts = explode('/', $endpoint);
        $command = array_pop($parts);
        $type = implode('/', $parts);

        return $this->sendCommand($command, $type, $data, $method);
    }
}
