<?php
/**
 * Klasa Database - obsługa SQLite3
 */

class Database
{
    private static ?Database $instance = null;
    private SQLite3 $db;

    private function __construct()
    {
        $dbPath = DB_PATH;
        $isNew = !file_exists($dbPath);
        
        // Upewnij się, że katalog istnieje
        $dir = dirname($dbPath);
        if (!is_dir($dir)) {
            mkdir($dir, 0755, true);
        }
        
        $this->db = new SQLite3($dbPath);
        $this->db->enableExceptions(true);
        $this->db->busyTimeout(5000);
        
        // WAL mode dla lepszej wydajności
        $this->db->exec('PRAGMA journal_mode = WAL');
        $this->db->exec('PRAGMA foreign_keys = ON');
        
        if ($isNew) {
            $this->createTables();
            $this->createDefaultAdmin();
        }
    }

    public static function getInstance(): Database
    {
        if (self::$instance === null) {
            self::$instance = new self();
        }
        return self::$instance;
    }

    public function getConnection(): SQLite3
    {
        return $this->db;
    }

    /**
     * Wykonaj zapytanie SELECT i zwróć wszystkie wyniki
     */
    public function fetchAll(string $sql, array $params = []): array
    {
        $stmt = $this->prepare($sql, $params);
        $result = $stmt->execute();
        
        $rows = [];
        while ($row = $result->fetchArray(SQLITE3_ASSOC)) {
            $rows[] = $row;
        }
        
        return $rows;
    }

    /**
     * Wykonaj zapytanie SELECT i zwróć pierwszy wynik
     */
    public function fetchOne(string $sql, array $params = []): ?array
    {
        $stmt = $this->prepare($sql, $params);
        $result = $stmt->execute();
        $row = $result->fetchArray(SQLITE3_ASSOC);
        
        return $row ?: null;
    }

    /**
     * Wykonaj zapytanie INSERT/UPDATE/DELETE
     */
    public function execute(string $sql, array $params = []): bool
    {
        $stmt = $this->prepare($sql, $params);
        return $stmt->execute() !== false;
    }

    /**
     * Wykonaj INSERT i zwróć ID
     */
    public function insert(string $sql, array $params = []): int
    {
        $this->execute($sql, $params);
        return $this->db->lastInsertRowID();
    }

    /**
     * Zwróć liczbę zmienionych wierszy
     */
    public function rowCount(): int
    {
        return $this->db->changes();
    }

    /**
     * Przygotuj zapytanie z parametrami
     */
    private function prepare(string $sql, array $params = []): SQLite3Stmt
    {
        $stmt = $this->db->prepare($sql);
        
        foreach ($params as $key => $value) {
            $paramName = is_int($key) ? $key + 1 : ':' . $key;
            
            if (is_int($value)) {
                $stmt->bindValue($paramName, $value, SQLITE3_INTEGER);
            } elseif (is_float($value)) {
                $stmt->bindValue($paramName, $value, SQLITE3_FLOAT);
            } elseif (is_null($value)) {
                $stmt->bindValue($paramName, null, SQLITE3_NULL);
            } else {
                $stmt->bindValue($paramName, $value, SQLITE3_TEXT);
            }
        }
        
        return $stmt;
    }

    /**
     * Tworzenie tabel
     */
    private function createTables(): void
    {
        $sql = <<<SQL
        -- Użytkownicy
        CREATE TABLE IF NOT EXISTS users (
            id INTEGER PRIMARY KEY AUTOINCREMENT,
            username VARCHAR(50) UNIQUE NOT NULL,
            password_hash VARCHAR(255) NOT NULL,
            role VARCHAR(20) DEFAULT 'user' CHECK(role IN ('admin', 'user')),
            device_limit INTEGER DEFAULT 3,
            is_active INTEGER DEFAULT 1,
            created_at DATETIME DEFAULT CURRENT_TIMESTAMP,
            updated_at DATETIME DEFAULT CURRENT_TIMESTAMP,
            last_login DATETIME NULL
        );

        -- Urządzenia
        CREATE TABLE IF NOT EXISTS devices (
            id INTEGER PRIMARY KEY AUTOINCREMENT,
            user_id INTEGER NOT NULL,
            mac VARCHAR(17) NOT NULL,
            code VARCHAR(100) NOT NULL,
            name VARCHAR(100) DEFAULT '',
            is_blocked INTEGER DEFAULT 0,
            created_at DATETIME DEFAULT CURRENT_TIMESTAMP,
            updated_at DATETIME DEFAULT CURRENT_TIMESTAMP,
            FOREIGN KEY (user_id) REFERENCES users(id) ON DELETE CASCADE,
            UNIQUE(mac, code)
        );

        -- Playlisty
        CREATE TABLE IF NOT EXISTS playlists (
            id INTEGER PRIMARY KEY AUTOINCREMENT,
            device_id INTEGER NOT NULL,
            type VARCHAR(20) NOT NULL CHECK(type IN ('xtream', 'm3u', 'stalker')),
            name VARCHAR(100) NOT NULL,
            config_json TEXT NOT NULL,
            update_date INTEGER NOT NULL,
            sort_order INTEGER DEFAULT 0,
            is_active INTEGER DEFAULT 1,
            created_at DATETIME DEFAULT CURRENT_TIMESTAMP,
            updated_at DATETIME DEFAULT CURRENT_TIMESTAMP,
            FOREIGN KEY (device_id) REFERENCES devices(id) ON DELETE CASCADE
        );

        -- Sesje
        CREATE TABLE IF NOT EXISTS sessions (
            id INTEGER PRIMARY KEY AUTOINCREMENT,
            user_id INTEGER NOT NULL,
            token VARCHAR(64) UNIQUE NOT NULL,
            ip VARCHAR(45) NOT NULL,
            user_agent TEXT,
            remember_me INTEGER DEFAULT 0,
            expires_at DATETIME NOT NULL,
            created_at DATETIME DEFAULT CURRENT_TIMESTAMP,
            last_activity DATETIME DEFAULT CURRENT_TIMESTAMP,
            FOREIGN KEY (user_id) REFERENCES users(id) ON DELETE CASCADE
        );

        -- Logi aplikacji
        CREATE TABLE IF NOT EXISTS logs (
            id INTEGER PRIMARY KEY AUTOINCREMENT,
            user_id INTEGER NULL,
            action VARCHAR(100) NOT NULL,
            details TEXT,
            ip VARCHAR(45),
            user_agent TEXT,
            created_at DATETIME DEFAULT CURRENT_TIMESTAMP,
            FOREIGN KEY (user_id) REFERENCES users(id) ON DELETE SET NULL
        );

        -- Logi API
        CREATE TABLE IF NOT EXISTS api_logs (
            id INTEGER PRIMARY KEY AUTOINCREMENT,
            device_id INTEGER NULL,
            mac VARCHAR(17),
            code VARCHAR(100),
            ip VARCHAR(45),
            response_status VARCHAR(20),
            playlists_count INTEGER DEFAULT 0,
            created_at DATETIME DEFAULT CURRENT_TIMESTAMP,
            FOREIGN KEY (device_id) REFERENCES devices(id) ON DELETE SET NULL
        );

        -- Rate limiting
        CREATE TABLE IF NOT EXISTS rate_limits (
            id INTEGER PRIMARY KEY AUTOINCREMENT,
            identifier VARCHAR(100) NOT NULL,
            action VARCHAR(50) NOT NULL,
            attempts INTEGER DEFAULT 1,
            first_attempt DATETIME DEFAULT CURRENT_TIMESTAMP,
            UNIQUE(identifier, action)
        );

        -- Notatki użytkowników
        CREATE TABLE IF NOT EXISTS notes (
            id INTEGER PRIMARY KEY AUTOINCREMENT,
            user_id INTEGER NOT NULL UNIQUE,
            content TEXT DEFAULT '',
            created_at DATETIME DEFAULT CURRENT_TIMESTAMP,
            updated_at DATETIME DEFAULT CURRENT_TIMESTAMP,
            FOREIGN KEY (user_id) REFERENCES users(id) ON DELETE CASCADE
        );

        -- Indeksy
        CREATE INDEX IF NOT EXISTS idx_devices_user_id ON devices(user_id);
        CREATE INDEX IF NOT EXISTS idx_devices_mac_code ON devices(mac, code);
        CREATE INDEX IF NOT EXISTS idx_playlists_device_id ON playlists(device_id);
        CREATE INDEX IF NOT EXISTS idx_sessions_token ON sessions(token);
        CREATE INDEX IF NOT EXISTS idx_sessions_user_id ON sessions(user_id);
        CREATE INDEX IF NOT EXISTS idx_logs_user_id ON logs(user_id);
        CREATE INDEX IF NOT EXISTS idx_logs_created_at ON logs(created_at);
        CREATE INDEX IF NOT EXISTS idx_api_logs_created_at ON api_logs(created_at);
        CREATE INDEX IF NOT EXISTS idx_api_logs_device_id ON api_logs(device_id);
        CREATE INDEX IF NOT EXISTS idx_notes_user_id ON notes(user_id);
SQL;

        $this->db->exec($sql);
    }

    /**
     * Tworzenie domyślnego administratora
     */
    private function createDefaultAdmin(): void
    {
        $username = 'admin';
        $password = 'admin123'; // Zmień po pierwszym logowaniu!
        $hash = password_hash($password, PASSWORD_BCRYPT, ['cost' => BCRYPT_COST]);
        
        $this->insert(
            "INSERT INTO users (username, password_hash, role, device_limit) VALUES (:username, :password_hash, 'admin', 999)",
            ['username' => $username, 'password_hash' => $hash]
        );
    }

    /**
     * Rozpocznij transakcję
     */
    public function beginTransaction(): bool
    {
        return $this->db->exec('BEGIN TRANSACTION');
    }

    /**
     * Zatwierdź transakcję
     */
    public function commit(): bool
    {
        return $this->db->exec('COMMIT');
    }

    /**
     * Cofnij transakcję
     */
    public function rollback(): bool
    {
        return $this->db->exec('ROLLBACK');
    }
}
