/**
 * TestDBFactory.kt
 * 
 * Test database factory for Android DailyNotification plugin recovery testing
 * Provides utilities to create test databases with intentionally invalid/corrupt data
 * for testing recovery scenarios.
 * 
 * Similar to iOS TestDBFactory.swift, but uses Room in-memory databases
 * 
 * @author Matthew Raymer
 * @version 1.0.0
 * @since 2025-12-22
 */

package org.timesafari.dailynotification

import android.content.Context
import androidx.room.Room
import androidx.room.RoomDatabase
import kotlinx.coroutines.runBlocking
import java.util.UUID

/**
 * Test database factory for recovery testing
 * 
 * Provides utilities to create test databases with intentionally invalid/corrupt data
 * for testing recovery scenarios.
 */
object TestDBFactory {
    
    /**
     * Create an in-memory test database
     * 
     * Uses Room.inMemoryDatabaseBuilder() for isolation between tests.
     * Each test gets a fresh database instance.
     * 
     * @param context Application context (can be mock/test context)
     * @return In-memory database instance
     */
    fun createInMemoryDatabase(context: Context): DailyNotificationDatabase {
        return Room.inMemoryDatabaseBuilder(
            context,
            DailyNotificationDatabase::class.java
        )
        .allowMainThreadQueries()  // Allow synchronous queries for testing
        .build()
    }
    
    /**
     * Inject invalid schedule record into database
     * 
     * Creates a schedule with empty ID or null required fields to test
     * recovery's ability to handle invalid data gracefully.
     * 
     * @param database Database instance
     * @param id Schedule ID (can be empty for invalid test)
     * @param nextRunAt Next run time (can be null or invalid)
     * @param kind Schedule kind (can be invalid)
     */
    fun injectInvalidSchedule(
        database: DailyNotificationDatabase,
        id: String = "",
        nextRunAt: Long? = null,
        kind: String = "notify"
    ) {
        val schedule = Schedule(
            id = id,
            kind = kind,
            cron = null,
            clockTime = null,
            enabled = true,
            lastRunAt = null,
            nextRunAt = nextRunAt,
            jitterMs = 0,
            backoffPolicy = "exp",
            stateJson = null
        )
        
        runBlocking {
            try {
                database.scheduleDao().upsert(schedule)
                println("TestDBFactory: Injected invalid schedule: id='$id', nextRunAt=$nextRunAt")
            } catch (e: Exception) {
                println("TestDBFactory: Failed to inject invalid schedule: ${e.message}")
            }
        }
    }
    
    /**
     * Inject schedule with null/empty required fields
     * 
     * Tests recovery's ability to handle null fields gracefully.
     */
    fun injectScheduleWithNullFields(database: DailyNotificationDatabase) {
        injectInvalidSchedule(
            database = database,
            id = "",
            nextRunAt = null,
            kind = ""
        )
    }
    
    /**
     * Inject duplicate schedule records (same ID, different times)
     * 
     * Creates multiple schedule entries with the same ID but different
     * nextRunAt times to test duplicate delivery deduplication.
     * 
     * @param database Database instance
     * @param id Schedule ID (same for all duplicates)
     * @param times List of nextRunAt times (one per duplicate)
     * @param kind Schedule kind
     */
    fun injectDuplicateSchedules(
        database: DailyNotificationDatabase,
        id: String,
        times: List<Long>,
        kind: String = "notify"
    ) {
        runBlocking {
            times.forEach { time ->
                val schedule = Schedule(
                    id = id,
                    kind = kind,
                    cron = null,
                    clockTime = null,
                    enabled = true,
                    lastRunAt = null,
                    nextRunAt = time,
                    jitterMs = 0,
                    backoffPolicy = "exp",
                    stateJson = null
                )
                
                try {
                    // Use upsert to allow overwriting (for testing duplicate delivery scenarios)
                    database.scheduleDao().upsert(schedule)
                    println("TestDBFactory: Injected duplicate schedule: id='$id', nextRunAt=$time")
                } catch (e: Exception) {
                    // Room will throw on duplicate primary key - this is expected
                    // For testing duplicate delivery, we need to use delivery records instead
                    println("TestDBFactory: Duplicate schedule insert failed (expected): ${e.message}")
                }
            }
        }
    }
    
    /**
     * Inject schedule at DST boundary
     * 
     * Creates a schedule with nextRunAt at a DST transition time
     * to test recovery's handling of DST boundary transitions.
     * 
     * @param database Database instance
     * @param id Schedule ID
     * @param dstBoundaryTime Time at DST boundary (epoch ms)
     * @param kind Schedule kind
     */
    fun injectDSTBoundarySchedule(
        database: DailyNotificationDatabase,
        id: String,
        dstBoundaryTime: Long,
        kind: String = "notify"
    ) {
        val schedule = Schedule(
            id = id,
            kind = kind,
            cron = null,
            clockTime = null,
            enabled = true,
            lastRunAt = null,
            nextRunAt = dstBoundaryTime,
            jitterMs = 0,
            backoffPolicy = "exp",
            stateJson = null
        )
        
        runBlocking {
            try {
                database.scheduleDao().upsert(schedule)
                println("TestDBFactory: Injected DST boundary schedule: id='$id', time=$dstBoundaryTime")
            } catch (e: Exception) {
                println("TestDBFactory: Failed to inject DST boundary schedule: ${e.message}")
            }
        }
    }
    
    /**
     * Inject past schedule (already delivered, needs rollover)
     * 
     * Creates a schedule with nextRunAt in the past to test
     * rollover recovery scenarios.
     * 
     * @param database Database instance
     * @param id Schedule ID
     * @param pastTime Time in the past (epoch ms)
     * @param kind Schedule kind
     */
    fun injectPastSchedule(
        database: DailyNotificationDatabase,
        id: String,
        pastTime: Long,
        kind: String = "notify"
    ) {
        val schedule = Schedule(
            id = id,
            kind = kind,
            cron = null,
            clockTime = null,
            enabled = true,
            lastRunAt = null,
            nextRunAt = pastTime,
            jitterMs = 0,
            backoffPolicy = "exp",
            stateJson = null
        )
        
        runBlocking {
            try {
                database.scheduleDao().upsert(schedule)
                println("TestDBFactory: Injected past schedule: id='$id', time=$pastTime")
            } catch (e: Exception) {
                println("TestDBFactory: Failed to inject past schedule: ${e.message}")
            }
        }
    }
    
    /**
     * Clear all schedules from database
     * 
     * Useful for test cleanup between scenarios.
     * 
     * @param database Database instance
     */
    fun clearAllSchedules(database: DailyNotificationDatabase) {
        runBlocking {
            try {
                val allSchedules = database.scheduleDao().getAll()
                allSchedules.forEach { schedule ->
                    database.scheduleDao().deleteById(schedule.id)
                }
                println("TestDBFactory: Cleared all schedules")
            } catch (e: Exception) {
                println("TestDBFactory: Failed to clear schedules: ${e.message}")
            }
        }
    }
}

