<?php

namespace App\Modules\Registrar\Oxxa;

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

class Oxxa extends AbstractRegistrar
{
    /**
     * OXXA API endpoint
     */
    protected string $apiUrl = 'https://api.oxxa.com/command.php';

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

    /**
     * Get module configuration fields
     */
    public function getConfigFields(): array
    {
        return [
            'api_user' => [
                'type' => 'text',
                'label' => __('admin/modules.oxxa_field_api_user_label'),
                'required' => true,
                'description' => __('admin/modules.oxxa_field_api_user_description'),
                'placeholder' => 'your_api_user',
                'value' => $this->config['api_user'] ?? '',
            ],
            'api_password' => [
                'type' => 'password',
                'label' => __('admin/modules.oxxa_field_api_password_label'),
                'required' => true,
                'description' => __('admin/modules.oxxa_field_api_password_description'),
                'placeholder' => '********',
                'value' => $this->config['api_password'] ?? '',
            ],
        ];
    }

    // -------------------------------------------------------------------------
    //  API Helpers
    // -------------------------------------------------------------------------

    /**
     * Call OXXA API with given command and parameters.
     *
     * OXXA is an XML-over-HTTP GET API: apiuser/apipassword/command + params are
     * sent as a query string (ref: hostbill api.php::_send → strtolower(name)=value).
     *
     * @param  string  $command  The OXXA API command
     * @param  array  $method  Additional GET parameters (bc: 2nd arg = params)
     * @return array Parsed API response
     */
    protected function callApi($endpoint, $method = 'GET', $data = [])
    {
        // Backward compatibility: old signature was callApi($command, $params = [])
        $command = $endpoint;
        $params = is_array($method) ? $method : $data;

        $query = array_merge([
            'apiuser' => $this->config['api_user'] ?? '',
            'apipassword' => $this->config['api_password'] ?? '',
            'command' => $command,
        ], $params);

        // OXXA lowercases every key on the wire (ref: api.php:58 strtolower($name)).
        $lowered = [];
        foreach ($query as $name => $value) {
            $lowered[strtolower((string) $name)] = $value;
        }

        Log::debug('OXXA API request', [
            'command' => $command,
            'params' => array_diff_key($lowered, ['apipassword' => '']),
        ]);

        $ch = curl_init();
        curl_setopt($ch, CURLOPT_URL, $this->apiUrl.'?'.http_build_query($lowered));
        curl_setopt($ch, CURLOPT_RETURNTRANSFER, true);
        curl_setopt($ch, CURLOPT_HEADER, false);
        curl_setopt($ch, CURLOPT_SSL_VERIFYPEER, false);
        curl_setopt($ch, CURLOPT_SSL_VERIFYHOST, false);
        curl_setopt($ch, CURLOPT_TIMEOUT, 60);

        $response = curl_exec($ch);
        $curlError = curl_error($ch);
        $curlErrno = curl_errno($ch);
        curl_close($ch);

        if ($curlErrno || ! $response) {
            Log::error('OXXA: cURL error', ['errno' => $curlErrno, 'error' => $curlError]);

            return $this->errorResponse(
                __('admin/modules.oxxa_curl_error', ['error' => $curlError ?: 'No response'])
            );
        }

        return $this->parseXmlResponse($response);
    }

    /**
     * Parse OXXA XML response.
     *
     * simplexml root IS <channel>; the payload node is <order> directly beneath it
     * (ref: api.php:41-51 → $xml->order). Success token is XMLOK, error XMLERR,
     * derived by stripping digits/whitespace from status_code (ref: api.php:45,48).
     *
     * @param  string  $xml  Raw XML response
     * @return array Parsed response with status, message, and data
     */
    protected function parseXmlResponse(string $xml): array
    {
        $parsed = @simplexml_load_string($xml);

        if ($parsed === false) {
            Log::error('OXXA: Invalid XML response', ['response' => substr($xml, 0, 500)]);

            return $this->errorResponse(__('admin/modules.oxxa_invalid_xml'));
        }

        $order = $parsed->order ?? null;

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

        $statusCode = (string) ($order->status_code ?? '');
        // OXXA status_code is "<TOKEN> <number>" e.g. "XMLOK 11". The alpha token
        // is XMLOK/XMLERR (ref: api.php:45); the numeric part is the granular code
        // (ref: api.php:46 status_no, Cyberfusion StatusCode enum). Availability is
        // carried ONLY by the number (10=taken, 11=free), not by any details field.
        $statusToken = preg_replace('/[0-9\s]/', '', $statusCode); // XMLOK / XMLERR
        $statusNo = (int) preg_replace('/[^0-9]/', '', $statusCode);
        $statusDescription = (string) ($order->status_description ?? '');

        if ($statusToken !== 'XMLOK') {
            Log::warning('OXXA: API error', [
                'status_code' => $statusCode,
                'status_description' => $statusDescription,
            ]);

            return $this->errorResponse($statusDescription ?: __('admin/modules.oxxa_api_error'));
        }

        // Convert details node to array
        $details = [];
        if (isset($order->details)) {
            $details = json_decode(json_encode($order->details), true) ?: [];
        }

        $response = $this->successResponse($statusDescription, $details);
        $response['status_no'] = $statusNo; // granular numeric code (e.g. 11 = available)

        return $response;
    }

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

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

    /**
     * Split a domain into SLD and TLD parts (TLD from the domain string, PRC-49).
     */
    protected function splitDomain(string $domain): array
    {
        $parts = explode('.', $domain, 2);

        return [
            'sld' => $parts[0] ?? '',
            'tld' => $parts[1] ?? '',
        ];
    }

    // -------------------------------------------------------------------------
    //  Contact / NSGROUP handle helpers (OXXA handle-based model)
    // -------------------------------------------------------------------------

    /**
     * Build an OXXA identity (contact) payload from a canonical contact group.
     *
     * Canonical fields come from AbstractRegistrar::contact() (PRC-969). OXXA
     * expects: company(Y/N), firstname, lastname, street, number, postalcode,
     * city, state, tel, email, country [, company_name, fax]
     * (ref: class.oxxa.php::prepareContact line 322). The house `number`
     * (address2/last address token) is a distinct OXXA field.
     */
    protected function prepareContact(array $params, string $group): array
    {
        $c = $this->contact($params, $group);

        // OXXA wants a separate street/number pair; canonical address1=street,
        // address2=number. Fall back to the last token of street if address2 empty.
        $street = trim((string) $c['address1']);
        $number = trim((string) $c['address2']);
        if ($number === '' && $street !== '') {
            $tokens = explode(' ', $street);
            if (count($tokens) > 1) {
                $number = array_pop($tokens);
                $street = implode(' ', $tokens);
            }
        }
        if ($number === '') {
            $number = '00';
        }

        $hasCompany = trim((string) $c['company']) !== '';

        $contact = [
            'company' => $hasCompany ? 'Y' : 'N',
            'firstname' => $c['first_name'],
            'lastname' => $c['last_name'],
            'street' => $street,
            'number' => $number,
            'postalcode' => $c['postcode'],
            'city' => $c['city'],
            'state' => $c['state'],
            'tel' => $c['phone'] !== '' ? $c['phone'] : '000000000',
            'email' => $c['email'],
            'country' => $c['country_code'],
        ];

        if ($hasCompany) {
            $contact['company_name'] = $c['company'];
        }

        // Forward TLD-specific extra fields (.nl/.be etc.) into the identity
        // (ref: class.oxxa.php:332-336 folds options['ext'] into the contact).
        foreach (($params['additional_fields'] ?? []) as $key => $value) {
            if ($value !== '' && $value !== null && ! isset($contact[$key])) {
                $contact[$key] = $value;
            }
        }

        return $contact;
    }

    /**
     * Create an OXXA identity handle for a contact group and return the handle.
     * (ref: class.oxxa.php::registerContact → identity_add(...)->details)
     */
    protected function mintIdentity(array $params, string $group): string
    {
        $result = $this->callApi('identity_add', $this->prepareContact($params, $group));

        if ($result['status'] !== 'success') {
            throw new \Exception($result['message'] ?: 'identity_add failed');
        }

        return $this->scalar($result['data']);
    }

    /**
     * Create an OXXA NSGROUP handle from the nameserver list and return the handle.
     * OXXA has no per-domain ns1..ns4; nameservers live behind an NSGROUP handle
     * with keys ALIAS + NS{n}_FQDN, minimum 2 (ref: class.oxxa.php::registerNSGroup).
     */
    protected function buildNsGroup(string $domain, array $nameservers): string
    {
        $payload = ['ALIAS' => $domain];
        $n = 0;
        foreach ($nameservers as $ns) {
            $ns = trim((string) $ns);
            if ($ns === '') {
                continue;
            }
            $n++;
            $payload['NS'.$n.'_FQDN'] = $ns;
            if ($n >= 6) {
                break;
            }
        }

        if ($n < 2) {
            throw new \Exception((string) __('admin/modules.oxxa_min_two_nameservers'));
        }

        $result = $this->callApi('nsgroup_add', $payload);

        if ($result['status'] !== 'success') {
            // Fall back to an existing group with this alias (ref: nsgroup_list).
            $list = $this->callApi('nsgroup_list', ['alias' => $domain]);
            $handle = $list['status'] === 'success'
                ? ($list['data']['nsgroup']['handle'] ?? null)
                : null;

            if (empty($handle)) {
                throw new \Exception($result['message'] ?: 'nsgroup_add failed');
            }

            return (string) $handle;
        }

        return $this->scalar($result['data']);
    }

    /**
     * Reduce an OXXA details payload to a scalar handle string.
     * identity_add / nsgroup_add return the handle directly under ->details.
     */
    protected function scalar($value): string
    {
        if (is_array($value)) {
            // Empty simplexml scalar decodes to [] → treat as empty.
            return '';
        }

        return trim((string) $value);
    }

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

    /**
     * Register a domain.
     *
     * OXXA register payload: SLD/TLD/PERIOD/AUTORENEW/LOCK + four identity-*
     * handles + a single NSGROUP handle (ref: class.oxxa.php::Register).
     */
    public function registerDomain(array $params)
    {
        $domain = $params['domain'] ?? '';
        if (empty($domain)) {
            return $this->errorResponse(__('admin/modules.oxxa_domain_required'));
        }

        $domainParts = $this->splitDomain($domain);
        $period = (int) ($params['period'] ?? 1);

        try {
            $identityRegistrant = $this->mintIdentity($params, 'registrant');
            $identityAdmin = $this->mintIdentity($params, 'admin');
            $identityTech = $this->mintIdentity($params, 'tech');
            $identityBilling = $this->mintIdentity($params, 'billing');
            $nsGroup = $this->buildNsGroup($domain, $params['nameservers'] ?? []);
        } catch (\Exception $e) {
            Log::error('OXXA: register prerequisites failed', ['domain' => $domain, 'error' => $e->getMessage()]);

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

        $apiParams = [
            'SLD' => $domainParts['sld'],
            'TLD' => $domainParts['tld'],
            'PERIOD' => $period,
            'AUTORENEW' => ! empty($params['auto_renew']) ? 'Y' : 'N',
            'LOCK' => 'Y',
            'IDENTITY-REGISTRANT' => $identityRegistrant,
            'IDENTITY-ADMIN' => $identityAdmin,
            'IDENTITY-TECH' => $identityTech,
            'IDENTITY-BILLING' => $identityBilling,
            'NSGROUP' => $nsGroup,
        ];

        // Forward TLD-specific extra fields (.nl/.be etc.) into the register call.
        foreach (($params['additional_fields'] ?? []) as $key => $value) {
            if ($value !== '' && $value !== null && ! isset($apiParams[$key])) {
                $apiParams[$key] = $value;
            }
        }

        $result = $this->callApi('register', $apiParams);

        if ($result['status'] === 'success') {
            Log::info('OXXA: Domain registered', ['domain' => $domain]);

            return $this->successResponse(
                __('admin/modules.oxxa_register_success'),
                [
                    'domain' => $domain,
                    'period' => $period,
                    'registered_at' => now()->toISOString(),
                ]
            );
        }

        return $result;
    }

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

    /**
     * Transfer a domain.
     *
     * OXXA transfer payload: SLD/TLD/PERIOD/TRANS_EPP + four IDENTITY-* handles +
     * NSGROUP (ref: class.oxxa.php::Transfer).
     */
    public function transferDomain(array $params)
    {
        $domain = $params['domain'] ?? '';
        if (empty($domain)) {
            return $this->errorResponse(__('admin/modules.oxxa_domain_required'));
        }

        $domainParts = $this->splitDomain($domain);
        $period = (int) ($params['period'] ?? 1);
        $eppCode = $params['epp_code'] ?? $params['authcode'] ?? '';

        try {
            $identityRegistrant = $this->mintIdentity($params, 'registrant');
            $identityAdmin = $this->mintIdentity($params, 'admin');
            $identityTech = $this->mintIdentity($params, 'tech');
            $identityBilling = $this->mintIdentity($params, 'billing');
            $nsGroup = $this->buildNsGroup($domain, $params['nameservers'] ?? []);
        } catch (\Exception $e) {
            Log::error('OXXA: transfer prerequisites failed', ['domain' => $domain, 'error' => $e->getMessage()]);

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

        $apiParams = [
            'SLD' => $domainParts['sld'],
            'TLD' => $domainParts['tld'],
            'PERIOD' => $period,
            'TRANS_EPP' => $eppCode,
            'IDENTITY-REGISTRANT' => $identityRegistrant,
            'IDENTITY-ADMIN' => $identityAdmin,
            'IDENTITY-TECH' => $identityTech,
            'IDENTITY-BILLING' => $identityBilling,
            'NSGROUP' => $nsGroup,
        ];

        $result = $this->callApi('transfer', $apiParams);

        if ($result['status'] === 'success') {
            Log::info('OXXA: Domain transfer initiated', ['domain' => $domain]);

            return $this->successResponse(
                __('admin/modules.oxxa_transfer_success'),
                [
                    'domain' => $domain,
                    'transfer_started_at' => now()->toISOString(),
                ]
            );
        }

        return $result;
    }

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

    /**
     * Renew a domain (ref: class.oxxa.php::Renew → renew with SLD/TLD/PERIOD).
     */
    public function renewDomain(array $params)
    {
        $domain = $params['domain'] ?? '';
        if (empty($domain)) {
            return $this->errorResponse(__('admin/modules.oxxa_domain_required'));
        }

        $domainParts = $this->splitDomain($domain);
        $period = (int) ($params['period'] ?? 1);

        $result = $this->callApi('renew', [
            'SLD' => $domainParts['sld'],
            'TLD' => $domainParts['tld'],
            'PERIOD' => $period,
        ]);

        if ($result['status'] === 'success') {
            Log::info('OXXA: Domain renewed', ['domain' => $domain]);

            return $this->successResponse(
                __('admin/modules.oxxa_renew_success'),
                [
                    'domain' => $domain,
                    'period' => $period,
                    'renewed_at' => now()->toISOString(),
                ]
            );
        }

        return $result;
    }

    // -------------------------------------------------------------------------
    //  Nameservers (OXXA NSGROUP indirection)
    // -------------------------------------------------------------------------

    /**
     * Fetch the NSGROUP handle bound to a domain via domain_inf.
     *
     * @return array{status:string, handle?:string, error?:array}
     */
    protected function fetchNsGroupHandle(array $domainParts): array
    {
        $info = $this->callApi('domain_inf', [
            'SLD' => $domainParts['sld'],
            'TLD' => $domainParts['tld'],
        ]);

        if ($info['status'] !== 'success') {
            return ['status' => 'error', 'error' => $info];
        }

        $handle = $info['data']['nsgroup'] ?? null;
        if (empty($handle) || ! is_string($handle)) {
            return ['status' => 'empty'];
        }

        return ['status' => 'success', 'handle' => $handle];
    }

    /**
     * Set nameservers for a domain via NSGROUP update.
     *
     * OXXA has no domain_dns command; nameservers are updated on the domain's
     * NSGROUP handle with keys NS{n}_FQDN, minimum 2
     * (ref: class.oxxa.php::updateNameServers).
     */
    public function setNameservers(array $params)
    {
        $domain = $params['domain'] ?? '';
        if (empty($domain)) {
            return $this->errorResponse(__('admin/modules.oxxa_domain_required'));
        }

        $domainParts = $this->splitDomain($domain);
        $nameservers = array_values(array_filter(array_map('trim', $params['nameservers'] ?? [])));

        if (count($nameservers) < 2) {
            return $this->errorResponse(__('admin/modules.oxxa_min_two_nameservers'));
        }

        $handleResult = $this->fetchNsGroupHandle($domainParts);
        if ($handleResult['status'] === 'error') {
            return $handleResult['error'];
        }

        if ($handleResult['status'] !== 'success') {
            // No group bound yet — create one and rebind via domain_upd.
            try {
                $handle = $this->buildNsGroup($domain, $nameservers);
            } catch (\Exception $e) {
                return $this->errorResponse($e->getMessage());
            }

            $rebind = $this->callApi('domain_upd', [
                'SLD' => $domainParts['sld'],
                'TLD' => $domainParts['tld'],
                'NSGROUP' => $handle,
            ]);

            if ($rebind['status'] === 'success') {
                return $this->successResponse(
                    __('admin/modules.oxxa_nameservers_updated'),
                    ['domain' => $domain, 'nameservers' => $nameservers, 'updated_at' => now()->toISOString()]
                );
            }

            return $rebind;
        }

        $apiParams = [
            'nsgroup' => $handleResult['handle'],
            'alias' => $domain,
        ];
        $n = 0;
        foreach ($nameservers as $ns) {
            $n++;
            $apiParams['NS'.$n.'_FQDN'] = $ns;
            if ($n >= 6) {
                break;
            }
        }

        $result = $this->callApi('nsgroup_upd', $apiParams);

        if ($result['status'] === 'success') {
            Log::info('OXXA: Nameservers updated', ['domain' => $domain]);

            return $this->successResponse(
                __('admin/modules.oxxa_nameservers_updated'),
                [
                    'domain' => $domain,
                    'nameservers' => $nameservers,
                    'updated_at' => now()->toISOString(),
                ]
            );
        }

        return $result;
    }

    /**
     * Get nameservers for a domain.
     *
     * Flow: domain_inf → nsgroup handle → nsgroup_get → collect NS* values
     * (ref: class.oxxa.php::getNameServers).
     */
    public function getNameservers(array $params)
    {
        $domain = $params['domain'] ?? '';
        if (empty($domain)) {
            return $this->errorResponse(__('admin/modules.oxxa_domain_required'));
        }

        $domainParts = $this->splitDomain($domain);

        $handleResult = $this->fetchNsGroupHandle($domainParts);
        if ($handleResult['status'] === 'error') {
            return $handleResult['error'];
        }

        $nameservers = [];
        if ($handleResult['status'] === 'success') {
            $group = $this->callApi('nsgroup_get', ['nsgroup' => $handleResult['handle']]);
            if ($group['status'] !== 'success') {
                return $group;
            }

            foreach (($group['data'] ?? []) as $key => $value) {
                if (stripos((string) $key, 'ns') !== false && ! empty($value) && is_string($value)) {
                    $nameservers[] = $value;
                }
            }
        }

        return $this->successResponse(
            __('admin/modules.oxxa_nameservers_retrieved'),
            [
                'domain' => $domain,
                'nameservers' => $nameservers,
            ]
        );
    }

    // -------------------------------------------------------------------------
    //  Child Nameservers (Glue Records) — unsupported by OXXA (NSGROUP model)
    // -------------------------------------------------------------------------

    public function getChildNameservers(array $params)
    {
        return $this->errorResponse(__('admin/modules.oxxa_child_ns_unsupported'));
    }

    public function addChildNameserver(array $params)
    {
        return $this->errorResponse(__('admin/modules.oxxa_child_ns_unsupported'));
    }

    public function updateChildNameserver(array $params)
    {
        return $this->errorResponse(__('admin/modules.oxxa_child_ns_unsupported'));
    }

    public function deleteChildNameserver(array $params)
    {
        return $this->errorResponse(__('admin/modules.oxxa_child_ns_unsupported'));
    }

    // -------------------------------------------------------------------------
    //  Privacy — unsupported by OXXA (no WHOIS-privacy command in reference)
    // -------------------------------------------------------------------------

    public function updatePrivacySettings(array $params)
    {
        return $this->errorResponse(__('admin/modules.oxxa_privacy_unsupported'));
    }

    // -------------------------------------------------------------------------
    //  Contacts (OXXA handle-based identity model)
    // -------------------------------------------------------------------------

    /**
     * Set contact details for a domain's four identity groups.
     *
     * Core passes a 4-group payload (`groups`/`contacts[$group]` + flat client_*).
     * OXXA update is per-handle: read the domain's identity-* handles from
     * domain_inf, then `idenity_upd` (sic) each handle in place with key `idenity`
     * (sic); on collision mint a new handle and rebind via domain_upd
     * (ref: class.oxxa.php::updateContactInfo, prepareContact).
     */
    public function setContactDetails(array $params)
    {
        $domain = $params['domain'] ?? '';
        if (empty($domain)) {
            return $this->errorResponse(__('admin/modules.oxxa_domain_required'));
        }

        $domainParts = $this->splitDomain($domain);

        $info = $this->callApi('domain_inf', [
            'SLD' => $domainParts['sld'],
            'TLD' => $domainParts['tld'],
        ]);
        if ($info['status'] !== 'success') {
            return $info;
        }

        // Read existing identity-* handles keyed by group (registrant/admin/tech/billing).
        $handles = [];
        foreach (($info['data'] ?? []) as $key => $value) {
            if (is_string($value) && strpos((string) $key, 'identity') === 0) {
                $group = str_replace('-', '', substr((string) $key, 8)); // "identity-admin" → "admin"
                $handles[$group] = $value;
            }
        }

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

        $used = [];
        $rebound = [];
        $needRebind = false;

        foreach ($handles as $group => $handle) {
            if (in_array($handle, $used, true)) {
                // Shared handle across groups → mint a fresh one for this group.
                try {
                    $newHandle = $this->mintIdentity($params, $group);
                } catch (\Exception $e) {
                    return $this->errorResponse($e->getMessage());
                }
                $rebound[$group] = $newHandle;
                $needRebind = true;
            } else {
                $payload = $this->prepareContact($params, $group);
                $payload['idenity'] = $handle; // sic — OXXA misspelling (ref: class.oxxa.php:287)
                $upd = $this->callApi('idenity_upd', $payload); // sic (ref: class.oxxa.php:288)
                if ($upd['status'] !== 'success') {
                    return $upd;
                }
                $used[$group] = $handle;
                $rebound[$group] = $handle;
            }
        }

        if ($needRebind) {
            $rebind = [
                'SLD' => $domainParts['sld'],
                'TLD' => $domainParts['tld'],
            ];
            foreach (['registrant', 'admin', 'tech', 'billing'] as $group) {
                if (isset($rebound[$group])) {
                    $rebind['IDENTITY-'.strtoupper($group)] = $rebound[$group];
                }
            }
            $domainUpd = $this->callApi('domain_upd', $rebind);
            if ($domainUpd['status'] !== 'success') {
                return $domainUpd;
            }
        }

        Log::info('OXXA: Contact details updated', ['domain' => $domain]);

        return $this->successResponse(
            __('admin/modules.oxxa_contacts_updated'),
            [
                'domain' => $domain,
                'updated_at' => now()->toISOString(),
            ]
        );
    }

    /**
     * Get contact details for a domain.
     *
     * Flow: domain_inf → identity-registrant/-admin/-tech/-billing handles →
     * identity_get(IDENTITY=handle) per handle → map OXXA identity fields
     * (ref: class.oxxa.php::getContactInfo).
     */
    public function getContactDetails(array $params)
    {
        $domain = $params['domain'] ?? '';
        if (empty($domain)) {
            return $this->errorResponse(__('admin/modules.oxxa_domain_required'));
        }

        $domainParts = $this->splitDomain($domain);

        $info = $this->callApi('domain_inf', [
            'SLD' => $domainParts['sld'],
            'TLD' => $domainParts['tld'],
        ]);
        if ($info['status'] !== 'success') {
            return $info;
        }

        // Handles arrive under keys prefixed "identity-*" (ref: substr($name, 8)).
        $handles = [];
        foreach (($info['data'] ?? []) as $key => $value) {
            if (is_string($value) && strpos((string) $key, 'identity') === 0) {
                $group = str_replace('-', '', substr((string) $key, 8));
                $handles[$group] = $value;
            }
        }

        $groups = [];
        $cache = [];
        foreach ($handles as $group => $handle) {
            if (! isset($cache[$handle])) {
                $identityResult = $this->callApi('identity_get', ['IDENTITY' => $handle]);
                $cache[$handle] = $identityResult['status'] === 'success'
                    ? ($identityResult['data'] ?? [])
                    : null;
            }

            $cdata = $cache[$handle];
            if ($cdata === null) {
                continue;
            }

            $groups[$group] = [
                'handle' => $handle,
                'firstname' => $this->flattenXml($cdata['firstname'] ?? ''),
                'lastname' => $this->flattenXml($cdata['lastname'] ?? ''),
                'company' => $this->flattenXml($cdata['company_name'] ?? ''),
                'address1' => $this->flattenXml($cdata['street'] ?? ''),
                'address2' => $this->flattenXml($cdata['number'] ?? ''),
                'city' => $this->flattenXml($cdata['city'] ?? ''),
                'state' => $this->flattenXml($cdata['state'] ?? ''),
                'postal_code' => $this->flattenXml($cdata['postalcode'] ?? ''),
                'country' => $this->flattenXml($cdata['country'] ?? ''),
                'phone' => $this->flattenXml($cdata['tel'] ?? ''),
                'email' => $this->flattenXml($cdata['email'] ?? ''),
            ];
        }

        return $this->successResponse(
            __('admin/modules.oxxa_contacts_retrieved'),
            ['domain' => $domain, 'groups' => $groups]
        );
    }

    /**
     * Flatten an OXXA field that may decode to a scalar or an empty [] node.
     */
    protected function flattenXml($value): string
    {
        if (is_array($value)) {
            return trim(implode(' ', array_map('strval', $value)));
        }

        return (string) $value;
    }

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

    /**
     * Get domain details (ref: class.oxxa.php uses domain_inf ->details).
     */
    public function getDomainDetails(array $params)
    {
        $domain = $params['domain'] ?? '';
        if (empty($domain)) {
            return $this->errorResponse(__('admin/modules.oxxa_domain_required'));
        }

        $domainParts = $this->splitDomain($domain);

        $result = $this->callApi('domain_inf', [
            'SLD' => $domainParts['sld'],
            'TLD' => $domainParts['tld'],
        ]);

        if ($result['status'] === 'success') {
            return $this->successResponse(
                __('admin/modules.oxxa_domain_details_retrieved'),
                array_merge(['domain' => $domain], $result['data'] ?? [])
            );
        }

        return $result;
    }

    /**
     * Sync domain expiry + status + nameservers from OXXA.
     *
     * domain_inf → expire_date (d-m-Y) → Y-m-d, Expired if past; nameservers via
     * nsgroup handle (ref: class.oxxa.php::synchInfo).
     */
    public function syncDomain(array $params)
    {
        $domain = $params['domain'] ?? '';
        if (empty($domain)) {
            return $this->errorResponse(__('admin/modules.oxxa_domain_required'));
        }

        $domainParts = $this->splitDomain($domain);

        $info = $this->callApi('domain_inf', [
            'SLD' => $domainParts['sld'],
            'TLD' => $domainParts['tld'],
        ]);
        if ($info['status'] !== 'success') {
            return $info;
        }

        $data = $info['data'] ?? [];
        $out = ['domain' => $domain];

        $expireRaw = $this->flattenXml($data['expire_date'] ?? '');
        if ($expireRaw !== '') {
            $expires = \DateTime::createFromFormat('d-m-Y', $expireRaw);
            if ($expires instanceof \DateTime) {
                $out['expiry_date'] = $expires->format('Y-m-d');
                $out['status'] = $expires < new \DateTime ? 'Expired' : 'Active';
            }
        }

        // Refresh nameservers from the bound NSGROUP.
        $handle = $data['nsgroup'] ?? null;
        if (! empty($handle) && is_string($handle)) {
            $group = $this->callApi('nsgroup_get', ['nsgroup' => $handle]);
            if ($group['status'] === 'success') {
                $ns = [];
                foreach (($group['data'] ?? []) as $key => $value) {
                    if (stripos((string) $key, 'ns') !== false && ! empty($value) && is_string($value)) {
                        $ns[] = $value;
                    }
                }
                $out['nameservers'] = $ns;
            }
        }

        return $this->successResponse(__('admin/modules.oxxa_sync_success'), $out);
    }

    /**
     * Sync a pending transfer/registration to completion.
     *
     * transfer_status (or register_status) → status == XMLOK means completed →
     * Active (ref: class.oxxa.php::synchInfo lines 135-143).
     */
    public function syncDomainTransfer(array $params)
    {
        $domain = $params['domain'] ?? '';
        if (empty($domain)) {
            return $this->errorResponse(__('admin/modules.oxxa_domain_required'));
        }

        $domainParts = $this->splitDomain($domain);

        // Registration-pending domains poll register_status; otherwise transfer_status.
        $status = strtolower((string) ($params['status'] ?? ''));
        $command = str_contains($status, 'registration') ? 'register_status' : 'transfer_status';

        $result = $this->callApi($command, [
            'SLD' => $domainParts['sld'],
            'TLD' => $domainParts['tld'],
        ]);

        // parseXmlResponse only returns success when the envelope token is XMLOK,
        // which is exactly the "transfer/registration completed" signal.
        if ($result['status'] === 'success') {
            Log::info('OXXA: Transfer/registration completed', ['domain' => $domain]);

            // Caller (SyncDomainTransfersCommand) matches lowercase 'active'/'completed'
            // via strict in_array — must not emit capital 'Active'.
            return $this->successResponse(
                __('admin/modules.oxxa_transfer_completed'),
                ['domain' => $domain, 'status' => 'active']
            );
        }

        // Still pending (or error) — surface as non-active without a fake success.
        return $this->successResponse(
            __('admin/modules.oxxa_transfer_pending'),
            ['domain' => $domain, 'status' => 'pending']
        );
    }

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

    /**
     * Get domain lock status (ref: class.oxxa.php::getRegistrarLock → lock == Y).
     */
    public function getDomainLock(array $params)
    {
        $domain = $params['domain'] ?? '';
        if (empty($domain)) {
            return $this->errorResponse(__('admin/modules.oxxa_domain_required'));
        }

        $domainParts = $this->splitDomain($domain);

        $result = $this->callApi('domain_inf', [
            'SLD' => $domainParts['sld'],
            'TLD' => $domainParts['tld'],
        ]);

        if ($result['status'] === 'success') {
            $data = $result['data'] ?? [];
            $lockStatus = $this->flattenXml($data['lock'] ?? '');
            $isLocked = strtoupper($lockStatus) === 'Y';

            return $this->successResponse(
                __('admin/modules.oxxa_lock_status_retrieved'),
                [
                    'domain' => $domain,
                    'locked' => $isLocked,
                    'lock_status' => $lockStatus,
                ]
            );
        }

        return $result;
    }

    /**
     * Update domain lock status (ref: class.oxxa.php::updateRegistrarLock →
     * lock command with LOCK=Y/N).
     */
    public function updateDomainLock(array $params)
    {
        $domain = $params['domain'] ?? '';
        if (empty($domain)) {
            return $this->errorResponse(__('admin/modules.oxxa_domain_required'));
        }

        $domainParts = $this->splitDomain($domain);
        $lock = ! empty($params['lock']);

        $result = $this->callApi('lock', [
            'SLD' => $domainParts['sld'],
            'TLD' => $domainParts['tld'],
            'LOCK' => $lock ? 'Y' : 'N',
        ]);

        if ($result['status'] === 'success') {
            Log::info('OXXA: Domain lock updated', ['domain' => $domain, 'locked' => $lock]);

            return $this->successResponse(
                $lock
                    ? __('admin/modules.oxxa_domain_locked')
                    : __('admin/modules.oxxa_domain_unlocked'),
                [
                    'domain' => $domain,
                    'locked' => $lock,
                    'updated_at' => now()->toISOString(),
                ]
            );
        }

        return $result;
    }

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

    /**
     * Get EPP/auth code via the dedicated domain_epp command; empty details means
     * the code was sent by email (ref: class.oxxa.php::getEppCode).
     */
    public function getEppCode(array $params)
    {
        $domain = $params['domain'] ?? '';
        if (empty($domain)) {
            return $this->errorResponse(__('admin/modules.oxxa_domain_required'));
        }

        $domainParts = $this->splitDomain($domain);

        $result = $this->callApi('domain_epp', [
            'SLD' => $domainParts['sld'],
            'TLD' => $domainParts['tld'],
        ]);

        if ($result['status'] === 'success') {
            $epp = $this->flattenXml($result['data']);

            return $this->successResponse(
                $epp !== ''
                    ? __('admin/modules.oxxa_epp_code_retrieved')
                    : __('admin/modules.oxxa_epp_sent_via_mail'),
                [
                    'domain' => $domain,
                    'epp_code' => $epp,
                    'sent_via_email' => $epp === '',
                ]
            );
        }

        return $result;
    }

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

    /**
     * Check domain availability via domain_check.
     *
     * OXXA signals availability ONLY through the numeric status_code, NOT via any
     * details field: "XMLOK 10" = taken, "XMLOK 11" = free
     * (ref: Cyberfusion StatusCode::STATUS_DOMAIN_TAKEN=10 / STATUS_DOMAIN_AVAILABLE=11,
     * DomainEndpoint::check → available = statusCode === STATUS_DOMAIN_AVAILABLE).
     * Both taken and free arrive under the XMLOK envelope, so parseXmlResponse
     * returns status=success for either; the numeric status_no disambiguates.
     */
    public function checkDomainAvailability(array $params)
    {
        $domain = $params['domain'] ?? '';
        if (empty($domain)) {
            return $this->errorResponse(__('admin/modules.oxxa_domain_required'));
        }

        $domainParts = $this->splitDomain($domain);

        $result = $this->callApi('domain_check', [
            'SLD' => $domainParts['sld'],
            'TLD' => $domainParts['tld'],
        ]);

        if ($result['status'] === 'success') {
            $statusNo = (int) ($result['status_no'] ?? 0);

            // 11 = available (free), 10 = taken. Fail closed: any other code that
            // is not an explicit "available" is treated as not available.
            $available = $statusNo === 11;

            return $this->successResponse(
                $available
                    ? __('admin/modules.oxxa_domain_available')
                    : __('admin/modules.oxxa_domain_not_available'),
                [
                    'domain' => $domain,
                    'available' => $available,
                    'raw_status' => (string) $statusNo,
                ]
            );
        }

        return $result;
    }

    /**
     * Domain suggestions — OXXA exposes no suggestion/spinning command.
     */
    public function checkDomainSuggestions(array $params)
    {
        return $this->errorResponse(__('admin/modules.oxxa_suggestions_unsupported'));
    }

    // -------------------------------------------------------------------------
    //  DNS Zone Management — unsupported by OXXA
    //
    //  OXXA exposes no DNS-record (zone) CRUD API. Nameservers are managed via the
    //  NSGROUP indirection (see setNameservers/getNameservers) and record-level
    //  security via DNSSEC only. The mature Cyberfusion OXXA client ships Domain,
    //  Dnssec, Glue, Identity, NameserverGroup, Product, Task and UserTld endpoints
    //  — and no DNS-record endpoint; HostBill's class.oxxa.php likewise has none.
    //  The previous dnszone_list/dnszone_rec_add|upd|del commands + field names were
    //  fabricated (present in no reference/API doc) and would always fail. Report
    //  honestly as unsupported instead of pretending success.
    // -------------------------------------------------------------------------

    public function getDNSRecords(array $params)
    {
        return $this->errorResponse(__('admin/modules.oxxa_dns_unsupported'));
    }

    public function addDNSRecord(array $params)
    {
        return $this->errorResponse(__('admin/modules.oxxa_dns_unsupported'));
    }

    public function updateDNSRecord(array $params)
    {
        return $this->errorResponse(__('admin/modules.oxxa_dns_unsupported'));
    }

    public function deleteDNSRecord(array $params)
    {
        return $this->errorResponse(__('admin/modules.oxxa_dns_unsupported'));
    }
}
