package com.doctate.watch.domain

import com.doctate.watch.audio.PendingStore
import com.doctate.watch.audio.PendingUpload
import com.doctate.watch.testfixtures.InMemoryCaseStore
import com.google.common.truth.Truth.assertThat
import java.io.File
import kotlinx.coroutines.test.runTest
import org.junit.Rule
import org.junit.Test
import org.junit.rules.TemporaryFolder

class StartupCleanupTest {
    @get:Rule val tempDir = TemporaryFolder()

    private fun newPendingStore(root: File): PendingStore = PendingStore(File(root, "pending"))

    private fun PendingStore.seed(caseId: String, recordedAt: String): File {
        val audio = audioPathFor(caseId, recordedAt)
        audio.writeBytes(byteArrayOf(0x66, 0x74, 0x79, 0x70))
        writeSidecar(PendingUpload(caseId, recordedAt, audio))
        return audio
    }

    @Test fun sweeps_stale_synced_markers() = runTest {
        val store = InMemoryCaseStore()
        val pendingStore = newPendingStore(tempDir.root)
        val now = 10_000_000_000L
        // Old + synced → eligible.
        val oldId = "case-old"
        store.markActivity(oldId, now = now - 100L * 3600L * 1000L) // 100h ago
        store.markSynced(oldId)
        // Recent + synced → keep.
        val recentId = "case-recent"
        store.markActivity(recentId, now = now - 1L * 3600L * 1000L) // 1h ago
        store.markSynced(recentId)

        StartupCleanup.run(
            store = store,
            pendingStore = pendingStore,
            pendingDir = File(tempDir.root, "pending"),
            nowMs = now,
            markerRetentionMs = 72L * 3600L * 1000L,
        )
        val ids = store.casesFlow.value.map { it.caseId }
        assertThat(ids).containsExactly(recentId)
    }

    @Test fun preserves_pending_marker_with_pending_pair_regardless_of_age() = runTest {
        // A marker with syncedToServer=false AND a matching pending pair
        // is a real waiting upload — must survive any age threshold.
        val store = InMemoryCaseStore()
        val pendingStore = newPendingStore(tempDir.root)
        val now = 10_000_000_000L
        val pendingId = "case-old-pending"
        store.markActivity(pendingId, now = now - 200L * 3600L * 1000L)
        pendingStore.seed(pendingId, "2026-04-26T10:00:00Z")

        StartupCleanup.run(
            store = store,
            pendingStore = pendingStore,
            pendingDir = File(tempDir.root, "pending"),
            nowMs = now,
            markerRetentionMs = 72L * 3600L * 1000L,
        )
        val ids = store.casesFlow.value.map { it.caseId }
        assertThat(ids).containsExactly(pendingId)
    }

    @Test fun reaps_orphaned_unsynced_marker_when_audio_is_gone() = runTest {
        // syncedToServer=false + NO pending pair + older than the grace
        // window → orphan ("ghost" marker the user can't otherwise remove).
        // This is the exact regression fix for the 2026-05-03 21:08 ghost.
        val store = InMemoryCaseStore()
        val pendingStore = newPendingStore(tempDir.root)
        val now = 10_000_000_000L
        val ghostId = "case-ghost"
        store.markActivity(ghostId, now = now - 60L * 60L * 1000L) // 1h ago
        // No pendingStore.seed() — audio was already discarded.

        StartupCleanup.run(
            store = store,
            pendingStore = pendingStore,
            pendingDir = File(tempDir.root, "pending"),
            nowMs = now,
            orphanMarkerGraceMs = 5L * 60L * 1000L, // 5 min
        )
        assertThat(store.casesFlow.value).isEmpty()
    }

    @Test fun preserves_fresh_unsynced_marker_within_grace_window() = runTest {
        // A marker just written (within the grace window) might still have
        // its pending pair on the way — must not be reaped.
        val store = InMemoryCaseStore()
        val pendingStore = newPendingStore(tempDir.root)
        val now = 10_000_000_000L
        val freshId = "case-fresh"
        store.markActivity(freshId, now = now - 1_000L) // 1 s ago
        // No pending pair yet — but grace window protects it.

        StartupCleanup.run(
            store = store,
            pendingStore = pendingStore,
            pendingDir = File(tempDir.root, "pending"),
            nowMs = now,
            orphanMarkerGraceMs = 5L * 60L * 1000L,
        )
        val ids = store.casesFlow.value.map { it.caseId }
        assertThat(ids).containsExactly(freshId)
    }

    @Test fun reaps_old_orphan_audio_in_pending_dir() = runTest {
        val store = InMemoryCaseStore()
        val pendingDir = File(tempDir.root, "pending").apply { mkdirs() }
        val pendingStore = PendingStore(pendingDir)
        val now = 10_000_000_000L
        // Old orphan m4a (no sidecar) — should die.
        File(pendingDir, "old-orphan.m4a").apply {
            writeText("x"); setLastModified(now - 30L * 3600L * 1000L) // 30h ago
        }
        // Young orphan m4a — should survive (within 24h).
        File(pendingDir, "fresh-orphan.m4a").apply {
            writeText("x"); setLastModified(now - 1L * 3600L * 1000L) // 1h ago
        }
        StartupCleanup.run(
            store = store,
            pendingStore = pendingStore,
            pendingDir = pendingDir,
            nowMs = now,
            orphanAudioRetentionMs = 24L * 3600L * 1000L,
        )
        assertThat(File(pendingDir, "old-orphan.m4a").exists()).isFalse()
        assertThat(File(pendingDir, "fresh-orphan.m4a").exists()).isTrue()
    }
}
