package com.doctate.watch.domain

import com.google.common.truth.Truth.assertThat
import java.io.File
import java.time.Instant
import kotlinx.coroutines.async
import kotlinx.coroutines.awaitAll
import kotlinx.coroutines.test.runTest
import org.junit.Rule
import org.junit.Test
import org.junit.rules.TemporaryFolder

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

    private fun newStore(subdir: String = "cases"): DiskCaseStore =
        DiskCaseStore(File(tempDir.root, subdir))

    @Test fun createLocal_persists_marker_file_and_emits_snapshot() = runTest {
        val store = newStore()
        val id = store.createLocal(now = 1_700_000_000_000L)
        val list = store.casesFlow.value
        assertThat(list).hasSize(1)
        assertThat(list[0].caseId).isEqualTo(id)
        assertThat(list[0].syncedToServer).isFalse()

        val markerFile = File(tempDir.root, "cases/$id.json")
        assertThat(markerFile.exists()).isTrue()
        val parsed = CaseMarker.fromJson(markerFile.readText())
        assertThat(parsed.caseId).isEqualTo(id)
        assertThat(parsed.syncedToServer).isFalse()
    }

    @Test fun bootstrap_reloads_markers_from_disk() = runTest {
        val s1 = newStore()
        val a = s1.createLocal(now = 1_700_000_000_000L)
        val b = s1.createLocal(now = 1_700_000_001_000L)
        // Build a fresh store on the same dir → must repopulate from disk.
        val s2 = newStore()
        val ids = s2.casesFlow.value.map { it.caseId }.toSet()
        assertThat(ids).containsExactly(a, b)
    }

    @Test fun markActivity_creates_marker_if_missing() = runTest {
        val store = newStore()
        store.markActivity("11111111-1111-1111-1111-111111111111", now = 1_700_000_000_000L)
        val list = store.casesFlow.value
        assertThat(list).hasSize(1)
        assertThat(list[0].caseId).isEqualTo("11111111-1111-1111-1111-111111111111")
    }

    @Test fun markSynced_sets_flag_and_persists() = runTest {
        val store = newStore()
        val id = store.createLocal(now = 1_700_000_000_000L)
        store.markSynced(id)
        assertThat(store.casesFlow.value.first { it.caseId == id }.syncedToServer).isTrue()
        // Persisted?
        val parsed = CaseMarker.fromJson(File(tempDir.root, "cases/$id.json").readText())
        assertThat(parsed.syncedToServer).isTrue()
    }

    @Test fun mergeServerSnapshot_inserts_unknown_case_with_synced_true() = runTest {
        val store = newStore()
        val response = OnelinersResponse(
            asOf = "2026-04-26T12:00:00Z",
            windowHours = 72,
            oneliners = listOf(
                OnelinerEntry(
                    caseId = "case-aaaa",
                    oneliner = OnelinerState.Ready("hi", generatedAt = "2026-04-26T11:55:00Z"),
                    createdAt = "2026-04-26T11:00:00Z",
                    lastRecordingAt = "2026-04-26T11:30:00Z",
                    updatedAt = null,
                ),
            ),
        )
        store.mergeServerSnapshot(response)
        val entry = store.casesFlow.value.single()
        assertThat(entry.caseId).isEqualTo("case-aaaa")
        assertThat(entry.syncedToServer).isTrue()
        assertThat((entry.oneliner as OnelinerState.Ready).text).isEqualTo("hi")
    }

    @Test fun mergeServerSnapshot_keeps_local_manual_against_non_manual_server() = runTest {
        val store = newStore()
        val id = store.createLocal(now = 1_700_000_000_000L)
        store.setOnelinerLocal(id, OnelinerState.Manual("doc-text", setAt = "2026-04-26T11:00:00Z"))
        val response = OnelinersResponse(
            asOf = "2026-04-26T12:00:00Z",
            windowHours = 72,
            oneliners = listOf(
                OnelinerEntry(
                    caseId = id,
                    oneliner = OnelinerState.Ready("server-text", generatedAt = "2026-04-26T11:30:00Z"),
                    createdAt = "2026-04-26T11:00:00Z",
                    lastRecordingAt = null,
                    updatedAt = null,
                ),
            ),
        )
        store.mergeServerSnapshot(response)
        val entry = store.casesFlow.value.single()
        val manual = entry.oneliner as OnelinerState.Manual
        assertThat(manual.text).isEqualTo("doc-text")
    }

    @Test fun mergeServerSnapshot_keeps_newer_local_lastActivity() = runTest {
        val store = newStore()
        // Local activity at T+10s; server reports T+5s — local wins.
        val laterMs = Instant.parse("2026-04-26T11:00:10Z").toEpochMilli()
        val id = "case-bbbb"
        store.markActivity(id, now = laterMs)
        store.markSynced(id)
        val response = OnelinersResponse(
            asOf = "2026-04-26T12:00:00Z",
            windowHours = 72,
            oneliners = listOf(
                OnelinerEntry(
                    caseId = id,
                    oneliner = null,
                    createdAt = "2026-04-26T11:00:00Z",
                    lastRecordingAt = "2026-04-26T11:00:05Z",
                    updatedAt = null,
                ),
            ),
        )
        store.mergeServerSnapshot(response)
        val entry = store.casesFlow.value.single()
        assertThat(entry.lastActivityAt).isEqualTo(laterMs)
    }

    @Test fun reconcile_removes_synced_marker_inside_window_missing_from_server() = runTest {
        val store = newStore()
        val asOf = "2026-04-26T12:00:00Z"
        val asOfMs = Instant.parse(asOf).toEpochMilli()
        val id = "case-cccc"
        store.markActivity(id, now = asOfMs - 3_600_000L) // 1h ago, well inside 72h window
        store.markSynced(id)

        val response = OnelinersResponse(asOf = asOf, windowHours = 72, oneliners = emptyList())
        val removed = store.reconcileWithServerSnapshot(response)
        assertThat(removed).isEqualTo(1)
        assertThat(store.casesFlow.value).isEmpty()
        // marker file gone
        assertThat(File(tempDir.root, "cases/$id.json").exists()).isFalse()
    }

    @Test fun reconcile_preserves_pending_marker() = runTest {
        val store = newStore()
        val asOf = "2026-04-26T12:00:00Z"
        val asOfMs = Instant.parse(asOf).toEpochMilli()
        val id = store.createLocal(now = asOfMs - 3_600_000L) // pending: synced=false

        val response = OnelinersResponse(asOf = asOf, windowHours = 72, oneliners = emptyList())
        val removed = store.reconcileWithServerSnapshot(response)
        assertThat(removed).isEqualTo(0)
        assertThat(store.casesFlow.value.map { it.caseId }).containsExactly(id)
    }

    @Test fun reconcile_preserves_marker_outside_window() = runTest {
        val store = newStore()
        val asOf = "2026-04-26T12:00:00Z"
        val asOfMs = Instant.parse(asOf).toEpochMilli()
        val id = "case-old"
        // 80h ago — outside 72h window: server silence not authoritative.
        store.markActivity(id, now = asOfMs - 80 * 3_600_000L)
        store.markSynced(id)

        val response = OnelinersResponse(asOf = asOf, windowHours = 72, oneliners = emptyList())
        val removed = store.reconcileWithServerSnapshot(response)
        assertThat(removed).isEqualTo(0)
    }

    @Test fun cleanupStale_drops_only_synced() = runTest {
        val store = newStore()
        val cutoff = 2_000_000L
        val olderSynced = "case-old-synced"
        val olderPending = "case-old-pending"
        store.markActivity(olderSynced, now = 1_000_000L)
        store.markSynced(olderSynced)
        store.markActivity(olderPending, now = 1_000_000L)

        val removed = store.cleanupStale(cutoffMs = cutoff)
        assertThat(removed).isEqualTo(1)
        assertThat(store.casesFlow.value.map { it.caseId }).containsExactly(olderPending)
    }

    @Test fun clearAll_wipes_memory_and_disk() = runTest {
        val store = newStore()
        store.createLocal()
        store.createLocal()
        store.clearAll()
        assertThat(store.casesFlow.value).isEmpty()
        val files = File(tempDir.root, "cases").listFiles()?.toList().orEmpty()
        assertThat(files).isEmpty()
    }

    @Test fun concurrent_creates_serialize_via_mutex() = runTest {
        val store = newStore()
        val ids = (1..10).map {
            async { store.createLocal(now = 1_700_000_000_000L + it) }
        }.awaitAll()
        // Each create wrote one file; size matches.
        assertThat(store.casesFlow.value.map { it.caseId }.toSet()).hasSize(ids.size)
        val onDisk = File(tempDir.root, "cases").listFiles()?.filter { it.name.endsWith(".json") }
            ?.map { it.nameWithoutExtension } ?: emptyList()
        assertThat(onDisk.toSet()).isEqualTo(ids.toSet())
    }
}
