package dev.privacyllc.period.core.database.dao

import androidx.room.Dao
import androidx.room.Delete
import androidx.room.Insert
import androidx.room.OnConflictStrategy
import androidx.room.Query
import androidx.room.Update
import dev.privacyllc.period.core.database.entity.NotYetObservationEntity
import dev.privacyllc.period.core.database.entity.PeriodRecordEntity
import dev.privacyllc.period.core.database.entity.PredictionRecordEntity
import dev.privacyllc.period.core.database.entity.SpottingRecordEntity
import kotlinx.coroutines.flow.Flow
import java.time.LocalDate

/**
 * Reads return [Flow]. A one-shot read is a snapshot that is already stale when
 * the UI renders it, and the whole point of this product is that logging a
 * period updates the forecast on screen without anybody refreshing anything.
 *
 * Note there is no `deleteAll`-by-convenience anywhere except the explicit
 * `deleteEverything` on the database itself: Delete My Data is an irreversible
 * operation the user confirms, not something a DAO offers casually.
 */
@Dao
interface PeriodRecordDao {

    @Query("SELECT * FROM period_records WHERE isConfirmed = 1 ORDER BY startDate ASC")
    fun observeConfirmed(): Flow<List<PeriodRecordEntity>>

    @Query("SELECT * FROM period_records ORDER BY startDate ASC")
    fun observeAll(): Flow<List<PeriodRecordEntity>>

    @Query("SELECT * FROM period_records WHERE isConfirmed = 1 ORDER BY startDate DESC LIMIT 1")
    fun observeLatestConfirmed(): Flow<PeriodRecordEntity?>

    @Query("SELECT * FROM period_records WHERE id = :id")
    suspend fun byId(id: Long): PeriodRecordEntity?

    @Query("SELECT * FROM period_records WHERE startDate = :date LIMIT 1")
    suspend fun byStartDate(date: LocalDate): PeriodRecordEntity?

    /**
     * ABORT, not REPLACE. A duplicate start date is a mistake the caller has to
     * see: REPLACE would silently delete the existing row — including its
     * `createdAt` and its `source` — and health history is not something this
     * app overwrites without saying so (§14).
     */
    @Insert(onConflict = OnConflictStrategy.ABORT)
    suspend fun insert(record: PeriodRecordEntity): Long

    @Update
    suspend fun update(record: PeriodRecordEntity)

    @Delete
    suspend fun delete(record: PeriodRecordEntity)

    @Query("DELETE FROM period_records WHERE id = :id")
    suspend fun deleteById(id: Long)

    @Query("SELECT COUNT(*) FROM period_records WHERE isConfirmed = 1")
    suspend fun confirmedCount(): Int

    /**
     * Just the dates, for the prediction engine.
     *
     * The engine takes `List<LocalDate>` and nothing else — it is a pure-JVM
     * module that has never heard of a row. Selecting one column rather than
     * mapping whole entities keeps that honest and cheap.
     */
    @Query("SELECT startDate FROM period_records WHERE isConfirmed = 1 ORDER BY startDate ASC")
    suspend fun confirmedStartDates(): List<LocalDate>
}

@Dao
interface SpottingRecordDao {

    @Query("SELECT * FROM spotting_records ORDER BY date ASC")
    fun observeAll(): Flow<List<SpottingRecordEntity>>

    @Insert(onConflict = OnConflictStrategy.IGNORE)
    suspend fun insert(record: SpottingRecordEntity): Long

    @Query("DELETE FROM spotting_records WHERE date = :date")
    suspend fun deleteByDate(date: LocalDate)
}

@Dao
interface PredictionRecordDao {

    @Query("SELECT * FROM prediction_records ORDER BY generatedAt DESC LIMIT 1")
    fun observeLatest(): Flow<PredictionRecordEntity?>

    /** Scored predictions only — the ones whose outcome is known. §16's accuracy figures read this. */
    @Query("SELECT * FROM prediction_records WHERE actualStartDate IS NOT NULL ORDER BY generatedAt DESC LIMIT :limit")
    fun observeScored(limit: Int = 12): Flow<List<PredictionRecordEntity>>

    @Query("SELECT * FROM prediction_records WHERE actualStartDate IS NULL ORDER BY generatedAt DESC")
    suspend fun unscored(): List<PredictionRecordEntity>

    @Query("SELECT id FROM prediction_records ORDER BY generatedAt DESC LIMIT 1")
    suspend fun latestId(): Long?

    /** Newest first, for the engine's window. Only forecasts whose outcome is known. */
    @Query(
        "SELECT absoluteErrorDays FROM prediction_records " +
            "WHERE absoluteErrorDays IS NOT NULL ORDER BY generatedAt DESC LIMIT :limit",
    )
    suspend fun recentAbsoluteErrors(limit: Int = 12): List<Int>

    /**
     * Throw away forecasts that were replaced before their outcome was known.
     *
     * A superseded forecast is not a wrong one — nobody was ever shown it when
     * the period arrived. Keeping them would let a single cycle contribute
     * several "errors" to §16's figures, which is the difference between
     * measuring accuracy and inflating a sample.
     */
    @Query("DELETE FROM prediction_records WHERE actualStartDate IS NULL")
    suspend fun deleteUnscored()

    @Insert(onConflict = OnConflictStrategy.ABORT)
    suspend fun insert(record: PredictionRecordEntity): Long

    /**
     * The ONLY permitted mutation of a snapshot: recording what actually
     * happened. Deliberately not an `@Update` of the whole row, so a caller
     * cannot rewrite what was predicted after learning the answer.
     */
    @Query("UPDATE prediction_records SET actualStartDate = :actual, absoluteErrorDays = :errorDays WHERE id = :id")
    suspend fun score(id: Long, actual: LocalDate, errorDays: Int)
}

@Dao
interface NotYetObservationDao {

    @Query("SELECT * FROM not_yet_observations ORDER BY date ASC")
    fun observeAll(): Flow<List<NotYetObservationEntity>>

    @Query("SELECT * FROM not_yet_observations WHERE date >= :since ORDER BY date ASC")
    suspend fun since(since: LocalDate): List<NotYetObservationEntity>

    @Insert(onConflict = OnConflictStrategy.IGNORE)
    suspend fun insert(observation: NotYetObservationEntity): Long

    /** Cleared when a period is confirmed: they censored a forecast that is now resolved. */
    @Query("DELETE FROM not_yet_observations WHERE date < :before")
    suspend fun deleteBefore(before: LocalDate)
}
