/*
 * Database.vala
 * 
 * Core database connection and migration management for RSSuper Linux.
 * Uses SQLite with FTS5 for full-text search capabilities.
 */

/**
 * Database - Manages SQLite database connection and migrations
 */
public class RSSuper.Database : Object {
    private Sqlite.Database db;
    private string db_path;
    
    /**
     * Current database schema version
     */
    public const int CURRENT_VERSION = 1;
    
    /**
     * Signal emitted when database is ready
     */
    public signal void ready();
    
    /**
     * Signal emitted on error
     */
    public signal void error(string message);
    
    /**
     * Create a new database connection
     * 
     * @param db_path Path to the SQLite database file
     */
    public Database(string db_path) throws Error {
        this.db_path = db_path;
        this.open();
        this.migrate();
    }
    
    /**
     * Open database connection
     */
    private void open() throws Error {
        var file = File.new_for_path(db_path);
        var parent = file.get_parent();
        if (parent != null && !parent.query_exists()) {
            try {
                parent.make_directory_with_parents();
            } catch (Error e) {
                throw new DBError.FAILED("Failed to create database directory: %s", e.message);
            }
        }
        
        int result = Sqlite.Database.open(db_path, out db);
        if (result != Sqlite.OK) {
            throw new DBError.FAILED("Failed to open database: %s".printf(db.errmsg()));
        }
        
        execute("PRAGMA foreign_keys = ON;");
        execute("PRAGMA journal_mode = WAL;");
        
        debug("Database opened: %s", db_path);
    }
    
    /**
     * Run database migrations
     */
    private void migrate() throws Error {
        // Create schema_migrations table if not exists
        execute("CREATE TABLE IF NOT EXISTS schema_migrations (version INTEGER PRIMARY KEY, applied_at TEXT NOT NULL DEFAULT (datetime('now')));");
        
        // Create feed_subscriptions table
        execute("CREATE TABLE IF NOT EXISTS feed_subscriptions (id TEXT PRIMARY KEY, url TEXT NOT NULL UNIQUE, title TEXT NOT NULL, category TEXT, enabled INTEGER NOT NULL DEFAULT 1, fetch_interval INTEGER NOT NULL DEFAULT 60, created_at TEXT NOT NULL, updated_at TEXT NOT NULL, last_fetched_at TEXT, next_fetch_at TEXT, error TEXT, http_auth_username TEXT, http_auth_password TEXT);");
        
        // Create feed_items table
        execute("CREATE TABLE IF NOT EXISTS feed_items (id TEXT PRIMARY KEY, subscription_id TEXT NOT NULL, title TEXT NOT NULL, link TEXT, description TEXT, content TEXT, author TEXT, published TEXT, updated TEXT, categories TEXT, enclosure_url TEXT, enclosure_type TEXT, enclosure_length TEXT, guid TEXT, is_read INTEGER NOT NULL DEFAULT 0, is_starred INTEGER NOT NULL DEFAULT 0, created_at TEXT NOT NULL DEFAULT (datetime('now')), FOREIGN KEY (subscription_id) REFERENCES feed_subscriptions(id) ON DELETE CASCADE);");
        
        // Create indexes for feed_items
        execute("CREATE INDEX IF NOT EXISTS idx_feed_items_subscription ON feed_items(subscription_id);");
        execute("CREATE INDEX IF NOT EXISTS idx_feed_items_published ON feed_items(published DESC);");
        execute("CREATE INDEX IF NOT EXISTS idx_feed_items_read ON feed_items(is_read);");
        execute("CREATE INDEX IF NOT EXISTS idx_feed_items_starred ON feed_items(is_starred);");
        
        // Create search_history table
        execute("CREATE TABLE IF NOT EXISTS search_history (id INTEGER PRIMARY KEY AUTOINCREMENT, query TEXT NOT NULL, filters_json TEXT, sort_option TEXT NOT NULL DEFAULT 'relevance', page INTEGER NOT NULL DEFAULT 1, page_size INTEGER NOT NULL DEFAULT 20, result_count INTEGER, created_at TEXT NOT NULL DEFAULT (datetime('now')));");
        execute("CREATE INDEX IF NOT EXISTS idx_search_history_created ON search_history(created_at DESC);");
        
        // Create FTS5 virtual table
        execute("CREATE VIRTUAL TABLE IF NOT EXISTS feed_items_fts USING fts5(title, description, content, author, content='feed_items', content_rowid='rowid');");
        
        // Create triggers for FTS sync
        execute("CREATE TRIGGER IF NOT EXISTS feed_items_ai AFTER INSERT ON feed_items BEGIN INSERT INTO feed_items_fts(rowid, title, description, content, author) VALUES (new.rowid, new.title, new.description, new.content, new.author); END;");
        execute("CREATE TRIGGER IF NOT EXISTS feed_items_ad AFTER DELETE ON feed_items BEGIN INSERT INTO feed_items_fts(feed_items_fts, rowid, title, description, content, author) VALUES('delete', old.rowid, old.title, old.description, old.content, old.author); END;");
        execute("CREATE TRIGGER IF NOT EXISTS feed_items_au AFTER UPDATE ON feed_items BEGIN INSERT INTO feed_items_fts(feed_items_fts, rowid, title, description, content, author) VALUES('delete', old.rowid, old.title, old.description, old.content, old.author); INSERT INTO feed_items_fts(rowid, title, description, content, author) VALUES (new.rowid, new.title, new.description, new.content, new.author); END;");
        
        // Record migration
        execute("INSERT OR REPLACE INTO schema_migrations (version, applied_at) VALUES (" + CURRENT_VERSION.to_string() + ", datetime('now'));");
        
        debug("Database migrated to version %d", CURRENT_VERSION);
    }
    
    /**
     * Get current migration version
     */
    private int get_current_version() throws Error {
        try {
            Sqlite.Statement stmt;
            int result = db.prepare_v2("SELECT COALESCE(MAX(version), 0) FROM schema_migrations;", -1, out stmt, null);
            
            if (result != Sqlite.OK) {
                throw new DBError.FAILED("Failed to prepare statement: %s".printf(db.errmsg()));
            }
            
            int version = 0;
            if (stmt.step() == Sqlite.ROW) {
                version = stmt.column_int(0);
            }
            
            return version;
            
        } catch (Error e) {
            throw new DBError.FAILED("Failed to get migration version: %s".printf(e.message));
        }
    }
    
    /**
     * Execute a SQL statement
     */
    public void execute(string sql) throws Error {
        string? errmsg;
        int result = db.exec(sql, null, out errmsg);
        
        if (result != Sqlite.OK) {
            throw new DBError.FAILED("SQL execution failed: %s\nSQL: %s".printf(errmsg, sql));
        }
    }
    
    /**
     * Prepare a SQL statement
     */
    public Sqlite.Statement prepare(string sql) throws Error {
        Sqlite.Statement stmt;
        int result = db.prepare_v2(sql, -1, out stmt, null);
        
        if (result != Sqlite.OK) {
            throw new DBError.FAILED("Failed to prepare statement: %s\nSQL: %s".printf(db.errmsg(), sql));
        }
        
        return stmt;
    }
    
    /**
     * Get the database connection handle
     */
    public unowned Sqlite.Database get_handle() {
        return db;
    }
    
    /**
     * Close database connection
     */
    public void close() {
        if (db != null) {
            db = null;
            debug("Database closed: %s", db_path);
        }
    }
    
    /**
     * Begin a transaction
     */
    public void begin_transaction() throws Error {
        execute("BEGIN TRANSACTION;");
    }
    
    /**
     * Commit a transaction
     */
    public void commit() throws Error {
        execute("COMMIT;");
    }
    
    /**
     * Rollback a transaction
     */
    public void rollback() throws Error {
        execute("ROLLBACK;");
    }

    /* Helper to convert GLib.List to array */
    private T[] toArray<T>(GLib.List<T> list) {
        T[] arr = {};
        for (unowned var node = list; node != null; node = node.next) {
            arr += node.data;
        }
        return arr;
    }

}
