<?php
declare(strict_types=1);

/**
 * Multi-servidor por MAC.
 * Mantém compatibilidade com as colunas legadas usuarios.dns/username/password:
 * o primeiro servidor ativo sempre é espelhado nessas colunas.
 */

if (!function_exists('ms_normalize_dns')) {
    function ms_normalize_dns(?string $value): string {
        $dns = trim((string)$value);
        if ($dns === '') return '';
        if (!preg_match('~^https?://~i', $dns)) $dns = 'http://' . $dns;
        return rtrim($dns, '/');
    }
}

if (!function_exists('ms_server_key')) {
    function ms_server_key(?string $key = null): string {
        $clean = preg_replace('/[^a-zA-Z0-9_-]/', '', trim((string)$key)) ?: '';
        if ($clean !== '') return substr($clean, 0, 64);
        try { return bin2hex(random_bytes(12)); } catch (Throwable $e) { return substr(hash('sha256', uniqid('', true)), 0, 24); }
    }
}

if (!function_exists('ms_ensure_schema')) {
    function ms_ensure_schema(PDO $db): void {
        // A mesma requisição usa este helper em mais de um fluxo. Evita repetir DDL/migração.
        static $ready = [];
        $connectionKey = function_exists('spl_object_id') ? spl_object_id($db) : 1;
        if (!empty($ready[$connectionKey])) return;

        $db->exec("CREATE TABLE IF NOT EXISTS user_servers (
            id INTEGER PRIMARY KEY AUTOINCREMENT,
            server_key TEXT NOT NULL,
            mac TEXT NOT NULL,
            owner_id INTEGER NOT NULL DEFAULT 0,
            name TEXT NOT NULL DEFAULT '',
            dns TEXT NOT NULL DEFAULT '',
            username TEXT NOT NULL DEFAULT '',
            password TEXT NOT NULL DEFAULT '',
            sort_order INTEGER NOT NULL DEFAULT 0,
            is_primary INTEGER NOT NULL DEFAULT 0,
            enabled INTEGER NOT NULL DEFAULT 1,
            created_at INTEGER NOT NULL DEFAULT (strftime('%s','now')),
            updated_at INTEGER NOT NULL DEFAULT (strftime('%s','now')),
            UNIQUE(mac, server_key)
        )");
        $db->exec("CREATE INDEX IF NOT EXISTS idx_user_servers_mac ON user_servers(mac, enabled, sort_order, id)");
        $db->exec("CREATE INDEX IF NOT EXISTS idx_user_servers_owner ON user_servers(owner_id, mac)");

        // Migração em uma única instrução: não faz uma consulta por usuário a cada pulse do app.
        try {
            $db->exec("INSERT INTO user_servers (
                    server_key,mac,owner_id,name,dns,username,password,
                    sort_order,is_primary,enabled,created_at,updated_at
                )
                SELECT
                    'legacy_' || substr(lower(hex(randomblob(16))),1,20),
                    TRIM(u.mac), COALESCE(u.usuario_id,0), 'Servidor principal',
                    TRIM(u.dns), TRIM(u.username), u.password,
                    0,1,1,strftime('%s','now'),strftime('%s','now')
                FROM usuarios u
                WHERE TRIM(COALESCE(u.mac,''))<>''
                  AND TRIM(COALESCE(u.dns,''))<>''
                  AND TRIM(COALESCE(u.username,''))<>''
                  AND TRIM(COALESCE(u.password,''))<>''
                  AND NOT EXISTS (SELECT 1 FROM user_servers s WHERE s.mac=u.mac)");
        } catch (Throwable $e) {
            // Um banco legado incompleto não pode derrubar users.php nem a sincronização dos apps.
        }
        $ready[$connectionKey] = true;
    }
}

if (!function_exists('ms_get_servers')) {
    function ms_get_servers(PDO $db, string $mac, ?array $legacyUser = null): array {
        ms_ensure_schema($db);
        $out = [];
        try {
            $st = $db->prepare("SELECT server_key,name,dns,username,password,sort_order,is_primary,enabled,updated_at FROM user_servers WHERE mac=? AND enabled=1 ORDER BY is_primary DESC, sort_order ASC, id ASC");
            $st->execute([$mac]);
            while ($r = $st->fetch(PDO::FETCH_ASSOC)) {
                $dns = ms_normalize_dns((string)($r['dns'] ?? ''));
                $username = trim((string)($r['username'] ?? ''));
                $password = (string)($r['password'] ?? '');
                if ($dns === '' || $username === '' || $password === '') continue;
                $out[] = [
                    'key' => (string)($r['server_key'] ?? ''),
                    'name' => trim((string)($r['name'] ?? '')) ?: ('Servidor ' . (count($out) + 1)),
                    'dns' => $dns,
                    'username' => $username,
                    'password' => $password,
                    'order' => (int)($r['sort_order'] ?? count($out)),
                    'primary' => (int)($r['is_primary'] ?? 0) === 1,
                    'updated_at' => (int)($r['updated_at'] ?? 0),
                ];
            }
            $st->closeCursor();
        } catch (Throwable $e) {}

        if (!$out && $legacyUser) {
            $dns = ms_normalize_dns((string)($legacyUser['dns'] ?? ''));
            $username = trim((string)($legacyUser['username'] ?? ''));
            $password = (string)($legacyUser['password'] ?? '');
            if ($dns !== '' && $username !== '' && $password !== '') {
                $out[] = [
                    'key' => 'legacy_' . substr(hash('sha256', $mac . '|' . $dns . '|' . $username), 0, 20),
                    'name' => 'Servidor principal',
                    'dns' => $dns,
                    'username' => $username,
                    'password' => $password,
                    'order' => 0,
                    'primary' => true,
                    'updated_at' => 0,
                ];
            }
        }

        if ($out) {
            $hasPrimary = false;
            foreach ($out as $server) if (!empty($server['primary'])) { $hasPrimary = true; break; }
            if (!$hasPrimary) $out[0]['primary'] = true;
        }
        return array_values($out);
    }
}

if (!function_exists('ms_credentials_signature')) {
    /**
     * Assinatura estável das credenciais liberadas no painel.
     * O aplicativo calcula o mesmo valor com a lista salva localmente e força
     * novo login quando DNS, usuário, senha ou a quantidade de servidores muda.
     */
    function ms_credentials_signature(array $servers): string {
        if (!$servers) return '';
        $parts = [];
        $seen = [];
        foreach ($servers as $server) {
            $dns = strtolower(rtrim(ms_normalize_dns((string)($server['dns'] ?? '')), '/'));
            $username = trim((string)($server['username'] ?? ''));
            $password = (string)($server['password'] ?? '');
            if ($dns === '' || $username === '' || $password === '') continue;
            $part = strlen($dns) . ':' . $dns
                . '|' . strlen($username) . ':' . $username
                . '|' . strlen($password) . ':' . $password;
            if (isset($seen[$part])) continue;
            $seen[$part] = true;
            $parts[] = $part;
        }
        sort($parts, SORT_STRING);
        return $parts ? hash('sha256', implode("\n", $parts)) : '';
    }
}

if (!function_exists('ms_payload')) {
    function ms_payload(PDO $db, string $mac, ?array $legacyUser = null): array {
        $servers = ms_get_servers($db, $mac, $legacyUser);
        return [
            'servers' => $servers,
            'multi_server_enabled' => count($servers) > 1,
            'server_count' => count($servers),
            'primary_server_key' => $servers[0]['key'] ?? '',
            'servers_updated_at' => $servers ? max(array_map(static fn($s) => (int)($s['updated_at'] ?? 0), $servers)) : 0,
            'credentials_signature' => ms_credentials_signature($servers),
        ];
    }
}

if (!function_exists('ms_save_servers')) {
    function ms_save_servers(PDO $db, string $mac, int $ownerId, array $servers): array {
        ms_ensure_schema($db);
        $clean = [];
        foreach ($servers as $index => $raw) {
            if (!is_array($raw)) continue;
            $dns = ms_normalize_dns((string)($raw['dns'] ?? ''));
            $username = trim((string)($raw['username'] ?? ''));
            $password = (string)($raw['password'] ?? '');
            if ($dns === '' || $username === '' || $password === '') continue;
            $name = trim((string)($raw['name'] ?? ''));
            if ($name === '') $name = 'Servidor ' . (count($clean) + 1);
            $clean[] = [
                'key' => ms_server_key((string)($raw['key'] ?? '')),
                'name' => mb_substr($name, 0, 60),
                'dns' => $dns,
                'username' => mb_substr($username, 0, 190),
                'password' => mb_substr($password, 0, 190),
                'order' => count($clean),
                'primary' => count($clean) === 0,
            ];
            if (count($clean) >= 12) break;
        }

        $db->beginTransaction();
        try {
            $existing = [];
            $stExisting = $db->prepare("SELECT server_key FROM user_servers WHERE mac=?");
            $stExisting->execute([$mac]);
            while ($key = $stExisting->fetchColumn()) $existing[(string)$key] = true;
            $stExisting->closeCursor();

            $upsert = $db->prepare("INSERT INTO user_servers (server_key,mac,owner_id,name,dns,username,password,sort_order,is_primary,enabled,created_at,updated_at)
                VALUES (?,?,?,?,?,?,?,?,?,1,?,?)
                ON CONFLICT(mac,server_key) DO UPDATE SET owner_id=excluded.owner_id,name=excluded.name,dns=excluded.dns,username=excluded.username,password=excluded.password,sort_order=excluded.sort_order,is_primary=excluded.is_primary,enabled=1,updated_at=excluded.updated_at");
            $now = time();
            $kept = [];
            foreach ($clean as $server) {
                $key = (string)$server['key'];
                $kept[] = $key;
                $upsert->execute([$key,$mac,$ownerId,$server['name'],$server['dns'],$server['username'],$server['password'],$server['order'],$server['primary']?1:0,$now,$now]);
            }

            if ($kept) {
                $marks = implode(',', array_fill(0, count($kept), '?'));
                $params = array_merge([$mac], $kept);
                $del = $db->prepare("DELETE FROM user_servers WHERE mac=? AND server_key NOT IN ($marks)");
                $del->execute($params);
            } else {
                $del = $db->prepare("DELETE FROM user_servers WHERE mac=?");
                $del->execute([$mac]);
            }

            // Espelha o primeiro servidor para manter todos os endpoints antigos funcionando.
            if ($clean) {
                $primary = $clean[0];
                $upUser = $db->prepare("UPDATE usuarios SET dns=?, username=?, password=? WHERE mac=?");
                $upUser->execute([$primary['dns'], $primary['username'], $primary['password'], $mac]);
            } else {
                $upUser = $db->prepare("UPDATE usuarios SET dns='', username='', password='' WHERE mac=?");
                $upUser->execute([$mac]);
            }

            $db->commit();
            return $clean;
        } catch (Throwable $e) {
            if ($db->inTransaction()) $db->rollBack();
            throw $e;
        }
    }
}

if (!function_exists('ms_delete_servers')) {
    function ms_delete_servers(PDO $db, string $mac): void {
        ms_ensure_schema($db);
        try {
            $st = $db->prepare("DELETE FROM user_servers WHERE mac=?");
            $st->execute([$mac]);
        } catch (Throwable $e) {}
    }
}


if (!function_exists('ms_get_servers_map')) {
    function ms_get_servers_map(PDO $db, ?int $ownerId = null): array {
        ms_ensure_schema($db);
        $map = [];
        try {
            if ($ownerId !== null) {
                $st = $db->prepare("SELECT server_key,mac,name,dns,username,password,sort_order,is_primary,updated_at FROM user_servers WHERE enabled=1 AND owner_id=? ORDER BY mac ASC,is_primary DESC,sort_order ASC,id ASC");
                $st->execute([$ownerId]);
            } else {
                $st = $db->query("SELECT server_key,mac,name,dns,username,password,sort_order,is_primary,updated_at FROM user_servers WHERE enabled=1 ORDER BY mac ASC,is_primary DESC,sort_order ASC,id ASC");
            }
            while ($r = $st->fetch(PDO::FETCH_ASSOC)) {
                $mac = trim((string)($r['mac'] ?? ''));
                $dns = ms_normalize_dns((string)($r['dns'] ?? ''));
                $username = trim((string)($r['username'] ?? ''));
                $password = (string)($r['password'] ?? '');
                if ($mac === '' || $dns === '' || $username === '' || $password === '') continue;
                if (!isset($map[$mac])) $map[$mac] = [];
                $map[$mac][] = [
                    'key' => (string)($r['server_key'] ?? ''),
                    'name' => trim((string)($r['name'] ?? '')) ?: ('Servidor ' . (count($map[$mac]) + 1)),
                    'dns' => $dns,
                    'username' => $username,
                    'password' => $password,
                    'order' => (int)($r['sort_order'] ?? count($map[$mac])),
                    'primary' => (int)($r['is_primary'] ?? 0) === 1,
                    'updated_at' => (int)($r['updated_at'] ?? 0),
                ];
            }
        } catch (Throwable $e) {}
        return $map;
    }
}
