package org.timesafari.dailynotification

import android.content.Context
import androidx.room.*
import androidx.room.migration.Migration
import androidx.sqlite.db.SupportSQLiteDatabase
import org.timesafari.dailynotification.entities.NotificationContentEntity
import org.timesafari.dailynotification.entities.NotificationDeliveryEntity
import org.timesafari.dailynotification.entities.NotificationConfigEntity
import org.timesafari.dailynotification.dao.NotificationContentDao
import org.timesafari.dailynotification.dao.NotificationDeliveryDao
import org.timesafari.dailynotification.dao.NotificationConfigDao

/**
 * Unified SQLite schema for Daily Notification Plugin
 * 
 * This database consolidates both Kotlin and Java schemas into a single
 * unified database. Contains all entities needed for:
 * - Recurring schedule patterns (reboot recovery)
 * - Content caching (offline-first)
 * - Configuration management
 * - Delivery tracking and analytics
 * - Execution history
 * 
 * Database name: daily_notification_plugin.db
 * 
 * @author Matthew Raymer
 * @version 2.0.0 - Unified schema consolidation
 */
@Entity(tableName = "content_cache")
data class ContentCache(
    @PrimaryKey val id: String,
    val fetchedAt: Long,           // epoch ms
    val ttlSeconds: Int,
    val payload: ByteArray,        // BLOB
    val meta: String? = null,
    /** dual | daily | legacy — see [ContentCacheScope] */
    val cacheScope: String = ContentCacheScope.LEGACY
)

@Entity(tableName = "schedules")
data class Schedule(
    @PrimaryKey val id: String,
    val kind: String,              // 'fetch' or 'notify'
    val cron: String? = null,      // optional cron expression
    val clockTime: String? = null, // optional HH:mm
    val enabled: Boolean = true,
    val lastRunAt: Long? = null,
    val nextRunAt: Long? = null,
    val jitterMs: Int = 0,
    val backoffPolicy: String = "exp",
    val stateJson: String? = null,
    /** When > 0, next occurrence is this many minutes after current trigger (dev/testing). Null or 0 = 24h. */
    val rolloverIntervalMinutes: Int? = null
)

@Entity(tableName = "callbacks")
data class Callback(
    @PrimaryKey val id: String,
    val kind: String,              // 'http', 'local', 'queue'
    val target: String,            // url_or_local
    val headersJson: String? = null,
    val enabled: Boolean = true,
    val createdAt: Long
)

@Entity(tableName = "history")
data class History(
    @PrimaryKey(autoGenerate = true) val id: Int = 0,
    val refId: String,             // content or schedule id
    val kind: String,              // fetch/notify/callback
    val occurredAt: Long,
    val durationMs: Long? = null,
    val outcome: String,           // success|failure|skipped_ttl|circuit_open
    val diagJson: String? = null
)

@Database(
    entities = [
        // Kotlin entities (from original schema)
        ContentCache::class,
        Schedule::class,
        Callback::class,
        History::class,
        // Java entities (merged from Java database)
        NotificationContentEntity::class,
        NotificationDeliveryEntity::class,
        NotificationConfigEntity::class
    ],
    version = 5, // 5: isolate static reminder rows into kind="static_reminder" (was "notify")
    exportSchema = false
)
@TypeConverters(Converters::class)
abstract class DailyNotificationDatabase : RoomDatabase() {
    // Kotlin DAOs
    abstract fun contentCacheDao(): ContentCacheDao
    abstract fun scheduleDao(): ScheduleDao
    abstract fun callbackDao(): CallbackDao
    abstract fun historyDao(): HistoryDao
    
    // Java DAOs (for compatibility with existing Java code)
    abstract fun notificationContentDao(): NotificationContentDao
    abstract fun notificationDeliveryDao(): NotificationDeliveryDao
    abstract fun notificationConfigDao(): NotificationConfigDao
    
    companion object {
        @Volatile
        private var INSTANCE: DailyNotificationDatabase? = null
        
        private const val DATABASE_NAME = "daily_notification_plugin.db"
        
        /**
         * Get singleton instance of unified database
         * 
         * @param context Application context
         * @return Database instance
         */
        fun getDatabase(context: Context): DailyNotificationDatabase {
            return INSTANCE ?: synchronized(this) {
                val instance = Room.databaseBuilder(
                    context.applicationContext,
                    DailyNotificationDatabase::class.java,
                    DATABASE_NAME
                )
                .addMigrations(MIGRATION_1_2, MIGRATION_2_3, MIGRATION_3_4, MIGRATION_4_5)
                .addCallback(roomCallback)
                .build()
                INSTANCE = instance
                instance
            }
        }
        
        /**
         * Java-compatible static method (for existing Java code)
         * 
         * @param context Application context
         * @return Database instance
         */
        @JvmStatic
        fun getInstance(context: Context): DailyNotificationDatabase {
            return getDatabase(context)
        }
        
        /**
         * Room database callback for initialization
         */
        private val roomCallback = object : RoomDatabase.Callback() {
            override fun onCreate(db: SupportSQLiteDatabase) {
                super.onCreate(db)
                // Initialize default data if needed
            }
            
            override fun onOpen(db: SupportSQLiteDatabase) {
                super.onOpen(db)
                // Cleanup expired data on open
            }
        }
        
        /**
         * Migration from version 1 (Kotlin-only) to version 2 (unified)
         */
        val MIGRATION_1_2 = object : Migration(1, 2) {
            override fun migrate(database: SupportSQLiteDatabase) {
                // Create Java entity tables
                database.execSQL("""
                    CREATE TABLE IF NOT EXISTS notification_content (
                        id TEXT PRIMARY KEY NOT NULL,
                        plugin_version TEXT,
                        timesafari_did TEXT,
                        notification_type TEXT,
                        title TEXT,
                        body TEXT,
                        scheduled_time INTEGER NOT NULL,
                        timezone TEXT,
                        priority INTEGER NOT NULL,
                        vibration_enabled INTEGER NOT NULL,
                        sound_enabled INTEGER NOT NULL,
                        media_url TEXT,
                        encrypted_content TEXT,
                        encryption_key_id TEXT,
                        created_at INTEGER NOT NULL,
                        updated_at INTEGER NOT NULL,
                        ttl_seconds INTEGER NOT NULL,
                        delivery_status TEXT,
                        delivery_attempts INTEGER NOT NULL,
                        last_delivery_attempt INTEGER NOT NULL,
                        user_interaction_count INTEGER NOT NULL,
                        last_user_interaction INTEGER NOT NULL,
                        metadata TEXT
                    )
                """.trimIndent())
                
                database.execSQL("""
                    CREATE INDEX IF NOT EXISTS index_notification_content_timesafari_did 
                    ON notification_content(timesafari_did)
                """.trimIndent())
                
                database.execSQL("""
                    CREATE INDEX IF NOT EXISTS index_notification_content_notification_type 
                    ON notification_content(notification_type)
                """.trimIndent())
                
                database.execSQL("""
                    CREATE INDEX IF NOT EXISTS index_notification_content_scheduled_time 
                    ON notification_content(scheduled_time)
                """.trimIndent())
                
                database.execSQL("""
                    CREATE TABLE IF NOT EXISTS notification_delivery (
                        id TEXT PRIMARY KEY NOT NULL,
                        notification_id TEXT,
                        timesafari_did TEXT,
                        delivery_timestamp INTEGER NOT NULL,
                        delivery_status TEXT,
                        delivery_method TEXT,
                        delivery_attempt_number INTEGER NOT NULL,
                        delivery_duration_ms INTEGER NOT NULL,
                        user_interaction_type TEXT,
                        user_interaction_timestamp INTEGER NOT NULL,
                        user_interaction_duration_ms INTEGER NOT NULL,
                        error_code TEXT,
                        error_message TEXT,
                        device_info TEXT,
                        network_info TEXT,
                        battery_level INTEGER NOT NULL,
                        doze_mode_active INTEGER NOT NULL,
                        exact_alarm_permission INTEGER NOT NULL,
                        notification_permission INTEGER NOT NULL,
                        metadata TEXT,
                        FOREIGN KEY(notification_id) REFERENCES notification_content(id) ON DELETE CASCADE
                    )
                """.trimIndent())
                
                database.execSQL("""
                    CREATE INDEX IF NOT EXISTS index_notification_delivery_notification_id 
                    ON notification_delivery(notification_id)
                """.trimIndent())
                
                database.execSQL("""
                    CREATE INDEX IF NOT EXISTS index_notification_delivery_delivery_timestamp 
                    ON notification_delivery(delivery_timestamp)
                """.trimIndent())
                
                database.execSQL("""
                    CREATE TABLE IF NOT EXISTS notification_config (
                        id TEXT PRIMARY KEY NOT NULL,
                        timesafari_did TEXT,
                        config_type TEXT,
                        config_key TEXT,
                        config_value TEXT,
                        config_data_type TEXT,
                        is_encrypted INTEGER NOT NULL,
                        encryption_key_id TEXT,
                        created_at INTEGER NOT NULL,
                        updated_at INTEGER NOT NULL,
                        ttl_seconds INTEGER NOT NULL,
                        is_active INTEGER NOT NULL,
                        metadata TEXT
                    )
                """.trimIndent())
                
                database.execSQL("""
                    CREATE INDEX IF NOT EXISTS index_notification_config_timesafari_did 
                    ON notification_config(timesafari_did)
                """.trimIndent())
                
                database.execSQL("""
                    CREATE INDEX IF NOT EXISTS index_notification_config_config_type 
                    ON notification_config(config_type)
                """.trimIndent())
            }
        }

        /**
         * Migration from version 2 to 3: add rollover_interval_minutes to schedules
         */
        val MIGRATION_2_3 = object : Migration(2, 3) {
            override fun migrate(database: SupportSQLiteDatabase) {
                database.execSQL("ALTER TABLE schedules ADD COLUMN rollover_interval_minutes INTEGER")
            }
        }

        /** Add cacheScope to content_cache for dual vs daily isolation */
        val MIGRATION_3_4 = object : Migration(3, 4) {
            override fun migrate(database: SupportSQLiteDatabase) {
                database.execSQL(
                    "ALTER TABLE content_cache ADD COLUMN cacheScope TEXT NOT NULL DEFAULT 'legacy'"
                )
            }
        }

        /**
         * Isolate the static reminder's row into its own "static_reminder" kind, away from the
         * shared "notify" kind (see PLAN-simplify-static-and-refactor.md §3.1, §3.7).
         *
         * The static reminder's id is caller-supplied (TimeSafari uses "daily_timesafari_reminder";
         * the plugin defaults to "daily_notification" if none is given), so we can't match it by a
         * fixed id. Instead, reclassify by elimination: any existing kind="notify" row that does
         * NOT match one of the three other generators' known id prefixes
         * (dual_notify_<ts>, notify_<ts>, daily_rollover_<ts>) must be the static reminder, since
         * those are the only generators that ever wrote kind="notify" rows.
         */
        val MIGRATION_4_5 = object : Migration(4, 5) {
            override fun migrate(database: SupportSQLiteDatabase) {
                database.execSQL(
                    """
                    UPDATE schedules SET kind = 'static_reminder'
                    WHERE kind = 'notify'
                      AND id NOT LIKE 'dual_notify_%'
                      AND id NOT LIKE 'notify_%'
                      AND id NOT LIKE 'daily_rollover_%'
                    """.trimIndent()
                )
            }
        }
    }
}

@Dao
interface ContentCacheDao {
    @Query("SELECT * FROM content_cache WHERE id = :id")
    suspend fun getById(id: String): ContentCache?
    
    @Query("SELECT * FROM content_cache ORDER BY fetchedAt DESC LIMIT 1")
    suspend fun getLatest(): ContentCache?

    @Query("SELECT * FROM content_cache WHERE cacheScope = :scope ORDER BY fetchedAt DESC LIMIT 1")
    suspend fun getLatestByScope(scope: String): ContentCache?
    
    @Query("SELECT * FROM content_cache ORDER BY fetchedAt DESC LIMIT :limit")
    suspend fun getHistory(limit: Int): List<ContentCache>
    
    @Insert(onConflict = OnConflictStrategy.REPLACE)
    suspend fun upsert(contentCache: ContentCache)
    
    @Query("DELETE FROM content_cache WHERE fetchedAt < :cutoffTime")
    suspend fun deleteOlderThan(cutoffTime: Long)
    
    @Query("DELETE FROM content_cache")
    suspend fun deleteAll()
    
    @Query("SELECT COUNT(*) FROM content_cache")
    suspend fun getCount(): Int
}

@Dao
interface ScheduleDao {
    @Query("SELECT * FROM schedules WHERE enabled = 1")
    suspend fun getEnabled(): List<Schedule>
    
    @Query("SELECT * FROM schedules WHERE id = :id")
    suspend fun getById(id: String): Schedule?
    
    @Query("SELECT * FROM schedules")
    suspend fun getAll(): List<Schedule>
    
    @Query("SELECT * FROM schedules WHERE kind = :kind")
    suspend fun getByKind(kind: String): List<Schedule>
    
    @Query("SELECT * FROM schedules WHERE kind = :kind AND enabled = :enabled")
    suspend fun getByKindAndEnabled(kind: String, enabled: Boolean): List<Schedule>
    
    @Insert(onConflict = OnConflictStrategy.REPLACE)
    suspend fun upsert(schedule: Schedule)
    
    @Query("UPDATE schedules SET enabled = :enabled WHERE id = :id")
    suspend fun setEnabled(id: String, enabled: Boolean)
    
    @Query("UPDATE schedules SET lastRunAt = :lastRunAt, nextRunAt = :nextRunAt WHERE id = :id")
    suspend fun updateRunTimes(id: String, lastRunAt: Long?, nextRunAt: Long?)
    
    @Query("DELETE FROM schedules WHERE id = :id")
    suspend fun deleteById(id: String)
    
    @Query("UPDATE schedules SET enabled = :enabled, cron = :cron, clockTime = :clockTime, jitterMs = :jitterMs, backoffPolicy = :backoffPolicy, stateJson = :stateJson WHERE id = :id")
    suspend fun update(id: String, enabled: Boolean?, cron: String?, clockTime: String?, jitterMs: Int?, backoffPolicy: String?, stateJson: String?)
}

@Dao
interface CallbackDao {
    @Query("SELECT * FROM callbacks WHERE enabled = 1")
    suspend fun getEnabled(): List<Callback>
    
    @Query("SELECT * FROM callbacks")
    suspend fun getAll(): List<Callback>
    
    @Query("SELECT * FROM callbacks WHERE enabled = :enabled")
    suspend fun getByEnabled(enabled: Boolean): List<Callback>
    
    @Query("SELECT * FROM callbacks WHERE id = :id")
    suspend fun getById(id: String): Callback?
    
    @Insert(onConflict = OnConflictStrategy.REPLACE)
    suspend fun upsert(callback: Callback)
    
    @Query("UPDATE callbacks SET enabled = :enabled WHERE id = :id")
    suspend fun setEnabled(id: String, enabled: Boolean)
    
    @Query("UPDATE callbacks SET kind = :kind, target = :target, headersJson = :headersJson, enabled = :enabled WHERE id = :id")
    suspend fun update(id: String, kind: String?, target: String?, headersJson: String?, enabled: Boolean?)
    
    @Query("DELETE FROM callbacks WHERE id = :id")
    suspend fun deleteById(id: String)
}

@Dao
interface HistoryDao {
    @Insert
    suspend fun insert(history: History)
    
    @Query("SELECT * FROM history WHERE occurredAt >= :since ORDER BY occurredAt DESC")
    suspend fun getSince(since: Long): List<History>
    
    @Query("SELECT * FROM history WHERE occurredAt >= :since AND kind = :kind ORDER BY occurredAt DESC LIMIT :limit")
    suspend fun getSinceByKind(since: Long, kind: String, limit: Int): List<History>
    
    @Query("SELECT * FROM history ORDER BY occurredAt DESC LIMIT :limit")
    suspend fun getRecent(limit: Int): List<History>
    
    @Query("DELETE FROM history WHERE occurredAt < :cutoffTime")
    suspend fun deleteOlderThan(cutoffTime: Long)
    
    @Query("SELECT COUNT(*) FROM history")
    suspend fun getCount(): Int
}

class Converters {
    @TypeConverter
    fun fromByteArray(value: ByteArray?): String? {
        return value?.let { String(it) }
    }
    
    @TypeConverter
    fun toByteArray(value: String?): ByteArray? {
        return value?.toByteArray()
    }
}
