<?php

declare(strict_types=1);

use WHMCS\Database\Capsule;

if (!defined('WHMCS')) {
    die('This file cannot be accessed directly');
}

require_once __DIR__ . '/lib/ApiClient.php';
require_once __DIR__ . '/lib/ModuleStorage.php';
require_once __DIR__ . '/lib/EventPoller.php';
require_once __DIR__ . '/lib/ProductMappingStorage.php';
require_once __DIR__ . '/../../../lib/MtlsConfig.php';

const HEXAGAMECLOUD_ADDON_VERSION = '1.1.0';

function hexagamecloud_config(): array
{
    return [
        'name' => 'HexaHost GameCloud',
        'description' => 'Global GameCloud integration — dashboard, reconciliation, and event polling.',
        'version' => HEXAGAMECLOUD_ADDON_VERSION,
        'author' => 'HexaHost',
        'language' => 'english',
        'fields' => [
            'api_url' => [
                'FriendlyName' => 'GameCloud API URL',
                'Type' => 'text',
                'Size' => '128',
                'Default' => 'https://api.example.net',
                'Description' => 'Base URL of the GameCloud API (no trailing slash)',
            ],
            'integration_id' => [
                'FriendlyName' => 'Integration ID',
                'Type' => 'text',
                'Size' => '64',
                'Description' => 'Matches WHMCS_INTEGRATION_ID on GameCloud',
            ],
            'api_secret' => [
                'FriendlyName' => 'API Secret',
                'Type' => 'password',
                'Size' => '128',
                'Description' => 'HMAC signing secret — stored encrypted by WHMCS',
            ],
            'sso_origin' => [
                'FriendlyName' => 'SSO Panel Origin',
                'Type' => 'text',
                'Size' => '128',
                'Default' => 'https://panel.example.net',
                'Description' => 'Customer panel URL for SSO redirects',
            ],
            'event_poll_limit' => [
                'FriendlyName' => 'Event poll batch size',
                'Type' => 'text',
                'Size' => '4',
                'Default' => '50',
            ],
            'auto_reconcile' => [
                'FriendlyName' => 'Auto-reconcile on daily cron',
                'Type' => 'yesno',
                'Description' => 'Run dry reconciliation during daily event poll',
            ],
            'use_mtls' => [
                'FriendlyName' => 'Use mTLS client certificate',
                'Type' => 'yesno',
                'Description' => 'Requires INTEGRATION_MTLS_ENABLED=true on GameCloud',
            ],
            'mtls_cert_path' => [
                'FriendlyName' => 'mTLS client certificate path',
                'Type' => 'text',
                'Size' => '256',
            ],
            'mtls_key_path' => [
                'FriendlyName' => 'mTLS client private key path',
                'Type' => 'text',
                'Size' => '256',
            ],
            'mtls_ca_path' => [
                'FriendlyName' => 'mTLS CA bundle path',
                'Type' => 'text',
                'Size' => '256',
            ],
            'mtls_cert_fingerprint' => [
                'FriendlyName' => 'Client certificate SHA-256 fingerprint',
                'Type' => 'text',
                'Size' => '128',
                'Description' => 'openssl x509 -in client.crt -outform DER | openssl dgst -sha256',
            ],
        ],
    ];
}

function hexagamecloud_activate(): array
{
    try {
        $sql = file_get_contents(__DIR__ . '/sql/install.sql');
        if ($sql === false) {
            throw new RuntimeException('Unable to read install.sql');
        }

        foreach (array_filter(array_map('trim', explode(';', $sql))) as $statement) {
            if ($statement !== '') {
                Capsule::connection()->statement($statement);
            }
        }

        return [
            'status' => 'success',
            'description' => 'HexaHost GameCloud addon activated. Copy hooks/hexagamecloud.php to includes/hooks/ for cron polling.',
        ];
    } catch (Throwable $exception) {
        return [
            'status' => 'error',
            'description' => 'Activation failed: ' . $exception->getMessage(),
        ];
    }
}

function hexagamecloud_deactivate(): array
{
    return [
        'status' => 'success',
        'description' => 'Addon deactivated. Module tables were kept for audit history.',
    ];
}

function hexagamecloud_upgrade(array $vars): void
{
    if (!function_exists('hexagamecloud_getModuleVersion')) {
        return;
    }

    $current = hexagamecloud_getModuleVersion();
    if (version_compare($current, '1.1.0', '<')) {
        $upgrade = file_get_contents(__DIR__ . '/sql/upgrade-1.1.0.sql');
        if ($upgrade !== false) {
            foreach (array_filter(array_map('trim', explode(';', $upgrade))) as $statement) {
                if ($statement !== '') {
                    Capsule::connection()->statement($statement);
                }
            }
        }
    }
}

function hexagamecloud_output(array $vars): void
{
    $action = $_GET['action'] ?? 'dashboard';
    $modulelink = $vars['modulelink'];
    $storage = new HexaGameCloudModuleStorage();

    try {
        $client = hexagamecloud_create_client($vars);
    } catch (Throwable $exception) {
        hexagamecloud_render_error($vars, $exception->getMessage());
        return;
    }

    switch ($action) {
        case 'events':
            hexagamecloud_page_events($vars, $client, $storage, $modulelink);
            break;
        case 'reconciliation':
            hexagamecloud_page_reconciliation($vars, $client, $storage, $modulelink);
            break;
        case 'poll':
            hexagamecloud_action_poll($vars, $client, $storage, $modulelink);
            break;
        case 'reconcile':
            hexagamecloud_action_reconcile($vars, $client, $storage, $modulelink);
            break;
        case 'ack':
            hexagamecloud_action_ack($vars, $client, $storage, $modulelink);
            break;
        case 'mappings':
            hexagamecloud_page_mappings($vars, $client, $modulelink);
            break;
        case 'save_mapping':
            hexagamecloud_action_save_mapping($vars, $client, $modulelink);
            break;
        case 'delete_mapping':
            hexagamecloud_action_delete_mapping($vars, $modulelink);
            break;
        case 'mtls':
            hexagamecloud_page_mtls($vars, $client, $modulelink);
            break;
        case 'register_mtls':
            hexagamecloud_action_register_mtls($vars, $client, $modulelink);
            break;
        default:
            hexagamecloud_page_dashboard($vars, $client, $storage, $modulelink);
            break;
    }
}

function hexagamecloud_create_client(array $vars): HexaGameCloudAddonApiClient
{
    $apiUrl = trim((string) ($vars['api_url'] ?? ''));
    $integrationId = trim((string) ($vars['integration_id'] ?? ''));
    $apiSecret = trim((string) ($vars['api_secret'] ?? ''));

    if ($apiUrl === '' || $integrationId === '' || $apiSecret === '') {
        throw new RuntimeException('Configure API URL, Integration ID and API Secret in addon settings.');
    }

    $mtls = HexaGameCloudMtlsConfig::fromAddonSettings($vars);

    return new HexaGameCloudAddonApiClient($apiUrl, $integrationId, $apiSecret, $mtls);
}

function hexagamecloud_page_dashboard(
    array $vars,
    HexaGameCloudAddonApiClient $client,
    HexaGameCloudModuleStorage $storage,
    string $modulelink
): void {
    $health = $client->health();
    $stats = $client->dashboardStats();
    $accounts = $client->listAccounts();
    $unlinked = hexagamecloud_count_unlinked_whmcs_services($accounts['accounts'] ?? []);

    $orphans = 0;
    foreach ($accounts['accounts'] ?? [] as $account) {
        if (($account['linkStatus'] ?? '') === 'TERMINATED' && ($account['gameCloudStatus'] ?? '') !== 'DELETED') {
            $orphans++;
        }
    }

    $storage->recordOperation('dashboard:view', 'success', [
        'integrationId' => $stats['integrationId'] ?? null,
    ]);

    echo hexagamecloud_render_template('dashboard', [
        'modulelink' => $modulelink,
        'version' => HEXAGAMECLOUD_ADDON_VERSION,
        'health' => $health,
        'stats' => $stats,
        'linkedServices' => count($accounts['accounts'] ?? []),
        'unlinkedWhmcsServices' => $unlinked,
        'driftCount' => $orphans,
        'lastError' => $storage->getLastError(),
    ]);
}

function hexagamecloud_page_events(
    array $vars,
    HexaGameCloudAddonApiClient $client,
    HexaGameCloudModuleStorage $storage,
    string $modulelink
): void {
    $localEvents = $storage->listLocalEvents(100);
    $pendingCount = $storage->countLocalEventsByStatus('pending');

    echo hexagamecloud_render_template('events', [
        'modulelink' => $modulelink,
        'localEvents' => $localEvents,
        'pendingCount' => $pendingCount,
        'deadLetterCount' => $storage->countLocalEventsByStatus('dead_letter'),
    ]);
}

function hexagamecloud_page_reconciliation(
    array $vars,
    HexaGameCloudAddonApiClient $client,
    HexaGameCloudModuleStorage $storage,
    string $modulelink
): void {
    $runs = $storage->listReconciliationRuns(20);
    $dryRun = isset($_GET['dry']) ? true : false;
    $liveResult = null;

    if (isset($_GET['run'])) {
        try {
            $liveResult = $client->reconcileAll([
                'dryRun' => $dryRun,
                'autoRepair' => !$dryRun && !empty($_GET['repair']),
            ]);
            $storage->saveReconciliationRun($liveResult, $dryRun ? 'dry_run' : 'live');
        } catch (Throwable $exception) {
            $storage->setLastError($exception->getMessage());
            $liveResult = ['error' => $exception->getMessage()];
        }
    }

    echo hexagamecloud_render_template('reconciliation', [
        'modulelink' => $modulelink,
        'runs' => $runs,
        'liveResult' => $liveResult,
        'dryRun' => $dryRun,
    ]);
}

function hexagamecloud_action_poll(
    array $vars,
    HexaGameCloudAddonApiClient $client,
    HexaGameCloudModuleStorage $storage,
    string $modulelink
): void {
    $limit = (int) ($vars['event_poll_limit'] ?? 50);
    $poller = new HexaGameCloudEventPoller($client, $storage, max(1, min($limit, 100)));

    try {
        $result = $poller->pollAndAcknowledge();
        $storage->recordOperation('events:poll', 'success', $result);
    } catch (Throwable $exception) {
        $storage->setLastError($exception->getMessage());
        $storage->recordOperation('events:poll', 'error', ['message' => $exception->getMessage()]);
    }

    header('Location: ' . $modulelink . '&action=events');
    exit;
}

function hexagamecloud_action_reconcile(
    array $vars,
    HexaGameCloudAddonApiClient $client,
    HexaGameCloudModuleStorage $storage,
    string $modulelink
): void {
    header('Location: ' . $modulelink . '&action=reconciliation&run=1&dry=1');
    exit;
}

function hexagamecloud_action_ack(
    array $vars,
    HexaGameCloudAddonApiClient $client,
    HexaGameCloudModuleStorage $storage,
    string $modulelink
): void {
    $eventId = (int) ($_POST['local_event_id'] ?? 0);
    $deadLetter = !empty($_POST['dead_letter']);
    $local = $storage->getLocalEvent($eventId);

    if ($local && !empty($local['remote_event_id'])) {
        try {
            $client->acknowledgeEvents([$local['remote_event_id']], $deadLetter, 'Manual ack from WHMCS addon');
            $storage->updateLocalEventStatus($eventId, $deadLetter ? 'dead_letter' : 'acknowledged');
        } catch (Throwable $exception) {
            $storage->setLastError($exception->getMessage());
        }
    }

    header('Location: ' . $modulelink . '&action=events');
    exit;
}

function hexagamecloud_count_unlinked_whmcs_services(array $linkedAccounts): int
{
    $linkedIds = [];
    foreach ($linkedAccounts as $account) {
        if (!empty($account['externalServiceId'])) {
            $linkedIds[(string) $account['externalServiceId']] = true;
        }
    }

    $hosting = Capsule::table('tblhosting')
        ->join('tblproducts', 'tblproducts.id', '=', 'tblhosting.packageid')
        ->where('tblproducts.servertype', 'hexagamecloud')
        ->whereIn('tblhosting.domainstatus', ['Active', 'Suspended', 'Pending'])
        ->select('tblhosting.id')
        ->get();

    $unlinked = 0;
    foreach ($hosting as $row) {
        if (!isset($linkedIds[(string) $row->id])) {
            $unlinked++;
        }
    }

    return $unlinked;
}

function hexagamecloud_render_template(string $name, array $data): string
{
    $path = __DIR__ . '/templates/' . $name . '.tpl';
    if (!is_readable($path)) {
        return '<div class="alert alert-danger">Template missing: ' . htmlspecialchars($name) . '</div>';
    }

    $html = file_get_contents($path);
    if ($html === false) {
        return '';
    }

    foreach ($data as $key => $value) {
        $placeholder = '{{' . $key . '}}';
        if (is_scalar($value) || $value === null) {
            $html = str_replace($placeholder, htmlspecialchars((string) $value, ENT_QUOTES, 'UTF-8'), $html);
        }
    }

    return hexagamecloud_expand_blocks($html, $data);
}

function hexagamecloud_expand_blocks(string $html, array $data): string
{
    if (!empty($data['stats']) && is_array($data['stats'])) {
        foreach ($data['stats'] as $key => $value) {
            $html = str_replace('{{stats.' . $key . '}}', htmlspecialchars((string) $value, ENT_QUOTES, 'UTF-8'), $html);
        }
    }

    if (!empty($data['health']) && is_array($data['health'])) {
        foreach ($data['health'] as $key => $value) {
            $html = str_replace('{{health.' . $key . '}}', htmlspecialchars((string) $value, ENT_QUOTES, 'UTF-8'), $html);
        }
    }

    if (!empty($data['localEvents']) && is_array($data['localEvents'])) {
        $rows = '';
        foreach ($data['localEvents'] as $event) {
            $rows .= '<tr>';
            $rows .= '<td>' . (int) $event['id'] . '</td>';
            $rows .= '<td>' . htmlspecialchars((string) $event['event_type'], ENT_QUOTES, 'UTF-8') . '</td>';
            $rows .= '<td>' . htmlspecialchars((string) $event['status'], ENT_QUOTES, 'UTF-8') . '</td>';
            $rows .= '<td><code>' . htmlspecialchars((string) $event['payload'], ENT_QUOTES, 'UTF-8') . '</code></td>';
            $rows .= '<td>' . htmlspecialchars((string) $event['created_at'], ENT_QUOTES, 'UTF-8') . '</td>';
            $rows .= '<td><form method="post" action="' . htmlspecialchars((string) $data['modulelink'], ENT_QUOTES, 'UTF-8') . '&action=ack">';
            $rows .= '<input type="hidden" name="local_event_id" value="' . (int) $event['id'] . '">';
            $rows .= '<button type="submit" class="btn btn-default btn-xs">Ack</button> ';
            $rows .= '<button type="submit" name="dead_letter" value="1" class="btn btn-danger btn-xs">Dead letter</button>';
            $rows .= '</form></td>';
            $rows .= '</tr>';
        }
        $html = str_replace('{{events.rows}}', $rows, $html);
    }

    if (!empty($data['runs']) && is_array($data['runs'])) {
        $rows = '';
        foreach ($data['runs'] as $run) {
            $rows .= '<tr>';
            $rows .= '<td>' . htmlspecialchars((string) $run['run_id'], ENT_QUOTES, 'UTF-8') . '</td>';
            $rows .= '<td>' . htmlspecialchars((string) $run['mode'], ENT_QUOTES, 'UTF-8') . '</td>';
            $rows .= '<td>' . (int) $run['issue_count'] . '</td>';
            $rows .= '<td>' . htmlspecialchars((string) $run['created_at'], ENT_QUOTES, 'UTF-8') . '</td>';
            $rows .= '</tr>';
        }
        $html = str_replace('{{reconciliation.rows}}', $rows, $html);
    }

    if (!empty($data['liveResult']) && is_array($data['liveResult'])) {
        $html = str_replace(
            '{{reconciliation.result}}',
            '<pre>' . htmlspecialchars(json_encode($data['liveResult'], JSON_PRETTY_PRINT), ENT_QUOTES, 'UTF-8') . '</pre>',
            $html
        );
    } else {
        $html = str_replace('{{reconciliation.result}}', '', $html);
    }

    if (!empty($data['status']) && is_array($data['status'])) {
        foreach ($data['status'] as $key => $value) {
            $display = is_bool($value) ? ($value ? 'yes' : 'no') : (string) $value;
            $html = str_replace('{{status.' . $key . '}}', htmlspecialchars($display, ENT_QUOTES, 'UTF-8'), $html);
        }
    }

    if (!empty($data['products']) && is_array($data['products'])) {
        $productOptions = '';
        foreach ($data['products'] as $product) {
            $productOptions .= '<option value="' . (int) $product['id'] . '">'
                . htmlspecialchars((string) $product['name'], ENT_QUOTES, 'UTF-8')
                . ' (#' . (int) $product['id'] . ')</option>';
        }
        $html = str_replace('{{mappings.product_options}}', $productOptions, $html);
    }

    if (!empty($data['plans']['plans']) && is_array($data['plans']['plans'])) {
        $planOptions = '';
        foreach ($data['plans']['plans'] as $plan) {
            $planOptions .= '<option value="' . htmlspecialchars((string) $plan['slug'], ENT_QUOTES, 'UTF-8') . '">'
                . htmlspecialchars((string) $plan['name'], ENT_QUOTES, 'UTF-8')
                . ' (' . htmlspecialchars((string) $plan['slug'], ENT_QUOTES, 'UTF-8') . ')</option>';
        }
        $html = str_replace('{{mappings.plan_options}}', $planOptions, $html);
    }

    if (!empty($data['mappings']) && is_array($data['mappings'])) {
        $rows = '';
        foreach ($data['mappings'] as $mapping) {
            $rows .= '<tr>';
            $rows .= '<td>' . (int) $mapping['whmcs_product_id'] . '</td>';
            $rows .= '<td>' . htmlspecialchars((string) $mapping['whmcs_product_name'], ENT_QUOTES, 'UTF-8') . '</td>';
            $rows .= '<td>' . htmlspecialchars((string) $mapping['plan_slug'], ENT_QUOTES, 'UTF-8') . '</td>';
            $rows .= '<td>' . htmlspecialchars((string) $mapping['default_software'], ENT_QUOTES, 'UTF-8') . '</td>';
            $rows .= '<td>' . ((int) $mapping['is_active'] === 1 ? 'yes' : 'no') . '</td>';
            $rows .= '<td><a class="btn btn-danger btn-xs" href="'
                . htmlspecialchars((string) $data['modulelink'], ENT_QUOTES, 'UTF-8')
                . '&action=delete_mapping&product_id=' . (int) $mapping['whmcs_product_id']
                . '">Delete</a></td>';
            $rows .= '</tr>';
        }
        $html = str_replace('{{mappings.rows}}', $rows, $html);
    }

    return $html;
}

function hexagamecloud_render_error(array $vars, string $message): void
{
    echo '<div class="alert alert-danger"><strong>GameCloud addon error:</strong> ' . htmlspecialchars($message, ENT_QUOTES, 'UTF-8') . '</div>';
    echo '<p>Configure the addon under <em>Setup → Addon Modules → HexaHost GameCloud</em>.</p>';
}

function hexagamecloud_page_mappings(array $vars, HexaGameCloudAddonApiClient $client, string $modulelink): void
{
    $mappingStorage = new HexaGameCloudProductMappingStorage();
    $plans = $client->listPlans();
    $products = $mappingStorage->listWhmcsProducts();
    $mappings = $mappingStorage->listMappings();
    $validation = null;

    if (!empty($_GET['validate'])) {
        try {
            $validation = $client->validatePlan((string) $_GET['validate']);
        } catch (Throwable $exception) {
            $validation = ['valid' => false, 'error' => $exception->getMessage()];
        }
    }

    echo hexagamecloud_render_template('mappings', [
        'modulelink' => $modulelink,
        'plans' => $plans,
        'products' => $products,
        'mappings' => $mappings,
        'validation' => $validation,
    ]);
}

function hexagamecloud_action_save_mapping(array $vars, HexaGameCloudAddonApiClient $client, string $modulelink): void
{
    $mappingStorage = new HexaGameCloudProductMappingStorage();
    $planSlug = trim((string) ($_POST['plan_slug'] ?? ''));

    try {
        $validation = $client->validatePlan($planSlug);
        if (empty($validation['valid'])) {
            throw new RuntimeException('Invalid GameCloud plan slug: ' . $planSlug);
        }

        $mappingStorage->upsertMapping([
            'whmcs_product_id' => (int) ($_POST['whmcs_product_id'] ?? 0),
            'whmcs_product_name' => (string) ($_POST['whmcs_product_name'] ?? ''),
            'plan_slug' => $planSlug,
            'default_edition' => (string) ($_POST['default_edition'] ?? 'JAVA'),
            'default_software' => (string) ($_POST['default_software'] ?? 'VANILLA'),
            'default_version' => (string) ($_POST['default_version'] ?? '1.21.1'),
            'is_active' => !empty($_POST['is_active']),
        ]);
    } catch (Throwable $exception) {
        echo '<div class="alert alert-danger">' . htmlspecialchars($exception->getMessage(), ENT_QUOTES, 'UTF-8') . '</div>';
        hexagamecloud_page_mappings($vars, $client, $modulelink);
        return;
    }

    header('Location: ' . $modulelink . '&action=mappings');
    exit;
}

function hexagamecloud_action_delete_mapping(array $vars, string $modulelink): void
{
    $mappingStorage = new HexaGameCloudProductMappingStorage();
    $mappingStorage->deleteMapping((int) ($_GET['product_id'] ?? 0));
    header('Location: ' . $modulelink . '&action=mappings');
    exit;
}

function hexagamecloud_page_mtls(array $vars, HexaGameCloudAddonApiClient $client, string $modulelink): void
{
    $status = $client->mtlsStatus();
    echo hexagamecloud_render_template('mtls', [
        'modulelink' => $modulelink,
        'status' => $status,
        'fingerprint_configured' => trim((string) ($vars['mtls_cert_fingerprint'] ?? '')),
    ]);
}

function hexagamecloud_action_register_mtls(array $vars, HexaGameCloudAddonApiClient $client, string $modulelink): void
{
    $fingerprint = trim((string) ($_POST['fingerprint'] ?? $vars['mtls_cert_fingerprint'] ?? ''));
    $subject = trim((string) ($_POST['subject'] ?? ''));

    try {
        $client->registerMtlsFingerprint($fingerprint, $subject !== '' ? $subject : null);
    } catch (Throwable $exception) {
        echo '<div class="alert alert-danger">' . htmlspecialchars($exception->getMessage(), ENT_QUOTES, 'UTF-8') . '</div>';
        hexagamecloud_page_mtls($vars, $client, $modulelink);
        return;
    }

    header('Location: ' . $modulelink . '&action=mtls');
    exit;
}
