<?php
/**
 * lib/device.php — Lógica de dominio del dispositivo y construcción de la config.
 *
 * Centraliza:
 *   - localizar/crear dispositivos por MAC,
 *   - construir el JSON de /api/device/config EXACTAMENTE como el contrato.
 */

declare(strict_types=1);

require_once __DIR__ . '/db.php';
require_once __DIR__ . '/helpers.php';
require_once __DIR__ . '/settings.php';

/**
 * Busca un dispositivo por MAC normalizada. Devuelve la fila o null.
 *
 * @return array<string,mixed>|null
 */
function device_find_by_mac(string $mac): ?array
{
    $stmt = db()->prepare('SELECT * FROM devices WHERE mac = ? LIMIT 1');
    $stmt->execute([$mac]);
    $row = $stmt->fetch();
    return $row ?: null;
}

/**
 * Genera un device_key único (reintenta si choca con uno existente).
 */
function device_unique_key(): string
{
    $check = db()->prepare('SELECT 1 FROM devices WHERE device_key = ? LIMIT 1');
    do {
        $key = generate_device_key();
        $check->execute([$key]);
    } while ($check->fetchColumn() !== false);
    return $key;
}

/**
 * Crea un dispositivo en el primer contacto con valores por defecto desde settings.
 * Devuelve la fila recién creada.
 *
 * @return array<string,mixed>
 */
function device_create(string $mac, ?string $deviceId, ?string $model, ?string $appVersion): array
{
    $defaultTheme      = (int) settings_get('default_theme', '0');
    $defaultLoginTheme = (int) settings_get('default_login_theme', '0');
    $defLoginTitle     = settings_get('def_login_title', 'Bienvenido');
    $defLoginContent   = settings_get('def_login_content', 'Sube tu lista para empezar');
    $defNotiTitle      = settings_get('def_noti_title', '');
    $defNotiContent    = settings_get('def_noti_content', '');

    $stmt = db()->prepare(
        'INSERT INTO devices
            (mac, device_id, model, device_key, registered, theme, login_theme,
             sub_trial, sub_expire, plan_id, price, locked,
             parent_pin, parent_synced,
             login_title, login_content, noti_title, noti_content,
             app_version, last_seen, created_at)
         VALUES
            (?, ?, ?, ?, 0, ?, ?,
             1, NULL, NULL, NULL, 0,
             "0000", 1,
             ?, ?, ?, ?,
             ?, ?, ?)'
    );
    $now = now_iso();
    $stmt->execute([
        $mac, $deviceId, $model, device_unique_key(),
        $defaultTheme, $defaultLoginTheme,
        $defLoginTitle, $defLoginContent, $defNotiTitle, $defNotiContent,
        $appVersion, $now, $now,
    ]);

    $row = device_find_by_mac($mac);
    if ($row === null) {
        throw new RuntimeException('No se pudo crear el dispositivo.');
    }
    return $row;
}

/**
 * Actualiza last_seen, modelo, device_id y versión de app en cada contacto.
 */
function device_touch(int $deviceId, ?string $model, ?string $deviceIdStr, ?string $appVersion): void
{
    $stmt = db()->prepare(
        'UPDATE devices
            SET last_seen = ?,
                model = COALESCE(NULLIF(?, ""), model),
                device_id = COALESCE(NULLIF(?, ""), device_id),
                app_version = COALESCE(NULLIF(?, ""), app_version)
          WHERE id = ?'
    );
    $stmt->execute([now_iso(), $model, $deviceIdStr, $appVersion, $deviceId]);
}

/**
 * Carga las playlists de un dispositivo, ordenadas.
 *
 * @return array<int,array<string,mixed>>
 */
function device_playlists(int $deviceId): array
{
    $stmt = db()->prepare(
        'SELECT id, name, type, url, username, password, protected
           FROM playlists
          WHERE device_id = ?
          ORDER BY sort ASC, id ASC'
    );
    $stmt->execute([$deviceId]);
    $out = [];
    foreach ($stmt->fetchAll() as $row) {
        $out[] = [
            'id'        => (int) $row['id'],
            'name'      => (string) $row['name'],
            'type'      => (string) $row['type'],
            'url'       => (string) $row['url'],
            'username'  => (string) ($row['username'] ?? ''),
            'password'  => (string) ($row['password'] ?? ''),
            'protected' => ((int) $row['protected']) === 1,
        ];
    }
    return $out;
}

/**
 * Idiomas habilitados, en el formato del contrato.
 *
 * @return array<int,array<string,string>>
 */
function enabled_languages(): array
{
    $rows = db()->query(
        'SELECT code, name FROM languages WHERE enabled = 1 ORDER BY id ASC'
    )->fetchAll();
    $out = [];
    foreach ($rows as $row) {
        $out[] = ['code' => (string) $row['code'], 'name' => (string) $row['name']];
    }
    return $out;
}

/**
 * Anuncios de menú, ordenados por sort. Formato del contrato: [{title,url}].
 *
 * @return array<int,array<string,string>>
 */
function menu_ads_list(): array
{
    $rows = db()->query(
        'SELECT title, url FROM menu_ads ORDER BY sort ASC, id ASC'
    )->fetchAll();
    $out = [];
    foreach ($rows as $row) {
        $out[] = ['title' => (string) $row['title'], 'url' => (string) $row['url']];
    }
    return $out;
}

/**
 * Bloque "demo" del contrato: url/username/password (cadenas vacías si faltan).
 *
 * @return array<string,string>
 */
function demo_block(): array
{
    return [
        'url'      => settings_get('demo_url', ''),
        'username' => settings_get('demo_username', ''),
        'password' => settings_get('demo_password', ''),
    ];
}

/**
 * Bloque "support" del contrato: message_one / message_two.
 *
 * @return array<string,string>
 */
function support_block(): array
{
    return [
        'message_one' => settings_get('support_message_one', ''),
        'message_two' => settings_get('support_message_two', ''),
    ];
}

/**
 * Bloque "sport" del contrato: enabled/header/border/background/text/days.
 *
 * @return array<string,mixed>
 */
function sport_block(): array
{
    return [
        'enabled'    => (int) settings_get('sport_enabled', '0') === 1,
        'header'     => settings_get('sport_header', 'Deportes'),
        'border'     => settings_get('sport_border', '#000000'),
        'background' => settings_get('sport_background', '#000000'),
        'text'       => settings_get('sport_text', '#ffffff'),
        'days'       => (int) settings_get('sport_days', '7'),
    ];
}

/**
 * Catálogo de ligas en el formato del contrato: [{name,league_id}].
 *
 * @return array<int,array<string,string>>
 */
function leagues_list(): array
{
    $rows = db()->query(
        'SELECT name, league_id FROM leagues ORDER BY name ASC, id ASC'
    )->fetchAll();
    $out = [];
    foreach ($rows as $row) {
        $out[] = [
            'name'      => (string) $row['name'],
            'league_id' => (string) ($row['league_id'] ?? ''),
        ];
    }
    return $out;
}

/**
 * Eventos deportivos de los próximos $days días (incluido hoy), ordenados por
 * fecha y hora. channels se divide en array a partir de la cadena con comas.
 *
 * @return array<int,array<string,mixed>>
 */
function sport_events_window(int $days): array
{
    if ($days < 1) {
        $days = 1;
    }
    $today = gmdate('Y-m-d');
    $until = gmdate('Y-m-d', strtotime($today . ' +' . ($days - 1) . ' day') ?: time());

    // Columnas que el importador agrega en BD nuevas; resiliente si no existen aún.
    $have = array_column(db()->query('PRAGMA table_info(sport_events)')->fetchAll(), 'name');
    $opt = static fn (string $c) => in_array($c, $have, true) ? $c : "'' AS $c";
    $sel = implode(', ', array_map($opt, ['category', 'home_badge', 'away_badge', 'league_badge', 'status', 'score']));

    $stmt = db()->prepare(
        "SELECT league, home, away, event_date, event_time, channels, $sel
           FROM sport_events
          WHERE event_date >= ? AND event_date <= ?
          ORDER BY event_date ASC, event_time ASC, sort ASC, id ASC"
    );
    $stmt->execute([$today, $until]);

    $out = [];
    foreach ($stmt->fetchAll() as $row) {
        $channels = array_values(array_filter(
            array_map('trim', explode(',', (string) ($row['channels'] ?? ''))),
            static fn ($c) => $c !== ''
        ));
        $out[] = [
            'league'       => (string) ($row['league'] ?? ''),
            'home'         => (string) ($row['home'] ?? ''),
            'away'         => (string) ($row['away'] ?? ''),
            'date'         => (string) ($row['event_date'] ?? ''),
            'time'         => (string) ($row['event_time'] ?? ''),
            'channels'     => $channels,
            'category'     => (string) ($row['category'] ?? ''),
            'home_badge'   => (string) ($row['home_badge'] ?? ''),
            'away_badge'   => (string) ($row['away_badge'] ?? ''),
            'league_badge' => (string) ($row['league_badge'] ?? ''),
            'status'       => (string) ($row['status'] ?? ''),
            'score'        => (string) ($row['score'] ?? ''),
        ];
    }
    return $out;
}

/**
 * Determina si la suscripción está bloqueada: explícitamente (locked=1)
 * o porque sub_expire ya pasó.
 */
function subscription_locked(array $device): bool
{
    if ((int) ($device['locked'] ?? 0) === 1) {
        return true;
    }
    $expire = $device['sub_expire'] ?? null;
    if ($expire === null || trim((string) $expire) === '') {
        return false; // sin fecha => no expira por tiempo
    }
    // Expira al final del día indicado.
    $expireTs = strtotime((string) $expire . ' 23:59:59');
    if ($expireTs === false) {
        return false;
    }
    return time() > $expireTs;
}

/**
 * Construye el JSON COMPLETO de /api/device/config para un dispositivo.
 * Toma branding/update/theme de la propia fila, con respaldo en settings.
 *
 * @param array<string,mixed> $device Fila de devices.
 * @return array<string,mixed>
 */
function build_device_config(array $device): array
{
    $deviceId = (int) $device['id'];

    // Branding: usa el del dispositivo; si está vacío, cae al default global.
    $loginTitle   = first_nonempty($device['login_title']   ?? null, settings_get('def_login_title', 'Bienvenido'));
    $loginContent = first_nonempty($device['login_content'] ?? null, settings_get('def_login_content', 'Sube tu lista para empezar'));
    $notiTitle    = first_nonempty($device['noti_title']    ?? null, settings_get('def_noti_title', ''));
    $notiContent  = first_nonempty($device['noti_content']  ?? null, settings_get('def_noti_content', ''));

    // OTA: siempre desde settings (versión y url del apk).
    $latestVersion = settings_get('app_version', '1.0');
    $apkUrl        = settings_get('apk_url', '');

    return [
        'registered'  => ((int) $device['registered']) === 1,
        'device_key'  => (string) ($device['device_key'] ?? ''),
        // Tema GLOBAL para todos los dispositivos (settings → default_theme). No es por-dispositivo:
        // cambiarlo en el panel lo cambia para todos en su próximo arranque.
        'theme'       => (int) settings_get('default_theme', '0'),
        'login_theme' => (int) $device['login_theme'],
        'subscription' => [
            'trial'       => ((int) $device['sub_trial']) === 1,
            'expire_date' => (string) ($device['sub_expire'] ?? ''),
            'plan_id'     => (string) ($device['plan_id'] ?? ''),
            'price'       => (string) ($device['price'] ?? '0'),
            'locked'      => subscription_locked($device),
        ],
        'parental' => [
            'pin'    => (string) ($device['parent_pin'] ?? '0000'),
            'synced' => ((int) $device['parent_synced']) === 1,
        ],
        'branding' => [
            'login_title'          => $loginTitle,
            'login_content'        => $loginContent,
            'notification_title'   => $notiTitle,
            'notification_content' => $notiContent,
        ],
        'update' => [
            'latest_version' => $latestVersion,
            'apk_url'        => $apkUrl,
        ],
        'playlists' => device_playlists($deviceId),
        'languages' => enabled_languages(),
        'tmdb_key'  => settings_get('tmdb_api_key', ''),
        'demo'      => demo_block(),
        'support'   => support_block(),
        'menu_ads'  => menu_ads_list(),
        'sport'     => sport_block(),
    ];
}

/**
 * Devuelve el primer valor no vacío de la lista de argumentos.
 */
function first_nonempty(?string ...$values): string
{
    foreach ($values as $v) {
        if ($v !== null && trim($v) !== '') {
            return $v;
        }
    }
    return '';
}

/* ------------------------------------------------------------------ *
 *  Login por credenciales (modelo reseller)
 * ------------------------------------------------------------------ */

/**
 * Resuelve la URL de un DNS por su id. Devuelve '' si no existe.
 */
function dns_url_by_id(?int $dnsId): string
{
    if ($dnsId === null || $dnsId <= 0) {
        return '';
    }
    $stmt = db()->prepare('SELECT url FROM dns WHERE id = ? LIMIT 1');
    $stmt->execute([$dnsId]);
    $url = $stmt->fetchColumn();
    return $url === false ? '' : (string) $url;
}

/**
 * Busca una coincidencia de credenciales (username + password) para el modelo
 * reseller. Mira primero en la tabla `credentials` (creadas por el admin) y, si
 * no hay, en cualquier playlist Xtream ya existente con esas credenciales.
 *
 * Devuelve un array normalizado con name/url(DNS)/username/password/protected/expire
 * o null si no hay coincidencia.
 *
 * @return array<string,mixed>|null
 */
function credential_match(string $username, string $password): ?array
{
    // 1) Credenciales gestionadas por el admin.
    $stmt = db()->prepare(
        'SELECT c.id, c.username, c.password, c.dns_id, c.name, c.protected, c.expire,
                d.url AS dns_url
           FROM credentials c
           LEFT JOIN dns d ON d.id = c.dns_id
          WHERE c.username = ? AND c.password = ?
          LIMIT 1'
    );
    $stmt->execute([$username, $password]);
    $row = $stmt->fetch();
    if ($row) {
        return [
            'source'    => 'credentials',
            'name'      => (string) ($row['name'] ?? '') !== '' ? (string) $row['name'] : 'Mi Lista',
            'url'       => (string) ($row['dns_url'] ?? ''),
            'username'  => (string) $row['username'],
            'password'  => (string) $row['password'],
            'protected' => ((int) ($row['protected'] ?? 0)) === 1,
            'expire'    => (string) ($row['expire'] ?? ''),
        ];
    }

    // 2) Playlist Xtream existente en cualquier dispositivo con esas credenciales.
    $stmt = db()->prepare(
        "SELECT name, url, username, password, protected
           FROM playlists
          WHERE type = 'xtream' AND username = ? AND password = ?
          LIMIT 1"
    );
    $stmt->execute([$username, $password]);
    $row = $stmt->fetch();
    if ($row) {
        return [
            'source'    => 'playlists',
            'name'      => (string) ($row['name'] ?? '') !== '' ? (string) $row['name'] : 'Mi Lista',
            'url'       => (string) ($row['url'] ?? ''),
            'username'  => (string) $row['username'],
            'password'  => (string) $row['password'],
            'protected' => ((int) ($row['protected'] ?? 0)) === 1,
            'expire'    => '',
        ];
    }

    return null;
}

/**
 * Vincula una playlist Xtream (resuelta desde una credencial) a un dispositivo
 * si éste no la tiene ya. No duplica si ya existe la misma url+username+password.
 *
 * @param array<string,mixed> $match Resultado de credential_match().
 */
function bind_credential_to_device(int $deviceId, array $match): void
{
    $url      = (string) ($match['url'] ?? '');
    $username = (string) ($match['username'] ?? '');
    $password = (string) ($match['password'] ?? '');

    // ¿Ya existe esta misma playlist Xtream en el dispositivo?
    $check = db()->prepare(
        "SELECT 1 FROM playlists
          WHERE device_id = ? AND type = 'xtream'
            AND url = ? AND username = ? AND password = ?
          LIMIT 1"
    );
    $check->execute([$deviceId, $url, $username, $password]);
    if ($check->fetchColumn() !== false) {
        return; // ya vinculada
    }

    // Siguiente orden de inserción.
    $sortStmt = db()->prepare('SELECT COALESCE(MAX(sort), -1) + 1 FROM playlists WHERE device_id = ?');
    $sortStmt->execute([$deviceId]);
    $sort = (int) $sortStmt->fetchColumn();

    $ins = db()->prepare(
        'INSERT INTO playlists (device_id, name, type, url, username, password, protected, sort)
         VALUES (?, ?, ?, ?, ?, ?, ?, ?)'
    );
    $ins->execute([
        $deviceId,
        (string) ($match['name'] ?? 'Mi Lista'),
        'xtream',
        $url,
        $username,
        $password,
        !empty($match['protected']) ? 1 : 0,
        $sort,
    ]);
}
