<?php
declare(strict_types=1);

require_once __DIR__ . '/media_assets.php';
require_once __DIR__ . '/revenda_schema.php';

function pp_column_exists(PDO $db, string $table, string $column): bool {
    $safeTable = preg_replace('/[^a-zA-Z0-9_]/', '', $table);
    if ($safeTable === '') return false;
    $rows = $db->query("PRAGMA table_info({$safeTable})")->fetchAll(PDO::FETCH_ASSOC) ?: [];
    foreach ($rows as $row) if ((string)($row['name'] ?? '') === $column) return true;
    return false;
}

function pp_add_column_if_missing(PDO $db, string $table, string $column, string $definition): void {
    $safeTable = preg_replace('/[^a-zA-Z0-9_]/', '', $table);
    $safeColumn = preg_replace('/[^a-zA-Z0-9_]/', '', $column);
    if ($safeTable === '' || $safeColumn === '' || pp_column_exists($db, $safeTable, $safeColumn)) return;

    try {
        $db->exec("ALTER TABLE {$safeTable} ADD COLUMN {$safeColumn} {$definition}");
    } catch (PDOException $e) {
        // Outra requisição pode ter concluído a mesma migração entre o PRAGMA e
        // o ALTER TABLE. Nesse caso a coluna já existe e o painel pode seguir.
        if (!pp_column_exists($db, $safeTable, $safeColumn)) throw $e;
    }
}

function pp_ensure_schema(PDO $db): void {
    $db->exec('PRAGMA busy_timeout = 30000');
    foreach ([
        'nome_cliente' => 'TEXT',
        'whatsapp' => 'TEXT',
        'usuario_id' => 'INTEGER DEFAULT 0',
        'device_type' => "TEXT DEFAULT 'tv'",
        'last_device_type' => "TEXT DEFAULT ''",
        'profile_image' => "TEXT DEFAULT ''",
        'profile_updated_at' => 'INTEGER DEFAULT 0',
        'profile_image_mime' => "TEXT DEFAULT ''",
        'profile_image_hash' => "TEXT DEFAULT ''",
        'profile_image_size' => 'INTEGER DEFAULT 0',
    ] as $column => $definition) {
        pp_add_column_if_missing($db, 'usuarios', $column, $definition);
    }

    $db->exec("CREATE TABLE IF NOT EXISTS app_pairing_codes (
        id INTEGER PRIMARY KEY AUTOINCREMENT,
        code TEXT NOT NULL UNIQUE,
        mac TEXT NOT NULL,
        device_type TEXT NOT NULL DEFAULT 'tv',
        status TEXT NOT NULL DEFAULT 'pending',
        revendedor_id INTEGER NOT NULL DEFAULT 0,
        created_at INTEGER NOT NULL DEFAULT 0,
        expires_at INTEGER NOT NULL DEFAULT 0,
        claimed_at INTEGER NOT NULL DEFAULT 0
    )");
    $db->exec('CREATE INDEX IF NOT EXISTS idx_app_pairing_mac ON app_pairing_codes(mac, status, expires_at)');
    $db->exec('CREATE INDEX IF NOT EXISTS idx_app_pairing_code ON app_pairing_codes(code)');

    // A tabela block_config já existia em versões antigas apenas com
    // id/title/message/show_qrcode/updated_at. Crie a base e migre TODAS as
    // colunas antes de executar qualquer INSERT que faça referência às novas.
    $db->exec("CREATE TABLE IF NOT EXISTS block_config (
        id INTEGER PRIMARY KEY CHECK (id=1),
        title TEXT NOT NULL DEFAULT 'Aplicativo em manutenção',
        message TEXT NOT NULL DEFAULT 'Estamos realizando uma manutenção rápida. Tente novamente em alguns instantes.',
        show_qrcode INTEGER NOT NULL DEFAULT 0,
        updated_at INTEGER NOT NULL DEFAULT 0,
        enabled INTEGER NOT NULL DEFAULT 0,
        background_path TEXT NOT NULL DEFAULT '',
        background_updated_at INTEGER NOT NULL DEFAULT 0
    )");

    foreach ([
        'title' => "TEXT NOT NULL DEFAULT 'Aplicativo em manutenção'",
        'message' => "TEXT NOT NULL DEFAULT 'Estamos realizando uma manutenção rápida. Tente novamente em alguns instantes.'",
        'show_qrcode' => 'INTEGER NOT NULL DEFAULT 0',
        'updated_at' => 'INTEGER NOT NULL DEFAULT 0',
        'enabled' => 'INTEGER NOT NULL DEFAULT 0',
        'background_path' => "TEXT NOT NULL DEFAULT ''",
        'background_updated_at' => 'INTEGER NOT NULL DEFAULT 0',
    ] as $column => $definition) {
        pp_add_column_if_missing($db, 'block_config', $column, $definition);
    }

    $db->exec("INSERT OR IGNORE INTO block_config
        (id,title,message,show_qrcode,updated_at,enabled,background_path,background_updated_at)
        VALUES
        (1,'Aplicativo em manutenção','Estamos realizando uma manutenção rápida. Tente novamente em alguns instantes.',0,strftime('%s','now'),0,'',0)");
}

function pp_normalize_mac(string $mac): string {
    $hex = strtoupper(preg_replace('/[^A-F0-9]/i', '', $mac));
    if (strlen($hex) !== 12) return '';
    return implode(':', str_split($hex, 2));
}

function pp_public_base(): string {
    return media_public_base_url();
}

function pp_asset_url(string $relativePath, int $version = 0): string {
    return media_asset_url($relativePath, $version, true);
}

function pp_profile_payload(array $row): array {
    $updated = (int)($row['profile_updated_at'] ?? 0);
    $path = (string)($row['profile_image'] ?? '');
    $url = pp_asset_url($path, $updated);
    // Banco antigo pode apontar para um arquivo removido manualmente. Nunca
    // devolva uma URL quebrada para os apps ou para o painel.
    if ($url === '') {
        $path = '';
        $updated = 0;
    }
    return [
        'profile_image' => $path,
        'profile_image_url' => $url,
        'profile_updated_at' => $updated,
        'profile_image_mime' => $url !== '' ? (string)($row['profile_image_mime'] ?? '') : '',
        'profile_image_hash' => $url !== '' ? (string)($row['profile_image_hash'] ?? '') : '',
        'profile_image_size' => $url !== '' ? (int)($row['profile_image_size'] ?? 0) : 0,
    ];
}

function pp_maintenance_payload(PDO $db): array {
    pp_ensure_schema($db);
    $cfg = $db->query('SELECT * FROM block_config WHERE id=1 LIMIT 1')->fetch(PDO::FETCH_ASSOC) ?: [];
    $bgUpdated = (int)($cfg['background_updated_at'] ?? 0);
    return [
        'maintenance_enabled' => ((int)($cfg['enabled'] ?? 0) === 1),
        'maintenance_title' => trim((string)($cfg['title'] ?? 'Aplicativo em manutenção')) ?: 'Aplicativo em manutenção',
        'maintenance_message' => trim((string)($cfg['message'] ?? 'Estamos realizando uma manutenção rápida. Tente novamente em alguns instantes.')),
        'maintenance_background' => pp_asset_url((string)($cfg['background_path'] ?? ''), $bgUpdated),
        'maintenance_updated_at' => (int)($cfg['updated_at'] ?? 0),
    ];
}

function pp_remove_uploaded_file(string $relativePath): void {
    media_delete_relative_file($relativePath);
}

function pp_store_image_meta(array $file, string $folder, string $prefix): array {
    return media_store_uploaded_image($file, $folder, $prefix, 5 * 1024 * 1024, false);
}

function pp_store_image(array $file, string $folder, string $prefix): string {
    $stored = pp_store_image_meta($file, $folder, $prefix);
    return (string)($stored['path'] ?? '');
}

function pp_create_pairing_code(PDO $db, string $mac, string $deviceType): array {
    pp_ensure_schema($db);
    $mac = pp_normalize_mac($mac);
    if ($mac === '') throw new RuntimeException('MAC inválido.');
    $deviceType = strtolower(trim($deviceType)) === 'mobile' ? 'mobile' : 'tv';
    $now = time();

    // O código permanece válido enquanto o cliente continua aguardando.
    // Códigos antigos com expiração continuam compatíveis. Registros já
    // concluídos são removidos para que os 9.000 códigos possam ser reutilizados.
    $db->prepare("UPDATE app_pairing_codes SET status='expired' WHERE status='pending' AND expires_at>0 AND expires_at<=?")->execute([$now]);
    $db->prepare("DELETE FROM app_pairing_codes WHERE status IN ('completed','cancelled','expired') AND created_at<?")->execute([$now - 86400]);
    $st = $db->prepare("SELECT * FROM app_pairing_codes WHERE mac=? AND status='pending' AND (expires_at=0 OR expires_at>?) ORDER BY id DESC LIMIT 1");
    $st->execute([$mac, $now]);
    $existing = $st->fetch(PDO::FETCH_ASSOC);
    if ($existing) return $existing;

    for ($i=0; $i<60; $i++) {
        $code = (string)random_int(1000, 9999);
        try {
            $ins = $db->prepare("INSERT INTO app_pairing_codes (code,mac,device_type,status,created_at,expires_at) VALUES (?,?,?,'pending',?,0)");
            $ins->execute([$code,$mac,$deviceType,$now]);
            return ['code'=>$code,'mac'=>$mac,'device_type'=>$deviceType,'status'=>'pending','created_at'=>$now,'expires_at'=>0,'revendedor_id'=>0];
        } catch (Throwable $e) {}
    }
    throw new RuntimeException('Não foi possível gerar o código.');
}

function pp_pairing_status(PDO $db, string $mac, string $code): array {
    pp_ensure_schema($db);
    $mac = pp_normalize_mac($mac);
    $code = preg_replace('/\D+/', '', $code);
    $st = $db->prepare('SELECT * FROM app_pairing_codes WHERE code=? AND mac=? ORDER BY id DESC LIMIT 1');
    $st->execute([$code,$mac]);
    $row = $st->fetch(PDO::FETCH_ASSOC) ?: [];
    if (!$row) return ['status'=>'not_found'];
    if ((string)$row['status'] === 'pending' && (int)$row['expires_at'] > 0 && (int)$row['expires_at'] <= time()) {
        $db->prepare("UPDATE app_pairing_codes SET status='expired' WHERE id=?")->execute([(int)$row['id']]);
        $row['status'] = 'expired';
    }
    return $row;
}

function pp_cancel_pairing(PDO $db, string $mac, string $code): bool {
    pp_ensure_schema($db);
    $mac = pp_normalize_mac($mac);
    $code = preg_replace('/\D+/', '', $code);
    if ($mac === '' || strlen($code) !== 4) return false;
    $st = $db->prepare("UPDATE app_pairing_codes SET status='cancelled' WHERE mac=? AND code=? AND status='pending'");
    $st->execute([$mac, $code]);
    return $st->rowCount() > 0;
}

function pp_complete_pairing(PDO $db, string $mac, string $code): bool {
    pp_ensure_schema($db);
    $mac = pp_normalize_mac($mac);
    $code = preg_replace('/\D+/', '', $code);
    if ($mac === '' || strlen($code) !== 4) return false;
    // O aplicativo só chama esta ação depois de receber DNS, usuário e senha.
    // A remoção libera imediatamente o código numérico para uso futuro.
    $st = $db->prepare("DELETE FROM app_pairing_codes WHERE mac=? AND code=? AND status='claimed'");
    $st->execute([$mac, $code]);
    return $st->rowCount() > 0;
}

function pp_claim_pairing(PDO $db, string $code, int $ownerId, bool $isAdmin = false): array {
    pp_ensure_schema($db);
    revenda_ensure_schema($db);
    $code = preg_replace('/\D+/', '', $code);
    if (strlen($code) !== 4) throw new RuntimeException('Informe o código de 4 dígitos.');

    if (!$isAdmin && $ownerId <= 0) {
        throw new RuntimeException('Revendedor inválido.');
    }
    if ($isAdmin) {
        $ownerId = 0;
    }

    $db->exec('BEGIN IMMEDIATE');
    try {
        $st = $db->prepare("SELECT * FROM app_pairing_codes WHERE code=? AND status='pending' AND (expires_at=0 OR expires_at>?) ORDER BY id DESC LIMIT 1");
        $st->execute([$code,time()]);
        $pair = $st->fetch(PDO::FETCH_ASSOC);
        if (!$pair) throw new RuntimeException('Código inválido, cancelado ou já utilizado.');

        if (!$isAdmin) {
            $revSt = $db->prepare('SELECT * FROM revendedores WHERE id=? LIMIT 1');
            $revSt->execute([$ownerId]);
            $rev = $revSt->fetch(PDO::FETCH_ASSOC);
            if (!$rev) throw new RuntimeException('Revendedor não encontrado.');
            if (strtolower(trim((string)($rev['status'] ?? 'ativo'))) !== 'ativo') throw new RuntimeException('Esta revenda está inativa.');
            if (function_exists('revenda_is_expired') && revenda_is_expired($rev['vencimento'] ?? '')) throw new RuntimeException('Esta revenda está vencida.');
        }

        $mac = (string)$pair['mac'];
        $ownerSt = $db->prepare('SELECT rowid AS _rowid, * FROM usuarios WHERE mac=? ORDER BY rowid DESC LIMIT 1');
        $ownerSt->execute([$mac]);
        $user = $ownerSt->fetch(PDO::FETCH_ASSOC);
        $currentOwner = $user ? (int)($user['usuario_id'] ?? 0) : 0;
        $hasCredentials = $user
            && trim((string)($user['dns'] ?? '')) !== ''
            && trim((string)($user['username'] ?? '')) !== ''
            && trim((string)($user['password'] ?? '')) !== '';

        if ($isAdmin) {
            if ($currentOwner > 0) {
                throw new RuntimeException('Este cliente já pertence a uma revenda.');
            }
            if ($currentOwner === 0 && $hasCredentials) {
                throw new RuntimeException('Este cliente já pertence ao administrador.');
            }
        } else {
            if ($currentOwner > 0 && $currentOwner !== $ownerId) {
                throw new RuntimeException('Este cliente já pertence a outra revenda.');
            }
            if ($currentOwner === 0 && $hasCredentials) {
                throw new RuntimeException('Este cliente já pertence ao administrador.');
            }
            if ((!$user || $currentOwner !== $ownerId) && !revenda_can_add_mac($db, $ownerId, 1)) {
                throw new RuntimeException('O limite de clientes desta revenda foi atingido.');
            }
        }

        if (!$user) {
            $db->prepare("INSERT INTO usuarios (mac,dns,username,password,status,vencimento,nome_cliente,whatsapp,usuario_id,device_type,last_device_type) VALUES (?,'','','','pending',NULL,'','',?,?,?)")
                ->execute([$mac,$ownerId,(string)$pair['device_type'],(string)$pair['device_type']]);
        } else {
            $db->prepare("UPDATE usuarios SET usuario_id=?, status=CASE WHEN trim(COALESCE(dns,''))='' OR trim(COALESCE(username,''))='' OR trim(COALESCE(password,''))='' THEN 'pending' ELSE status END, last_device_type=? WHERE rowid=?")
                ->execute([$ownerId,(string)$pair['device_type'],(int)$user['_rowid']]);
        }

        // O vínculo é exclusivo: admin remove qualquer associação antiga;
        // revenda substitui a associação pelo revendedor que confirmou o código.
        $db->prepare('DELETE FROM revendedor_clientes WHERE mac=?')->execute([$mac]);
        if (!$isAdmin) {
            $db->prepare('INSERT OR REPLACE INTO revendedor_clientes (revendedor_id,mac,created_at) VALUES (?,?,?)')
                ->execute([$ownerId,$mac,time()]);
        }
        try { $db->prepare('UPDATE online_status SET usuario_id=? WHERE mac=?')->execute([$ownerId,$mac]); } catch (Throwable $e) {}
        $db->prepare("UPDATE app_pairing_codes SET status='claimed',revendedor_id=?,claimed_at=? WHERE id=? AND status='pending'")
            ->execute([$ownerId,time(),(int)$pair['id']]);

        $db->exec('COMMIT');
        return [
            'status'=>'claimed',
            'mac'=>$mac,
            'device_type'=>(string)$pair['device_type'],
            'revendedor_id'=>$ownerId,
            'owner_id'=>$ownerId,
            'owner_type'=>$isAdmin ? 'admin' : 'revendedor',
            'requires_activation'=>!$hasCredentials,
        ];
    } catch (Throwable $e) {
        try { $db->exec('ROLLBACK'); } catch (Throwable $ignored) {}
        throw $e;
    }
}
