<?php

namespace App\Modules\Product\GogetSSL;

use App\Core\Product\AbstractProduct;
use Illuminate\Support\Facades\Cache;
use Illuminate\Support\Facades\Log;

class GogetSSL extends AbstractProduct
{
    /**
     * GoGetSSL API base URL
     */
    protected $apiUrl = 'https://my.gogetssl.com/api';

    /**
     * Cached authentication key
     */
    protected $authKey = null;

    /**
     * SSL modules do not require a server
     */
    public function requiresServer(): bool
    {
        return false;
    }

    /**
     * Create an SSL certificate order.
     *
     * Input contract (PRC-1021 CRITICAL-B). procp's `ModuleParamsBuilder` never
     * emits flat `csr`/`period`/GoGetSSL `product_id`/approver/contacts, and its
     * `product_id` is the LOCAL `Product.id` — NOT the GoGetSSL catalogue id.
     * So this module resolves each order input from the sources the builder does
     * emit, via the `sslOrderInputs()` helper:
     *   - GoGetSSL catalogue product id ← module-config `catalog_product_id`
     *     (admin field) or a `catalog_product_id` / `gogetssl_product_id`
     *     config-option — never the builder's `product_id`.
     *   - period (months) ← `period` config-option (or module-config default).
     *   - csr / dns_names / org_* ← custom fields (free-form textarea inputs).
     *   - approver_method / approver_email ← config-option / custom field.
     *   - admin and tech contacts ← client_* identity flattened by the builder.
     *
     * @param  array  $params  Provisioning params (WHMCS-esque, from ModuleParamsBuilder)
     * @return array Operation result
     */
    public function createAccount(array $params)
    {
        $in = $this->sslOrderInputs($params);

        // Hard requirements for any SSL order.
        foreach (['product_id', 'period', 'csr', 'domain'] as $param) {
            if (empty($in[$param])) {
                return [
                    'status' => 'error',
                    'message' => __('admin/modules.gogetssl_param_missing', ['param' => $param]),
                ];
            }
        }

        $postData = [
            'product_id' => $in['product_id'],
            'period' => $in['period'],
            'csr' => $in['csr'],
            'server_count' => $in['server_count'],
            'approver_method' => $in['approver_method'],
            'webserver_type' => $in['webserver_type'],
            'admin_firstname' => $in['admin_firstname'],
            'admin_lastname' => $in['admin_lastname'],
            'admin_email' => $in['admin_email'],
            'admin_phone' => $in['admin_phone'],
            'tech_firstname' => $in['tech_firstname'],
            'tech_lastname' => $in['tech_lastname'],
            'tech_email' => $in['tech_email'],
            'tech_phone' => $in['tech_phone'],
        ];

        // Organization fields for OV/EV certificates.
        if (! empty($in['org_name'])) {
            $postData['org_name'] = $in['org_name'];
            $postData['org_division'] = $in['org_division'];
            $postData['org_addressline1'] = $in['org_addressline1'];
            $postData['org_city'] = $in['org_city'];
            $postData['org_state'] = $in['org_state'];
            $postData['org_country'] = $in['org_country'];
            $postData['org_phone'] = $in['org_phone'];
            $postData['org_postalcode'] = $in['org_postalcode'];
        }

        // DCV approver email for email validation.
        if ($in['approver_method'] === 'email' && ! empty($in['approver_email'])) {
            $postData['approver_email'] = $in['approver_email'];
        }

        // SAN / wildcard additional domains.
        if (! empty($in['dns_names'])) {
            $postData['dns_names'] = $in['dns_names'];
        }

        // Thread module_config for credential resolution (PRC-1015).
        $postData['__params'] = $params;

        $response = $this->callApi('/orders/add_ssl_order/', 'POST', $postData);

        // add_ssl_order returns the order payload (order_id/order_status/invoice_id);
        // it does not reliably include a truthy `success` flag, so treat the
        // presence of an order_id as success (PRC-1021 A5).
        if (empty($response['error']) && ! empty($response['order_id'])) {
            return [
                'status' => 'success',
                'message' => __('admin/modules.gogetssl_order_created_success'),
                'data' => [
                    'order_id' => $response['order_id'] ?? null,
                    'invoice_id' => $response['invoice_id'] ?? null,
                    'order_status' => $response['order_status'] ?? null,
                ],
            ];
        }

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

    /**
     * Suspend (cancel/revoke) an SSL certificate order
     *
     * @param  array  $params  Parameters (order_id)
     * @return array Operation result
     */
    public function suspendAccount(array $params)
    {
        if (empty($params['order_id'])) {
            return [
                'status' => 'error',
                'message' => __('admin/modules.gogetssl_order_id_required'),
            ];
        }

        // GoGetSSL cancel takes order_id as a URL path segment; only the reason
        // travels in the POST body.
        $response = $this->callApi('/orders/cancel/' . (int) $params['order_id'], 'POST', [
            'reason' => $params['reason'] ?? 'Service suspended',
            '__params' => $params,
        ]);

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

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

    /**
     * Unsuspend (reissue) an SSL certificate
     *
     * @param  array  $params  Parameters (order_id)
     * @return array Operation result
     */
    public function unsuspendAccount(array $params)
    {
        if (empty($params['order_id'])) {
            return [
                'status' => 'error',
                'message' => __('admin/modules.gogetssl_order_id_required'),
            ];
        }

        $postData = [];

        if (! empty($params['csr'])) {
            $postData['csr'] = $params['csr'];
        }

        $postData['__params'] = $params;

        $response = $this->callApi('/orders/ssl/reissue/' . (int) $params['order_id'], 'POST', $postData);

        if (! empty($response['success'])) {
            return [
                'status' => 'success',
                'message' => __('admin/modules.gogetssl_order_reissued_success'),
                'data' => $response,
            ];
        }

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

    /**
     * Terminate (cancel) an SSL certificate order
     *
     * @param  array  $params  Parameters (order_id)
     * @return array Operation result
     */
    public function terminateAccount(array $params)
    {
        if (empty($params['order_id'])) {
            return [
                'status' => 'error',
                'message' => __('admin/modules.gogetssl_order_id_required'),
            ];
        }

        // GoGetSSL cancel takes order_id as a URL path segment; only the reason
        // travels in the POST body.
        $response = $this->callApi('/orders/cancel/' . (int) $params['order_id'], 'POST', [
            'reason' => $params['reason'] ?? 'Service terminated',
            '__params' => $params,
        ]);

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

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

    /**
     * Change account password - not applicable for SSL certificates
     *
     * @param  array  $params  Parameters
     * @return array Operation result
     */
    public function changeAccountPassword(array $params)
    {
        return [
            'status' => 'error',
            'message' => __('admin/modules.gogetssl_password_change_not_supported'),
        ];
    }

    /**
     * Test connection to GoGetSSL API
     *
     * @param  array  $params  Test parameters
     * @return array Test result
     */
    public function testConnection(array $params = [])
    {
        $response = $this->callApi('/account/balance/', 'GET', ['__params' => $params]);

        if (isset($response['balance'])) {
            return [
                'status' => 'success',
                'message' => __('admin/modules.gogetssl_connection_test_success', ['balance' => $response['balance']]),
                'data' => [
                    'balance' => $response['balance'],
                    'total_balance' => $response['total_balance'] ?? $response['balance'],
                ],
            ];
        }

        // Fallback: try /products endpoint
        $response = $this->callApi('/products/', 'GET', ['__params' => $params]);

        if (is_array($response) && ! isset($response['error'])) {
            return [
                'status' => 'success',
                'message' => __('admin/modules.gogetssl_connection_test_success_products'),
                'data' => $response,
            ];
        }

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

    /**
     * Admin panel configuration fields
     *
     * @return array Admin fields
     */
    public function adminFields()
    {
        return [
            'api_user' => [
                'name' => __('admin/modules.gogetssl_field_api_user'),
                'type' => 'text',
                'description' => __('admin/modules.gogetssl_field_api_user_description'),
                'required' => true,
            ],
            'api_password' => [
                'name' => __('admin/modules.gogetssl_field_api_password'),
                'type' => 'password',
                'description' => __('admin/modules.gogetssl_field_api_password_description'),
                'required' => true,
            ],
            'catalog_product_id' => [
                'name' => __('admin/modules.gogetssl_field_catalog_product_id'),
                'type' => 'text',
                'description' => __('admin/modules.gogetssl_field_catalog_product_id_description'),
                'required' => false,
            ],
            'sandbox' => [
                'name' => __('admin/modules.gogetssl_field_sandbox'),
                'type' => 'checkbox',
                'description' => __('admin/modules.gogetssl_field_sandbox_description'),
                'required' => false,
            ],
        ];
    }

    /**
     * Client panel fields
     *
     * @return array Client fields
     */
    public function clientFields()
    {
        return [
            'domain' => [
                'name' => __('admin/modules.gogetssl_field_domain'),
                'type' => 'text',
                'description' => __('admin/modules.gogetssl_field_domain_description'),
                'required' => true,
            ],
            'csr' => [
                'name' => __('admin/modules.gogetssl_field_csr'),
                'type' => 'textarea',
                'description' => __('admin/modules.gogetssl_field_csr_description'),
                'required' => true,
            ],
        ];
    }

    /**
     * Server fields - not applicable for SSL modules
     *
     * @return array Empty array
     */
    public function serverFields()
    {
        return [];
    }

    /**
     * Admin panel custom action buttons
     *
     * @return array Admin buttons
     */
    public function getAdminButtons(): array
    {
        return [
            [
                'action' => 'list_products',
                'label' => __('admin/modules.gogetssl_action_list_products'),
                'icon' => 'fas fa-list',
                'class' => 'btn-outline-info',
            ],
            [
                'action' => 'order_status',
                'label' => __('admin/modules.gogetssl_action_order_status'),
                'icon' => 'fas fa-info-circle',
                'class' => 'btn-outline-primary',
            ],
            [
                'action' => 'reissue',
                'label' => __('admin/modules.gogetssl_action_reissue'),
                'icon' => 'fas fa-redo',
                'class' => 'btn-outline-warning',
            ],
        ];
    }

    /**
     * Handle admin custom actions
     *
     * @param  string  $action  Action name (list_products, order_status, reissue, resend_approver)
     * @param  array  $params  Parameters
     * @return array Operation result
     */
    public function adminCustomAction(string $action, array $params): array
    {
        switch ($action) {
            case 'list_products':
                $response = $this->callApi('/products/', 'GET', ['__params' => $params]);

                if (is_array($response) && ! isset($response['error'])) {
                    $products = [];
                    foreach ($response as $product) {
                        if (is_array($product)) {
                            $products[] = [
                                'id' => $product['id'] ?? '',
                                'name' => $product['name'] ?? '',
                                'brand' => $product['brand'] ?? '',
                                'type' => $product['type'] ?? '',
                                'period' => $product['period'] ?? '',
                                'price' => $product['price'] ?? '',
                            ];
                        }
                    }

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

                return [
                    'status' => 'error',
                    'message' => $response['message'] ?? $response['description'] ?? __('admin/modules.gogetssl_list_products_failed'),
                ];

            case 'order_status':
                if (empty($params['order_id'])) {
                    return [
                        'status' => 'error',
                        'message' => __('admin/modules.gogetssl_order_id_required'),
                    ];
                }

                $response = $this->callApi('/orders/status/' . (int) $params['order_id'], 'GET', ['__params' => $params]);

                if (! empty($response['order_id'])) {
                    return [
                        'status' => 'success',
                        'message' => __('admin/modules.gogetssl_order_status_success'),
                        'data' => [
                            'order_id' => $response['order_id'],
                            'status' => $response['status'] ?? '',
                            'domain' => $response['domain'] ?? '',
                            'valid_from' => $response['valid_from'] ?? '',
                            'valid_till' => $response['valid_till'] ?? '',
                            'csr_code' => $response['csr_code'] ?? '',
                            'approver_method' => $response['approver_method'] ?? '',
                        ],
                    ];
                }

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

            case 'reissue':
                if (empty($params['order_id'])) {
                    return [
                        'status' => 'error',
                        'message' => __('admin/modules.gogetssl_order_id_required'),
                    ];
                }

                $postData = [];

                if (! empty($params['csr'])) {
                    $postData['csr'] = $params['csr'];
                }

                $postData['__params'] = $params;

                $response = $this->callApi('/orders/ssl/reissue/' . (int) $params['order_id'], 'POST', $postData);

                if (! empty($response['success'])) {
                    return [
                        'status' => 'success',
                        'message' => __('admin/modules.gogetssl_order_reissued_success'),
                        'data' => $response,
                    ];
                }

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

            default:
                return [
                    'status' => 'error',
                    'message' => __('admin/modules.gogetssl_action_not_supported', ['action' => $action]),
                ];
        }
    }

    /**
     * Client area actions for SSL certificates
     *
     * @return array Client actions
     */
    public function getClientActions(): array
    {
        return [
            'download_certificate' => [
                'label' => __('admin/modules.gogetssl_action_download_certificate'),
                'icon' => 'fas fa-download',
                'class' => 'btn-outline-success',
                'enabled' => true,
            ],
            'reissue' => [
                'label' => __('admin/modules.gogetssl_action_reissue'),
                'icon' => 'fas fa-redo',
                'class' => 'btn-outline-warning',
                'enabled' => true,
            ],
            'change_approver_method' => [
                'label' => __('admin/modules.gogetssl_action_change_approver_method'),
                'icon' => 'fas fa-exchange-alt',
                'class' => 'btn-outline-info',
                'enabled' => true,
            ],
        ];
    }

    /**
     * Handle client area actions
     *
     * @param  string  $action  Action name
     * @param  array  $params  Parameters
     * @return array Result
     */
    public function handleClientAction(string $action, array $params): array
    {
        switch ($action) {
            case 'download_certificate':
                if (empty($params['order_id'])) {
                    return [
                        'status' => 'error',
                        'message' => __('admin/modules.gogetssl_order_id_required'),
                    ];
                }

                $response = $this->callApi('/orders/status/' . (int) $params['order_id'], 'GET', ['__params' => $params]);

                if (! empty($response['crt_code'])) {
                    return [
                        'status' => 'success',
                        'message' => __('admin/modules.gogetssl_certificate_download_success'),
                        'data' => [
                            'certificate' => $response['crt_code'],
                            'ca_bundle' => $response['ca_code'] ?? '',
                            'domain' => $response['domain'] ?? '',
                            'valid_from' => $response['valid_from'] ?? '',
                            'valid_till' => $response['valid_till'] ?? '',
                        ],
                    ];
                }

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

            case 'reissue':
                if (empty($params['order_id'])) {
                    return [
                        'status' => 'error',
                        'message' => __('admin/modules.gogetssl_order_id_required'),
                    ];
                }

                $postData = [];

                if (! empty($params['csr'])) {
                    $postData['csr'] = $params['csr'];
                }

                $postData['__params'] = $params;

                $response = $this->callApi('/orders/ssl/reissue/' . (int) $params['order_id'], 'POST', $postData);

                if (! empty($response['success'])) {
                    return [
                        'status' => 'success',
                        'message' => __('admin/modules.gogetssl_order_reissued_success'),
                        'data' => $response,
                    ];
                }

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

            case 'change_approver_method':
                if (empty($params['order_id'])) {
                    return [
                        'status' => 'error',
                        'message' => __('admin/modules.gogetssl_order_id_required'),
                    ];
                }

                // GoGetSSL supports changing the DCV *email* address for an order
                // (`/orders/ssl/change_validation_email/{orderId}`). A new approver
                // email is required.
                if (empty($params['approver_email'])) {
                    return [
                        'status' => 'error',
                        'message' => __('admin/modules.gogetssl_approver_email_required'),
                    ];
                }

                $response = $this->callApi('/orders/ssl/change_validation_email/' . (int) $params['order_id'], 'POST', [
                    'approver_email' => $params['approver_email'],
                    '__params' => $params,
                ]);

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

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

            default:
                return [
                    'status' => 'error',
                    'message' => __('admin/modules.gogetssl_action_not_supported', ['action' => $action]),
                ];
        }
    }

    /**
     * Resolve the effective GoGetSSL API base URL (live vs sandbox).
     *
     * Sandbox is opted into via the admin `sandbox` module-config flag
     * (`https://sandbox.gogetssl.com/api`). Falls back to the live host.
     */
    protected function apiBaseUrl(array $params = []): string
    {
        $sandbox = $this->moduleConfig($params, 'sandbox', false);

        if ($sandbox === true || $sandbox === 1 || $sandbox === '1' || $sandbox === 'true') {
            return 'https://sandbox.gogetssl.com/api';
        }

        return $this->apiUrl;
    }

    /**
     * Read a custom field value by its (case-insensitive) field name.
     *
     * `ModuleParamsBuilder` emits custom fields as
     * `[{field_id, field_name, value}]` under `$params['custom_fields']`
     * (never as flat top-level keys), so free-form inputs like the CSR and
     * SAN list must be read from there — the config-option helper only covers
     * select-style options.
     *
     * @return mixed  The field value, or $default if absent/empty.
     */
    protected function customField(array $params, string $name, $default = null)
    {
        foreach (($params['custom_fields'] ?? []) as $field) {
            if (! is_array($field)) {
                continue;
            }
            if (isset($field['field_name']) && strcasecmp((string) $field['field_name'], $name) === 0) {
                $value = $field['value'] ?? null;

                return ($value !== null && $value !== '') ? $value : $default;
            }
        }

        return $default;
    }

    /**
     * First non-empty value across config-option, custom-field, then default.
     *
     * Admins may model an input either as a selectable configurable option or
     * as a free-form custom field; this checks both without the module caring
     * which surface was chosen.
     */
    protected function orderInput(array $params, string $name, $default = null)
    {
        $value = $this->configOption($params, $name, null);
        if ($value === null || $value === '') {
            $value = $this->customField($params, $name, null);
        }

        return ($value !== null && $value !== '') ? $value : $default;
    }

    /**
     * Resolve all SSL-order inputs from the builder-emitted surfaces
     * (config_options / custom_fields / client_* / module_config).
     *
     * Centralises PRC-1021 CRITICAL-B: nothing is read from a flat key the
     * builder never produces, and the GoGetSSL catalogue product id is sourced
     * from a DISTINCT key (module-config `catalog_product_id` or a
     * `catalog_product_id`/`gogetssl_product_id` config-option) — never the
     * builder's local `Product.id`.
     *
     * @return array<string,mixed>
     */
    protected function sslOrderInputs(array $params): array
    {
        // GoGetSSL catalogue product id — distinct from local Product.id.
        $catalogId = $this->moduleConfig($params, 'catalog_product_id', null);
        if ($catalogId === null || $catalogId === '') {
            $catalogId = $this->orderInput($params, 'catalog_product_id', null)
                ?? $this->orderInput($params, 'gogetssl_product_id', null);
        }

        $approverMethod = $this->orderInput($params, 'approver_method', 'email');

        return [
            'product_id' => $catalogId,
            'period' => $this->orderInput($params, 'period', $this->moduleConfig($params, 'default_period', null)),
            'csr' => $this->customField($params, 'csr', $this->customField($params, 'CSR', null)),
            'domain' => $params['domain'] ?? $this->customField($params, 'domain', null),
            'server_count' => (int) $this->orderInput($params, 'server_count', 1),
            'webserver_type' => $this->orderInput($params, 'webserver_type', 1),
            'approver_method' => $approverMethod,
            'approver_email' => $this->orderInput($params, 'approver_email', null),
            'dns_names' => $this->customField($params, 'dns_names', $this->customField($params, 'san', null)),

            // Contacts default from the client identity the builder flattens.
            'admin_firstname' => (string) ($params['client_first_name'] ?? $params['first_name'] ?? ''),
            'admin_lastname' => (string) ($params['client_last_name'] ?? $params['last_name'] ?? ''),
            'admin_email' => (string) ($params['client_email'] ?? $params['email'] ?? ''),
            'admin_phone' => (string) ($params['client_phone'] ?? ''),
            'tech_firstname' => (string) ($params['client_first_name'] ?? $params['first_name'] ?? ''),
            'tech_lastname' => (string) ($params['client_last_name'] ?? $params['last_name'] ?? ''),
            'tech_email' => (string) ($params['client_email'] ?? $params['email'] ?? ''),
            'tech_phone' => (string) ($params['client_phone'] ?? ''),

            // Organization (OV/EV) — from custom fields, defaulting to client data.
            'org_name' => $this->customField($params, 'org_name', $params['client_company'] ?? null),
            'org_division' => (string) $this->customField($params, 'org_division', ''),
            'org_addressline1' => (string) $this->customField($params, 'org_addressline1', $params['client_address1'] ?? ''),
            'org_city' => (string) $this->customField($params, 'org_city', $params['client_city'] ?? ''),
            'org_state' => (string) $this->customField($params, 'org_state', $params['client_state'] ?? ''),
            'org_country' => (string) $this->customField($params, 'org_country', $params['client_country_code'] ?? ''),
            'org_phone' => (string) $this->customField($params, 'org_phone', $params['client_phone'] ?? ''),
            'org_postalcode' => (string) $this->customField($params, 'org_postalcode', $params['client_postcode'] ?? ''),
        ];
    }

    /**
     * Resolve GoGetSSL API credentials via the PRC-1015 module-config helper.
     *
     * GoGetSSL is a serverless reseller module: there is no assigned server,
     * so credentials live in the product module config (`api_user`/`api_password`).
     * `moduleConfig()` reads `$params['module_config']` first, then `$this->config`.
     *
     * @return array{user:string,pass:string}
     */
    protected function apiCredentials(array $params = []): array
    {
        return [
            'user' => (string) $this->moduleConfig($params, 'api_user', ''),
            'pass' => (string) $this->moduleConfig($params, 'api_password', ''),
        ];
    }

    /**
     * Cache key for the auth token, derived from credentials (hashed — never logged).
     */
    protected function authCacheKey(array $params = []): string
    {
        $creds = $this->apiCredentials($params);

        return 'gogetssl_auth_' . md5($creds['user'] . $creds['pass']);
    }

    /**
     * Authenticate with GoGetSSL API and get auth key
     *
     * Caches the key for 60 minutes to avoid repeated auth calls.
     *
     * @param  array  $params  Provisioning params (module_config threaded per PRC-1015)
     * @return string|null Auth key or null on failure
     */
    protected function authenticate(array $params = []): ?string
    {
        if ($this->authKey) {
            return $this->authKey;
        }

        $cacheKey = $this->authCacheKey($params);

        $cachedKey = Cache::get($cacheKey);
        if ($cachedKey) {
            $this->authKey = $cachedKey;

            return $this->authKey;
        }

        $creds = $this->apiCredentials($params);

        if (empty($creds['user']) || empty($creds['pass'])) {
            Log::error('GoGetSSL: API credentials not configured');

            return null;
        }

        $ch = curl_init();
        curl_setopt($ch, CURLOPT_URL, $this->apiBaseUrl($params) . '/auth');
        curl_setopt($ch, CURLOPT_POST, 1);
        curl_setopt($ch, CURLOPT_POSTFIELDS, http_build_query([
            'user' => $creds['user'],
            'pass' => $creds['pass'],
        ]));
        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, 30);

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

        if ($curlError) {
            Log::error('GoGetSSL: cURL error during authentication', ['error' => $curlError]);

            return null;
        }

        $responseData = json_decode($response, true);

        if ($httpCode === 200 && ! empty($responseData['key'])) {
            $this->authKey = $responseData['key'];
            Cache::put($cacheKey, $this->authKey, now()->addMinutes(60));

            return $this->authKey;
        }

        Log::error('GoGetSSL: Authentication failed', [
            'http_code' => $httpCode,
            'response' => $responseData,
        ]);

        return null;
    }

    /**
     * Make an API call to GoGetSSL.
     *
     * GoGetSSL contract (official Wiki API / canonical `gogetssl-api` client):
     *   - The `auth_key` returned by `/auth/` is appended to the request URL as
     *     a `?auth_key=<token>` QUERY parameter on every subsequent call — it is
     *     NOT a POST body field (PRC-1021 CRITICAL-A).
     *   - Read resources (`/products/`, `/orders/status/{id}`, `/account/balance/`,
     *     `/webservers/`) are GET; order-mutating endpoints
     *     (`/orders/add_ssl_order/`, `/orders/cancel/{id}`, `/orders/ssl/reissue/{id}`)
     *     are POST. The HTTP verb is honored (previously every call was forced POST).
     *   - Path-param endpoints carry `{order_id}` as a URL path segment, not a body
     *     field — callers build that into `$endpoint`.
     *
     * Backward-compatible signature: `callApi($endpoint, $methodOrData, $data)`.
     * Legacy callers passed `callApi($endpoint, $arrayPayload)` — an array in the
     * second slot is still treated as the payload and defaults to POST. The
     * reserved `__params` key threads module_config for credential resolution
     * (PRC-1015) and is stripped before the request is sent.
     *
     * @param  string  $endpoint  API endpoint (e.g. /orders/add_ssl_order/)
     * @param  string|array  $method  HTTP verb ('GET'|'POST') or legacy payload array
     * @param  array  $data  Request payload
     * @return array API response
     */
    protected function callApi($endpoint, $method = 'GET', $data = [])
    {
        // Legacy shim: callApi($endpoint, $arrayPayload) → POST with that payload.
        if (is_array($method)) {
            $payload = $method;
            $verb = 'POST';
        } else {
            $payload = is_array($data) ? $data : [];
            $verb = strtoupper((string) $method) === 'POST' ? 'POST' : 'GET';
        }

        $ctx = [];
        if (isset($payload['__params']) && is_array($payload['__params'])) {
            $ctx = $payload['__params'];
            unset($payload['__params']);
        }

        $authKey = $this->authenticate($ctx);

        if (! $authKey) {
            return [
                'error' => true,
                'message' => __('admin/modules.gogetssl_auth_failed'),
            ];
        }

        $baseUrl = $this->apiBaseUrl($ctx);

        $responseData = $this->executeRequest($baseUrl, $endpoint, $verb, $payload, $authKey, $httpCode);

        if ($responseData === null) {
            return [
                'error' => true,
                'message' => __('admin/modules.gogetssl_invalid_response'),
            ];
        }

        if (isset($responseData['__curl_error'])) {
            return [
                'error' => true,
                'message' => __('admin/modules.gogetssl_api_call_failed', ['error' => $responseData['__curl_error']]),
            ];
        }

        // If the auth key expired, clear the cache and retry once with a fresh key.
        if ($httpCode === 401 || (isset($responseData['error']) && $responseData['error'] === 'auth_key_expired')) {
            Cache::forget($this->authCacheKey($ctx));
            $this->authKey = null;

            $authKey = $this->authenticate($ctx);
            if (! $authKey) {
                return [
                    'error' => true,
                    'message' => __('admin/modules.gogetssl_auth_failed'),
                ];
            }

            $responseData = $this->executeRequest($baseUrl, $endpoint, $verb, $payload, $authKey, $httpCode);

            if ($responseData === null) {
                return [
                    'error' => true,
                    'message' => __('admin/modules.gogetssl_invalid_response'),
                ];
            }

            if (isset($responseData['__curl_error'])) {
                return [
                    'error' => true,
                    'message' => __('admin/modules.gogetssl_api_call_failed', ['error' => $responseData['__curl_error']]),
                ];
            }
        }

        return $responseData;
    }

    /**
     * Execute a single GoGetSSL HTTP request with the auth_key in the query string.
     *
     * Returns the decoded array response, `['__curl_error' => msg]` on transport
     * failure, or `null` when the body was not valid JSON. `$httpCode` is set by
     * reference so the caller can detect 401 auth-expiry.
     *
     * @param  string  $baseUrl  Effective API base (live or sandbox)
     * @param  string  $endpoint  API path (path params already baked in)
     * @param  string  $verb  'GET' or 'POST'
     * @param  array  $payload  Body fields (GET → appended to query, POST → body)
     * @param  string  $authKey  Session auth token
     * @param  int|null  $httpCode  Out-param: HTTP status code
     */
    private function executeRequest(string $baseUrl, string $endpoint, string $verb, array $payload, string $authKey, ?int &$httpCode): ?array
    {
        // auth_key ALWAYS travels in the query string (GoGetSSL contract).
        $query = ['auth_key' => $authKey];

        // For GET reads, non-auth params also go in the query string.
        if ($verb === 'GET') {
            $query = array_merge($query, $payload);
        }

        $url = $baseUrl . $endpoint . '?' . http_build_query($query);

        $ch = curl_init();
        curl_setopt($ch, CURLOPT_URL, $url);

        if ($verb === 'POST') {
            curl_setopt($ch, CURLOPT_POST, 1);
            curl_setopt($ch, CURLOPT_POSTFIELDS, http_build_query($payload));
        } else {
            curl_setopt($ch, CURLOPT_HTTPGET, true);
        }

        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);

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

        if ($curlError) {
            Log::error('GoGetSSL: cURL error', [
                'endpoint' => $endpoint,
                'error' => $curlError,
            ]);

            return ['__curl_error' => $curlError];
        }

        $responseData = json_decode($response, true);

        if (! is_array($responseData)) {
            // PCI/PII: never log the raw response body (may contain cert/CSR/key material).
            Log::error('GoGetSSL: Invalid API response', [
                'endpoint' => $endpoint,
                'http_code' => $httpCode,
            ]);

            return null;
        }

        return $responseData;
    }

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

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

        return true;
    }
}
