/**
 * DailyNotificationDatabase.swift
 * 
 * iOS SQLite database management for daily notifications
 * 
 * @author Matthew Raymer
 * @version 1.0.0
 */

import Foundation
import SQLite3

/**
 * SQLite database manager for daily notifications on iOS
 * 
 * This class manages the SQLite database with the three-table schema:
 * - notif_contents: keep history, fast newest-first reads
 * - notif_deliveries: track many deliveries per slot/time
 * - notif_config: generic configuration KV
 */
class DailyNotificationDatabase {
    
    // MARK: - Constants
    
    private static let TAG = "DailyNotificationDatabase"
    
    // Table names
    static let TABLE_NOTIF_CONTENTS = "notif_contents"
    static let TABLE_NOTIF_DELIVERIES = "notif_deliveries"
    static let TABLE_NOTIF_CONFIG = "notif_config"
    
    // Column names
    static let COL_CONTENTS_ID = "id"
    static let COL_CONTENTS_SLOT_ID = "slot_id"
    static let COL_CONTENTS_PAYLOAD_JSON = "payload_json"
    static let COL_CONTENTS_FETCHED_AT = "fetched_at"
    static let COL_CONTENTS_ETAG = "etag"
    
    static let COL_DELIVERIES_ID = "id"
    static let COL_DELIVERIES_SLOT_ID = "slot_id"
    static let COL_DELIVERIES_FIRE_AT = "fire_at"
    static let COL_DELIVERIES_DELIVERED_AT = "delivered_at"
    static let COL_DELIVERIES_STATUS = "status"
    static let COL_DELIVERIES_ERROR_CODE = "error_code"
    static let COL_DELIVERIES_ERROR_MESSAGE = "error_message"
    
    static let COL_CONFIG_K = "k"
    static let COL_CONFIG_V = "v"
    
    // Status values
    static let STATUS_SCHEDULED = "scheduled"
    static let STATUS_SHOWN = "shown"
    static let STATUS_ERROR = "error"
    static let STATUS_CANCELED = "canceled"
    
    // MARK: - Properties
    
    private var db: OpaquePointer?
    private let path: String
    
    // MARK: - Initialization
    
    /**
     * Initialize database with path
     * 
     * @param path Database file path
     */
    init(path: String) {
        self.path = path
        openDatabase()
    }
    
    /**
     * Open database connection
     */
    private func openDatabase() {
        if sqlite3_open(path, &db) == SQLITE_OK {
            print("\(Self.TAG): Database opened successfully at \(path)")
            createTables()
            configureDatabase()
        } else {
            print("\(Self.TAG): Error opening database: \(String(cString: sqlite3_errmsg(db)))")
        }
    }
    
    /**
     * Create database tables
     */
    private func createTables() {
        // Create notif_contents table
        let createContentsTable = """
            CREATE TABLE IF NOT EXISTS \(Self.TABLE_NOTIF_CONTENTS)(
                \(Self.COL_CONTENTS_ID) INTEGER PRIMARY KEY AUTOINCREMENT,
                \(Self.COL_CONTENTS_SLOT_ID) TEXT NOT NULL,
                \(Self.COL_CONTENTS_PAYLOAD_JSON) TEXT NOT NULL,
                \(Self.COL_CONTENTS_FETCHED_AT) INTEGER NOT NULL,
                \(Self.COL_CONTENTS_ETAG) TEXT,
                UNIQUE(\(Self.COL_CONTENTS_SLOT_ID), \(Self.COL_CONTENTS_FETCHED_AT))
            );
        """
        
        // Create notif_deliveries table
        let createDeliveriesTable = """
            CREATE TABLE IF NOT EXISTS \(Self.TABLE_NOTIF_DELIVERIES)(
                \(Self.COL_DELIVERIES_ID) INTEGER PRIMARY KEY AUTOINCREMENT,
                \(Self.COL_DELIVERIES_SLOT_ID) TEXT NOT NULL,
                \(Self.COL_DELIVERIES_FIRE_AT) INTEGER NOT NULL,
                \(Self.COL_DELIVERIES_DELIVERED_AT) INTEGER,
                \(Self.COL_DELIVERIES_STATUS) TEXT NOT NULL DEFAULT '\(Self.STATUS_SCHEDULED)',
                \(Self.COL_DELIVERIES_ERROR_CODE) TEXT,
                \(Self.COL_DELIVERIES_ERROR_MESSAGE) TEXT
            );
        """
        
        // Create notif_config table
        let createConfigTable = """
            CREATE TABLE IF NOT EXISTS \(Self.TABLE_NOTIF_CONFIG)(
                \(Self.COL_CONFIG_K) TEXT PRIMARY KEY,
                \(Self.COL_CONFIG_V) TEXT NOT NULL
            );
        """
        
        // Create indexes
        let createContentsIndex = """
            CREATE INDEX IF NOT EXISTS notif_idx_contents_slot_time 
            ON \(Self.TABLE_NOTIF_CONTENTS)(\(Self.COL_CONTENTS_SLOT_ID), \(Self.COL_CONTENTS_FETCHED_AT) DESC);
        """
        
        // Execute table creation
        executeSQL(createContentsTable)
        executeSQL(createDeliveriesTable)
        executeSQL(createConfigTable)
        executeSQL(createContentsIndex)
        
        print("\(Self.TAG): Database tables created successfully")
    }
    
    /**
     * Configure database settings
     */
    private func configureDatabase() {
        // Enable WAL mode
        executeSQL("PRAGMA journal_mode=WAL")
        
        // Set synchronous mode
        executeSQL("PRAGMA synchronous=NORMAL")
        
        // Set busy timeout
        executeSQL("PRAGMA busy_timeout=5000")
        
        // Enable foreign keys
        executeSQL("PRAGMA foreign_keys=ON")
        
        // Set user version
        executeSQL("PRAGMA user_version=1")
        
        print("\(Self.TAG): Database configured successfully")
    }
    
    /**
     * Execute SQL statement
     * 
     * @param sql SQL statement to execute
     */
    func executeSQL(_ sql: String) {
        var statement: OpaquePointer?
        
        if sqlite3_prepare_v2(db, sql, -1, &statement, nil) == SQLITE_OK {
            if sqlite3_step(statement) == SQLITE_DONE {
                print("\(Self.TAG): SQL executed successfully: \(sql)")
            } else {
                print("\(Self.TAG): SQL execution failed: \(String(cString: sqlite3_errmsg(db)))")
            }
        } else {
            print("\(Self.TAG): SQL preparation failed: \(String(cString: sqlite3_errmsg(db)))")
        }
        
        sqlite3_finalize(statement)
    }
    
    /**
     * Query SQL and return integer result
     * 
     * @param sql SQL query statement
     * @return Integer result or nil if query fails
     */
    func queryInt(_ sql: String) -> Int? {
        var statement: OpaquePointer?
        var result: Int? = nil
        
        if sqlite3_prepare_v2(db, sql, -1, &statement, nil) == SQLITE_OK {
            if sqlite3_step(statement) == SQLITE_ROW {
                result = Int(sqlite3_column_int(statement, 0))
            }
        } else {
            print("\(Self.TAG): Query preparation failed: \(String(cString: sqlite3_errmsg(db)))")
        }
        
        sqlite3_finalize(statement)
        return result
    }
    
    // MARK: - Public Methods
    
    /**
     * Close database connection
     */
    func close() {
        if sqlite3_close(db) == SQLITE_OK {
            print("\(Self.TAG): Database closed successfully")
        } else {
            print("\(Self.TAG): Error closing database: \(String(cString: sqlite3_errmsg(db)))")
        }
    }
    
    /**
     * Get database path
     * 
     * @return Database file path
     */
    func getPath() -> String {
        return path
    }
    
    /**
     * Check if database is open
     * 
     * @return true if database is open
     */
    func isOpen() -> Bool {
        return db != nil
    }
    
    /**
     * Save notification content to database
     * 
     * @param content Notification content to save
     */
    func saveNotificationContent(_ content: NotificationContent) {
        do {
            guard db != nil else {
                print("\(Self.TAG): DB not open; cannot saveNotificationContent for \(content.id)")
                return
            }

            let encoder = JSONEncoder()
            let data = try encoder.encode(content)
            guard let json = String(data: data, encoding: .utf8) else {
                print("\(Self.TAG): Failed to encode NotificationContent to UTF-8 JSON for \(content.id)")
                return
            }

            let sql = """
            INSERT OR REPLACE INTO \(Self.TABLE_NOTIF_CONTENTS)
            (\(Self.COL_CONTENTS_SLOT_ID), \(Self.COL_CONTENTS_PAYLOAD_JSON), \(Self.COL_CONTENTS_FETCHED_AT), \(Self.COL_CONTENTS_ETAG))
            VALUES (?, ?, ?, ?);
            """

            var stmt: OpaquePointer?
            if sqlite3_prepare_v2(db, sql, -1, &stmt, nil) != SQLITE_OK {
                print("\(Self.TAG): saveNotificationContent prepare failed: \(String(cString: sqlite3_errmsg(db)))")
                sqlite3_finalize(stmt)
                return
            }

            sqlite3_bind_text(stmt, 1, (content.id as NSString).utf8String, -1, nil)
            sqlite3_bind_text(stmt, 2, (json as NSString).utf8String, -1, nil)
            sqlite3_bind_int64(stmt, 3, sqlite3_int64(content.fetchedAt))

            if let etag = content.etag {
                sqlite3_bind_text(stmt, 4, (etag as NSString).utf8String, -1, nil)
            } else {
                sqlite3_bind_null(stmt, 4)
            }

            if sqlite3_step(stmt) != SQLITE_DONE {
                print("\(Self.TAG): saveNotificationContent step failed: \(String(cString: sqlite3_errmsg(db)))")
            } else {
                print("\(Self.TAG): Saved notification content: slot=\(content.id) fetched_at=\(content.fetchedAt)")
            }

            sqlite3_finalize(stmt)

        } catch {
            print("\(Self.TAG): saveNotificationContent error for \(content.id): \(error)")
        }
    }
    
    /**
     * Delete notification content from database
     * 
     * @param id Notification ID
     */
    func deleteNotificationContent(id: String) {
        do {
            guard db != nil else {
                print("\(Self.TAG): DB not open; cannot deleteNotificationContent for \(id)")
                return
            }

            let sql = """
            DELETE FROM \(Self.TABLE_NOTIF_CONTENTS)
            WHERE \(Self.COL_CONTENTS_SLOT_ID) = ?;
            """

            var stmt: OpaquePointer?
            if sqlite3_prepare_v2(db, sql, -1, &stmt, nil) != SQLITE_OK {
                print("\(Self.TAG): deleteNotificationContent prepare failed: \(String(cString: sqlite3_errmsg(db)))")
                sqlite3_finalize(stmt)
                return
            }

            sqlite3_bind_text(stmt, 1, (id as NSString).utf8String, -1, nil)

            if sqlite3_step(stmt) != SQLITE_DONE {
                print("\(Self.TAG): deleteNotificationContent step failed: \(String(cString: sqlite3_errmsg(db)))")
            } else {
                print("\(Self.TAG): Deleted notification content rows for slot=\(id)")
            }

            sqlite3_finalize(stmt)

        } catch {
            print("\(Self.TAG): deleteNotificationContent error for \(id): \(error)")
        }
    }
    
    /**
     * Clear all notifications from database
     */
    func clearAllNotifications() {
        do {
            guard db != nil else {
                print("\(Self.TAG): DB not open; cannot clearAllNotifications")
                return
            }

            executeSQL("DELETE FROM \(Self.TABLE_NOTIF_CONTENTS);")
            executeSQL("DELETE FROM \(Self.TABLE_NOTIF_DELIVERIES);")

            print("\(Self.TAG): Cleared all notifications (contents + deliveries)")

        } catch {
            print("\(Self.TAG): clearAllNotifications error: \(error)")
        }
    }
}
