<?php

namespace App\Modules\Product\Interworx;

use App\Core\Product\AbstractProduct;
use App\Core\Product\Events\AccountCreated;

class Interworx extends AbstractProduct
{
    /**
     * Default InterWorx API port
     */
    protected $apiPort = 2443;

    /**
     * InterWorx module requires a server
     */
    public function requiresServer(): bool
    {
        return true;
    }

    /**
     * Create a new SiteWorx (or Reseller) account on the InterWorx server
     *
     * @param  array  $params  Parameters (domain, username, password, email, plan)
     * @return array Operation result
     */
    public function createAccount(array $params)
    {
        $isReseller = $this->isReseller($params);

        // SiteWorx accounts are keyed by domain; reseller accounts are keyed
        // by e-mail (nickname), so domain is only strictly required for SiteWorx.
        $requiredParams = $isReseller
            ? ['password']
            : ['domain', 'username', 'password'];

        foreach ($requiredParams as $param) {
            if (! isset($params[$param]) || empty($params[$param])) {
                return [
                    'status' => 'error',
                    'message' => __('admin/modules.interworx_missing_param', ['param' => $param]),
                ];
            }
        }

        // E-mail comes from the builder flat field (never client_email); WHMCS
        // uses clientsdetails.email for both SiteWorx and reseller accounts.
        $email = $params['email'] ?? null;
        if (empty($email)) {
            return [
                'status' => 'error',
                'message' => __('admin/modules.interworx_missing_param', ['param' => 'email']),
            ];
        }

        // Package/plan template — config option (WHMCS "Package Name") first,
        // then builder flat plan/package, then module-config legacy fallbacks.
        $packageTemplate = $this->configOption($params, 'Package Name')
            ?? $params['plan']
            ?? $params['package']
            ?? $this->moduleConfig($params, 'package_name')
            ?? $this->moduleConfig($params, 'plan');

        if (empty($packageTemplate)) {
            return [
                'status' => 'error',
                'message' => __('admin/modules.interworx_package_required'),
            ];
        }

        // Choose a free IP. Dedicated-IP config option maps to
        // listDedicatedFreeIps (WHMCS interworx_CreateAccount:53).
        $ipAction = $this->configOption($params, 'Dedicated IP')
            ? 'listDedicatedFreeIps'
            : 'listFreeIps';

        $ipResponse = $this->callInterworxApi($params, '/nodeworx/siteworx', $ipAction);

        if ($ipResponse['status'] !== 0) {
            return [
                'status' => 'error',
                'message' => __('admin/modules.interworx_free_ip_failed'),
                'data' => $ipResponse,
            ];
        }

        $ipAddress = $ipResponse['payload'][0][0] ?? null;

        if (! $ipAddress) {
            return [
                'status' => 'error',
                'message' => __('admin/modules.interworx_no_free_ip'),
            ];
        }

        // Nickname = firstname+lastname (WHMCS :79), NOT the username.
        $nickname = $this->buildNickname($params);

        // Theme from the "Theme" config option (WHMCS configoption2).
        $theme = $this->configOption($params, 'Theme') ?? $this->moduleConfig($params, 'theme', '');

        if ($isReseller) {
            // Disk & BW Overselling config option (WHMCS configoption3).
            $overselling = $this->configOption($params, 'Disk & BW Overselling') ? '1' : '0';

            $controller = '/nodeworx/reseller';
            $apiParams = [
                'nickname' => $nickname,
                'email' => $email,
                'password' => $params['password'],
                'confirm_password' => $params['password'],
                'language' => $params['language'] ?? 'en-us',
                'theme' => $theme,
                'billing_day' => '1',
                'status' => 'active',
                'packagetemplate' => $packageTemplate,
                'RSL_OPT_OVERSELL_STORAGE' => $overselling,
                'RSL_OPT_OVERSELL_BANDWIDTH' => $overselling,
                'ips' => $ipAddress,
                'database_servers' => 'localhost',
            ];
        } else {
            $controller = '/nodeworx/siteworx';
            $apiParams = [
                'domainname' => $params['domain'],
                'ipaddress' => $ipAddress,
                'uniqname' => $params['username'],
                'nickname' => $nickname,
                'email' => $email,
                'password' => $params['password'],
                'confirm_password' => $params['password'],
                'language' => $params['language'] ?? 'en-us',
                'theme' => $theme,
                'packagetemplate' => $packageTemplate,
            ];
        }

        $response = $this->callInterworxApi($params, $controller, 'add', $apiParams);

        if ($response['status'] === 0) {
            $result = [
                'status' => 'success',
                'message' => __('admin/modules.interworx_account_created_success'),
                'data' => $response['payload'] ?? [],
            ];

            AccountCreated::dispatch($params, $result);

            return $result;
        }

        $result = [
            'status' => 'error',
            'message' => $response['payload'] ?? __('admin/modules.interworx_account_create_failed'),
            'data' => $response,
        ];

        AccountCreated::dispatch($params, $result);

        return $result;
    }

    /**
     * Suspend a SiteWorx account by setting status to inactive (0)
     *
     * @param  array  $params  Parameters (domain)
     * @return array Operation result
     */
    public function suspendAccount(array $params)
    {
        if ($this->isReseller($params)) {
            $resellerId = $this->resolveResellerId($params);
            if (! $resellerId) {
                return [
                    'status' => 'error',
                    'message' => __('admin/modules.interworx_reseller_not_found'),
                ];
            }

            $response = $this->callInterworxApi($params, '/nodeworx/reseller', 'edit', [
                'reseller_id' => $resellerId,
                'status' => 'inactive',
            ]);

            if ($response['status'] === 0) {
                return [
                    'status' => 'success',
                    'message' => __('admin/modules.interworx_account_suspended_success'),
                ];
            }

            return [
                'status' => 'error',
                'message' => $response['payload'] ?? __('admin/modules.interworx_account_suspend_failed'),
                'data' => $response,
            ];
        }

        if (! isset($params['domain']) || empty($params['domain'])) {
            return [
                'status' => 'error',
                'message' => __('admin/modules.interworx_domain_required'),
            ];
        }

        $apiParams = [
            'domain' => $params['domain'],
            'status' => '0',
        ];

        $response = $this->callInterworxApi($params, '/nodeworx/siteworx', 'edit', $apiParams);

        if ($response['status'] === 0) {
            return [
                'status' => 'success',
                'message' => __('admin/modules.interworx_account_suspended_success'),
            ];
        }

        return [
            'status' => 'error',
            'message' => $response['payload'] ?? __('admin/modules.interworx_account_suspend_failed'),
            'data' => $response,
        ];
    }

    /**
     * Unsuspend a SiteWorx account by setting status to active (1)
     *
     * @param  array  $params  Parameters (domain)
     * @return array Operation result
     */
    public function unsuspendAccount(array $params)
    {
        if ($this->isReseller($params)) {
            $resellerId = $this->resolveResellerId($params);
            if (! $resellerId) {
                return [
                    'status' => 'error',
                    'message' => __('admin/modules.interworx_reseller_not_found'),
                ];
            }

            $response = $this->callInterworxApi($params, '/nodeworx/reseller', 'edit', [
                'reseller_id' => $resellerId,
                'status' => 'active',
            ]);

            if ($response['status'] === 0) {
                return [
                    'status' => 'success',
                    'message' => __('admin/modules.interworx_account_unsuspended_success'),
                ];
            }

            return [
                'status' => 'error',
                'message' => $response['payload'] ?? __('admin/modules.interworx_account_unsuspend_failed'),
                'data' => $response,
            ];
        }

        if (! isset($params['domain']) || empty($params['domain'])) {
            return [
                'status' => 'error',
                'message' => __('admin/modules.interworx_domain_required'),
            ];
        }

        $apiParams = [
            'domain' => $params['domain'],
            'status' => '1',
        ];

        $response = $this->callInterworxApi($params, '/nodeworx/siteworx', 'edit', $apiParams);

        if ($response['status'] === 0) {
            return [
                'status' => 'success',
                'message' => __('admin/modules.interworx_account_unsuspended_success'),
            ];
        }

        return [
            'status' => 'error',
            'message' => $response['payload'] ?? __('admin/modules.interworx_account_unsuspend_failed'),
            'data' => $response,
        ];
    }

    /**
     * Terminate (delete) a SiteWorx account
     *
     * @param  array  $params  Parameters (domain)
     * @return array Operation result
     */
    public function terminateAccount(array $params)
    {
        if ($this->isReseller($params)) {
            $resellerId = $this->resolveResellerId($params);
            if (! $resellerId) {
                return [
                    'status' => 'error',
                    'message' => __('admin/modules.interworx_reseller_not_found'),
                ];
            }

            $response = $this->callInterworxApi($params, '/nodeworx/reseller', 'delete', [
                'reseller_id' => $resellerId,
            ]);

            if ($response['status'] === 0) {
                return [
                    'status' => 'success',
                    'message' => __('admin/modules.interworx_account_terminated_success'),
                ];
            }

            return [
                'status' => 'error',
                'message' => $response['payload'] ?? __('admin/modules.interworx_account_terminate_failed'),
                'data' => $response,
            ];
        }

        if (! isset($params['domain']) || empty($params['domain'])) {
            return [
                'status' => 'error',
                'message' => __('admin/modules.interworx_domain_required'),
            ];
        }

        $apiParams = [
            'domain' => $params['domain'],
            'confirm_action' => '1',
        ];

        $response = $this->callInterworxApi($params, '/nodeworx/siteworx', 'delete', $apiParams);

        if ($response['status'] === 0) {
            return [
                'status' => 'success',
                'message' => __('admin/modules.interworx_account_terminated_success'),
            ];
        }

        return [
            'status' => 'error',
            'message' => $response['payload'] ?? __('admin/modules.interworx_account_terminate_failed'),
            'data' => $response,
        ];
    }

    /**
     * Change the password for a SiteWorx account
     *
     * @param  array  $params  Parameters (domain, password)
     * @return array Operation result
     */
    public function changeAccountPassword(array $params)
    {
        if (! isset($params['domain']) || empty($params['domain'])) {
            return [
                'status' => 'error',
                'message' => __('admin/modules.interworx_domain_required'),
            ];
        }

        if (! isset($params['password']) || empty($params['password'])) {
            return [
                'status' => 'error',
                'message' => __('admin/modules.interworx_password_required'),
            ];
        }

        if ($this->isReseller($params)) {
            $resellerId = $this->resolveResellerId($params);
            if (! $resellerId) {
                return [
                    'status' => 'error',
                    'message' => __('admin/modules.interworx_reseller_not_found'),
                ];
            }

            $response = $this->callInterworxApi($params, '/nodeworx/reseller', 'edit', [
                'reseller_id' => $resellerId,
                'password' => $params['password'],
                'confirm_password' => $params['password'],
            ]);

            if ($response['status'] === 0) {
                return [
                    'status' => 'success',
                    'message' => __('admin/modules.interworx_password_changed_success'),
                ];
            }

            return [
                'status' => 'error',
                'message' => $response['payload'] ?? __('admin/modules.interworx_password_change_failed'),
                'data' => $response,
            ];
        }

        $apiParams = [
            'domain' => $params['domain'],
            'password' => $params['password'],
            'confirm_password' => $params['password'],
        ];

        $response = $this->callInterworxApi($params, '/nodeworx/siteworx', 'edit', $apiParams);

        if ($response['status'] === 0) {
            return [
                'status' => 'success',
                'message' => __('admin/modules.interworx_password_changed_success'),
            ];
        }

        return [
            'status' => 'error',
            'message' => $response['payload'] ?? __('admin/modules.interworx_password_change_failed'),
            'data' => $response,
        ];
    }

    /**
     * Change the package template for a SiteWorx account
     *
     * @param  array  $params  Parameters (domain, plan)
     * @return array Operation result
     */
    public function changeAccountPackage(array $params)
    {
        if (! isset($params['domain']) || empty($params['domain'])) {
            return [
                'status' => 'error',
                'message' => __('admin/modules.interworx_domain_required'),
            ];
        }

        $packageTemplate = $this->configOption($params, 'Package Name')
            ?? $params['plan']
            ?? $params['package']
            ?? $this->moduleConfig($params, 'package_name')
            ?? $this->moduleConfig($params, 'plan');

        if (empty($packageTemplate)) {
            return [
                'status' => 'error',
                'message' => __('admin/modules.interworx_package_required'),
            ];
        }

        if ($this->isReseller($params)) {
            $resellerId = $this->resolveResellerId($params);
            if (! $resellerId) {
                return [
                    'status' => 'error',
                    'message' => __('admin/modules.interworx_reseller_not_found'),
                ];
            }

            $overselling = $this->configOption($params, 'Disk & BW Overselling') ? '1' : '0';

            $response = $this->callInterworxApi($params, '/nodeworx/reseller', 'edit', [
                'reseller_id' => $resellerId,
                'package_template' => $packageTemplate,
                'RSL_OPT_OVERSELL_STORAGE' => $overselling,
                'RSL_OPT_OVERSELL_BANDWIDTH' => $overselling,
            ]);

            if ($response['status'] === 0) {
                return [
                    'status' => 'success',
                    'message' => __('admin/modules.interworx_package_changed_success'),
                ];
            }

            return [
                'status' => 'error',
                'message' => $response['payload'] ?? __('admin/modules.interworx_package_change_failed'),
                'data' => $response,
            ];
        }

        $apiParams = [
            'domain' => $params['domain'],
            'package_template' => $packageTemplate,
        ];

        $response = $this->callInterworxApi($params, '/nodeworx/siteworx', 'edit', $apiParams);

        if ($response['status'] === 0) {
            return [
                'status' => 'success',
                'message' => __('admin/modules.interworx_package_changed_success'),
            ];
        }

        return [
            'status' => 'error',
            'message' => $response['payload'] ?? __('admin/modules.interworx_package_change_failed'),
            'data' => $response,
        ];
    }

    /**
     * Test the connection to the InterWorx server
     *
     * Uses listBandwidthAndStorageInMB to verify connectivity and authentication
     *
     * @param  array  $params  Test parameters
     * @return array Test result
     */
    public function testConnection(array $params = [])
    {
        $response = $this->callInterworxApi($params, '/nodeworx/siteworx', 'listBandwidthAndStorageInMB');

        if ($response['status'] === 0) {
            $accountCount = is_array($response['payload']) ? count($response['payload']) : 0;

            return [
                'status' => 'success',
                'message' => __('admin/modules.interworx_connection_test_success', ['count' => $accountCount]),
                'data' => [
                    'account_count' => $accountCount,
                ],
            ];
        }

        return [
            'status' => 'error',
            'message' => __('admin/modules.interworx_connection_test_failed'),
            'data' => $response,
        ];
    }

    /**
     * Create a Single Sign-On login URL for the SiteWorx panel
     *
     * InterWorx uses form-based login, so we build a login URL with credentials
     *
     * @param  array  $params  Parameters (domain, email, password)
     * @return array Operation result with login URL
     */
    public function createSSOLogin(array $params)
    {
        $server = $this->serverConfig($params);
        $protocol = $server['secure'] ? 'https' : 'http';
        $hostname = $server['hostname'] !== '' ? $server['hostname'] : $server['ip'];
        $port = ! empty($server['port']) ? $server['port'] : $this->apiPort;

        // Reseller accounts log into NodeWorx with username+password (WHMCS
        // interworx_ClientArea reseller branch), no domain field.
        if ($this->isReseller($params)) {
            $loginUrl = "{$protocol}://{$hostname}:{$port}/nodeworx/";

            if (! empty($params['password'])) {
                return [
                    'status' => 'success',
                    'message' => __('admin/modules.interworx_sso_created_success'),
                    'url' => $loginUrl,
                    'data' => [
                        'login_url' => "{$protocol}://{$hostname}:{$port}/nodeworx/index.php?action=login",
                        'method' => 'POST',
                        'fields' => [
                            'email' => $params['email'] ?? '',
                            'password' => $params['password'],
                        ],
                    ],
                ];
            }

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

        if (! isset($params['domain']) || empty($params['domain'])) {
            return [
                'status' => 'error',
                'message' => __('admin/modules.interworx_domain_required'),
            ];
        }

        // SiteWorx login URL
        $loginUrl = "{$protocol}://{$hostname}:{$port}/siteworx/";

        // SiteWorx form-login POSTs email+password+domain (WHMCS ClientArea).
        // E-mail comes from the client (builder flat email), never a raw creds field.
        if (! empty($params['email']) && ! empty($params['password'])) {
            return [
                'status' => 'success',
                'message' => __('admin/modules.interworx_sso_created_success'),
                'url' => $loginUrl,
                'data' => [
                    'login_url' => "{$protocol}://{$hostname}:{$port}/siteworx/index.php?action=login",
                    'method' => 'POST',
                    'fields' => [
                        'email' => $params['email'],
                        'password' => $params['password'],
                        'domain' => $params['domain'],
                    ],
                ],
            ];
        }

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

    /**
     * Get resource usage (disk and bandwidth) for a specific domain
     *
     * Uses the listBandwidthAndStorageInMB API action to retrieve usage data
     *
     * @param  array  $params  Parameters (domain)
     * @return array|null Resource usage data or null on failure
     */
    public function getResourceUsage(array $params): ?array
    {
        $response = $this->callInterworxApi($params, '/nodeworx/siteworx', 'listBandwidthAndStorageInMB');

        if ($response['status'] !== 0 || ! is_array($response['payload'])) {
            return null;
        }

        $domain = $params['domain'] ?? null;
        $domainData = null;

        // Find the matching domain in the payload
        foreach ($response['payload'] as $data) {
            if (isset($data['domain']) && $data['domain'] === $domain) {
                $domainData = $data;
                break;
            }
        }

        if (! $domainData) {
            return null;
        }

        $resources = [];

        // Disk / Storage usage
        $storageUsed = (float) ($domainData['storage_used'] ?? 0);
        $storageLimit = (float) ($domainData['storage'] ?? 0);
        $storagePercentage = $storageLimit > 0 ? round(($storageUsed / $storageLimit) * 100, 2) : 0;

        $resources[] = [
            'name' => __('admin/modules.interworx_resource_disk_space'),
            'used' => $storageUsed,
            'limit' => $storageLimit,
            'unit' => 'MB',
            'percentage' => $storagePercentage,
        ];

        // Bandwidth usage
        $bandwidthUsed = (float) ($domainData['bandwidth_used'] ?? 0);
        $bandwidthLimit = (float) ($domainData['bandwidth'] ?? 0);
        $bandwidthPercentage = $bandwidthLimit > 0 ? round(($bandwidthUsed / $bandwidthLimit) * 100, 2) : 0;

        $resources[] = [
            'name' => __('admin/modules.interworx_resource_bandwidth'),
            'used' => $bandwidthUsed,
            'limit' => $bandwidthLimit,
            'unit' => 'MB',
            'percentage' => $bandwidthPercentage,
        ];

        return $resources;
    }

    /**
     * Return admin panel configuration fields
     *
     * @return array Admin fields definition
     */
    public function adminFields()
    {
        return [
            'hostname' => [
                'name' => __('admin/modules.interworx_field_hostname'),
                'type' => 'text',
                'description' => __('admin/modules.interworx_field_hostname_description'),
                'required' => true,
            ],
            'port' => [
                'name' => __('admin/modules.interworx_field_port'),
                'type' => 'text',
                'description' => __('admin/modules.interworx_field_port_description'),
                'default' => '2443',
                'required' => false,
            ],
            'username' => [
                'name' => __('admin/modules.interworx_field_username'),
                'type' => 'text',
                'description' => __('admin/modules.interworx_field_username_description'),
                'required' => false,
            ],
            'password' => [
                'name' => __('admin/modules.interworx_field_password'),
                'type' => 'password',
                'description' => __('admin/modules.interworx_field_password_description'),
                'required' => false,
            ],
            'api_key' => [
                'name' => __('admin/modules.interworx_field_api_key'),
                'type' => 'password',
                'description' => __('admin/modules.interworx_field_api_key_description'),
                'required' => false,
            ],
            'secure' => [
                'name' => __('admin/modules.interworx_field_secure'),
                'type' => 'checkbox',
                'description' => __('admin/modules.interworx_field_secure_description'),
                'default' => true,
            ],
        ];
    }

    /**
     * Return client-facing fields for service details
     *
     * @return array Client fields definition
     */
    public function clientFields()
    {
        return [
            'domain' => [
                'name' => __('admin/modules.interworx_field_domain'),
                'type' => 'text',
                'description' => __('admin/modules.interworx_field_domain_description'),
                'required' => true,
            ],
            'username' => [
                'name' => __('admin/modules.interworx_field_client_username'),
                'type' => 'text',
                'description' => __('admin/modules.interworx_field_client_username_description'),
                'required' => true,
            ],
            'password' => [
                'name' => __('admin/modules.interworx_field_client_password'),
                'type' => 'password',
                'description' => __('admin/modules.interworx_field_client_password_description'),
                'required' => true,
            ],
            'email' => [
                'name' => __('admin/modules.interworx_field_client_email'),
                'type' => 'text',
                'description' => __('admin/modules.interworx_field_client_email_description'),
                'required' => true,
            ],
        ];
    }

    /**
     * Return server management fields
     *
     * @return array Server fields definition
     */
    public function serverFields()
    {
        return [
            'api_key' => [
                'name' => __('admin/modules.interworx_field_api_key'),
                'type' => 'password',
                'description' => __('admin/modules.interworx_field_api_key_server_description'),
                'required' => false,
            ],
        ];
    }

    /**
     * Validate the module configuration
     *
     * @return bool True if configuration is valid
     *
     * @throws \Exception When configuration is invalid
     */
    public function validateConfig()
    {
        if (empty($this->config['hostname'])) {
            throw new \Exception(__('admin/modules.interworx_config_hostname_missing'));
        }

        // Must have either API key or username+password
        $hasApiKey = ! empty($this->config['api_key']);
        $hasCredentials = ! empty($this->config['username']) && ! empty($this->config['password']);

        if (! $hasApiKey && ! $hasCredentials) {
            throw new \Exception(__('admin/modules.interworx_config_auth_missing'));
        }

        return true;
    }

    /**
     * Core API method: call the InterWorx NodeWorx/SiteWorx Remote API.
     *
     * InterWorx exposes a SOAP RPC endpoint (`/nodeworx/soap?wsdl`) and every
     * command is dispatched through a single generic entry point:
     *
     *     $client->route($key, $controller, $action, $input)
     *
     * where `$key` is the NodeWorx API access key (serveraccesshash). This is
     * the ONLY supported transport — a raw form-encoded POST to the UI
     * controllers (e.g. `/nodeworx/siteworx`) neither authenticates nor
     * dispatches and never returns the `{status,payload}` envelope, so the
     * previous curl implementation could not provision at all.
     *
     * Host/creds are resolved via serverConfig() (assigned server takes
     * priority over module_config); the access key comes from module_config
     * (api_key) with a serverConfig password fallback for serverless setups.
     * No secrets / raw responses are logged.
     *
     * @param  array  $params  Full module params (server/module_config context)
     * @param  string  $controller  The API controller path (e.g., /nodeworx/siteworx)
     * @param  string  $action  The API action (e.g., add, edit, delete)
     * @param  array  $apiParams  Input array for the API call
     * @return array API response with 'status' (0 = success) and 'payload' keys
     */
    protected function callInterworxApi(array $params, string $controller, string $action, array $apiParams = []): array
    {
        $server = $this->serverConfig($params);

        $protocol = $server['secure'] ? 'https' : 'http';
        $hostname = $server['hostname'] !== '' ? $server['hostname'] : $server['ip'];
        $port = ! empty($server['port']) ? $server['port'] : $this->apiPort;

        if ($hostname === '') {
            return [
                'status' => 1,
                'payload' => __('admin/modules.interworx_config_hostname_missing'),
            ];
        }

        // NodeWorx API access key (serveraccesshash). Prefer module_config
        // api_key; serverConfig password slot carries it for serverless setups.
        $key = $this->moduleConfig($params, 'api_key');
        if (empty($key)) {
            $key = $server['password'];
        }

        if (empty($key)) {
            return [
                'status' => 1,
                'payload' => __('admin/modules.interworx_config_auth_missing'),
            ];
        }

        // When connecting by IP the panel presents a self-signed certificate
        // for its hostname, so peer verification can only be honored when a
        // real hostname is configured (mirrors WHMCS interworx__getSoapClient).
        $verify = $server['hostname'] !== '';
        $wsdl = "{$protocol}://{$hostname}:{$port}/nodeworx/soap?wsdl";

        try {
            $soapOptions = [
                'connection_timeout' => 30,
                'exceptions' => true,
            ];

            if (! $verify) {
                $soapOptions['stream_context'] = stream_context_create([
                    'ssl' => [
                        'verify_peer' => false,
                        'verify_peer_name' => false,
                    ],
                ]);
            }

            $client = $this->makeSoapClient($wsdl, $soapOptions);

            $result = $client->route($key, $controller, $action, $apiParams);
        } catch (\Throwable $e) {
            // Never surface exception text (may contain host/key details).
            return [
                'status' => 1,
                'payload' => __('admin/modules.interworx_invalid_response'),
            ];
        }

        // SOAP returns an object or array; normalize to array.
        if (is_object($result)) {
            $result = (array) $result;
        }

        if (! is_array($result) || ! array_key_exists('status', $result)) {
            return [
                'status' => 1,
                'payload' => __('admin/modules.interworx_invalid_response'),
            ];
        }

        // InterWorx returns 'status' => 0 on success.
        return [
            'status' => $result['status'],
            'payload' => $result['payload'] ?? null,
        ];
    }

    /**
     * Instantiate the SOAP client. Extracted so tests can override transport.
     */
    protected function makeSoapClient(string $wsdl, array $options): \SoapClient
    {
        return new \SoapClient($wsdl, $options);
    }

    /**
     * Whether this service is provisioned as a NodeWorx reseller account
     * rather than a SiteWorx account (WHMCS `type == reselleraccount`).
     */
    protected function isReseller(array $params): bool
    {
        $type = $params['type']
            ?? $this->configOption($params, 'Account Type')
            ?? $this->moduleConfig($params, 'account_type');

        return is_string($type) && strtolower($type) === 'reselleraccount';
    }

    /**
     * Build the InterWorx nickname from the client name (firstname+lastname),
     * mirroring WHMCS. Falls back to the username when no name is available.
     */
    protected function buildNickname(array $params): string
    {
        $first = trim((string) ($params['first_name'] ?? ''));
        $last = trim((string) ($params['last_name'] ?? ''));
        $nickname = strtolower($first . $last);

        if ($nickname === '') {
            $nickname = strtolower((string) ($params['username'] ?? $params['email'] ?? ''));
        }

        return $nickname;
    }

    /**
     * Resolve the NodeWorx reseller id for the account's e-mail address.
     *
     * Reseller lifecycle actions key on `reseller_id`, but procp only knows the
     * client e-mail — so we list resellers (`/nodeworx/reseller:listIds`) and
     * map email → id, exactly as WHMCS interworx_GetResellers does.
     */
    protected function resolveResellerId(array $params): ?string
    {
        $email = $params['email'] ?? null;
        if (empty($email)) {
            return null;
        }

        $resellers = $this->getResellers($params);

        return $resellers[$email] ?? null;
    }

    /**
     * Return [email => reseller_id] map from the NodeWorx reseller list.
     *
     * The payload rows are `[id, "Name (email)"]`; we extract the email inside
     * the parentheses (WHMCS interworx_GetResellers).
     */
    protected function getResellers(array $params): array
    {
        $response = $this->callInterworxApi($params, '/nodeworx/reseller', 'listIds');

        if ($response['status'] !== 0 || ! is_array($response['payload'])) {
            return [];
        }

        $resellers = [];

        foreach ($response['payload'] as $row) {
            if (! is_array($row) || count($row) < 2) {
                continue;
            }

            [$resellerId, $label] = $row;

            // Label format: "First Last (email@example.com)".
            if (preg_match('/\(([^)]+)\)\s*$/', (string) $label, $m)) {
                $resellers[trim($m[1])] = $resellerId;
            }
        }

        return $resellers;
    }

    /**
     * Build a NodeWorx admin panel login form (WHMCS interworx_AdminLink) —
     * a self-submitting POST to `/nodeworx/`, opened in a new tab.
     *
     * @return array{status:string,url:string,data:array}
     */
    public function adminLink(array $params): array
    {
        $server = $this->serverConfig($params);
        $protocol = $server['secure'] ? 'https' : 'http';
        $hostname = $server['hostname'] !== '' ? $server['hostname'] : $server['ip'];
        $port = ! empty($server['port']) ? $server['port'] : $this->apiPort;

        return [
            'status' => 'success',
            'url' => "{$protocol}://{$hostname}:{$port}/nodeworx/",
            'data' => [
                'login_url' => "{$protocol}://{$hostname}:{$port}/nodeworx/index.php?action=login",
                'method' => 'POST',
                'target' => '_blank',
            ],
        ];
    }
}
