import Testing
import Foundation
@testable import Oppi

@Suite("SessionStore Partitioning")
@MainActor
struct SessionStorePartitioningTests {

    // MARK: - Server partitioning

    @Test func sessionsPartitionedByServer() {
        let store = SessionStore()
        store.switchServer(to: "srv1")
        store.upsert(makeTestSession(id: "s1"))

        store.switchServer(to: "srv2")
        store.upsert(makeTestSession(id: "s2"))

        #expect(store.sessions.count == 1)
        #expect(store.sessions[0].id == "s2")

        store.switchServer(to: "srv1")
        #expect(store.sessions.count == 1)
        #expect(store.sessions[0].id == "s1")
    }

    @Test func sessionsForSpecificServer() {
        let store = SessionStore()
        store.switchServer(to: "srv1")
        store.upsert(makeTestSession(id: "s1"))
        store.switchServer(to: "srv2")
        store.upsert(makeTestSession(id: "s2"))

        #expect(store.sessions(forServer: "srv1").count == 1)
        #expect(store.sessions(forServer: "srv1")[0].id == "s1")
        #expect(store.sessions(forServer: "nonexistent").isEmpty)
    }

    @Test func allSessionsSpansServers() {
        let store = SessionStore()
        store.switchServer(to: "srv1")
        store.upsert(makeTestSession(id: "s1", lastActivity: Date(timeIntervalSince1970: 100)))
        store.switchServer(to: "srv2")
        store.upsert(makeTestSession(id: "s2", lastActivity: Date(timeIntervalSince1970: 200)))

        let all = store.allSessions
        #expect(all.count == 2)
        #expect(all[0].id == "s2")
    }

    @Test func findSessionAcrossServers() {
        let store = SessionStore()
        store.switchServer(to: "srv1")
        store.upsert(makeTestSession(id: "s1"))
        store.switchServer(to: "srv2")
        store.upsert(makeTestSession(id: "s2"))

        let found = store.findSession(id: "s1")
        #expect(found?.session.id == "s1")
        #expect(found?.serverId == "srv1")
        #expect(store.findSession(id: "missing") == nil)
    }

    // MARK: - Upsert

    @Test func upsertReturnsFalseWhenUnchanged() {
        let store = SessionStore()
        store.switchServer(to: "srv1")
        let s = makeTestSession(id: "s1")
        store.upsert(s)
        #expect(store.upsert(s) == false)
    }

    @Test func upsertPreservesContextUsageWhenIncomingUpdateOmitsIt() {
        let store = SessionStore()
        store.switchServer(to: "srv1")

        var initial = makeTestSession(id: "s1", status: .busy, model: "anthropic/claude-sonnet-4-0")
        initial.contextTokens = 123_000
        initial.contextWindow = 200_000
        store.upsert(initial)

        let stopped = makeTestSession(id: "s1", status: .stopped, model: "anthropic/claude-sonnet-4-0")
        store.upsert(stopped)

        #expect(store.session(id: "s1")?.status == .stopped)
        #expect(store.session(id: "s1")?.contextTokens == 123_000)
        #expect(store.session(id: "s1")?.contextWindow == 200_000)
    }

    // MARK: - Remove

    @Test func removeClearsActiveSessionId() {
        let store = SessionStore()
        store.switchServer(to: "srv1")
        store.upsert(makeTestSession(id: "s1"))
        store.activeSessionId = "s1"

        store.remove(id: "s1")
        #expect(store.activeSessionId == nil)
        #expect(store.sessions.isEmpty)
    }

    // MARK: - Server removal

    @Test func removeServerClearsPartition() {
        let store = SessionStore()
        store.switchServer(to: "srv1")
        store.upsert(makeTestSession(id: "s1"))
        store.switchServer(to: "srv2")

        store.removeServer("srv1")
        #expect(store.sessions(forServer: "srv1").isEmpty)
    }

    @Test func removeActiveServerClearsId() {
        let store = SessionStore()
        store.switchServer(to: "srv1")
        store.removeServer("srv1")
        #expect(store.activeServerId == nil)
    }

    // MARK: - Snapshot merge

    @Test func snapshotReplacesData() {
        let store = SessionStore()
        store.switchServer(to: "srv1")
        // "old" is stopped and old enough to be dropped by the merge window
        store.upsert(makeTestSession(
            id: "old",
            status: .stopped,
            createdAt: Date(timeIntervalSinceNow: -600)
        ))

        let snapshot = [
            makeTestSession(id: "new1", lastActivity: Date(timeIntervalSince1970: 200)),
            makeTestSession(id: "new2", lastActivity: Date(timeIntervalSince1970: 100)),
        ]
        store.applyServerSnapshot(snapshot)

        #expect(store.sessions.count == 2)
        #expect(store.sessions[0].id == "new1")
    }

    @Test func snapshotPreservesActiveSessions() {
        let store = SessionStore()
        store.switchServer(to: "srv1")
        store.upsert(makeTestSession(id: "local-active", status: .busy))

        store.applyServerSnapshot([makeTestSession(id: "server-only")])

        let ids = Set(store.sessions.map(\.id))
        #expect(ids.contains("server-only"))
        #expect(ids.contains("local-active"))
    }

    @Test func snapshotPreservesRecentStopped() {
        let store = SessionStore()
        store.switchServer(to: "srv1")
        store.upsert(makeTestSession(id: "recent", status: .stopped, createdAt: Date()))

        store.applyServerSnapshot([makeTestSession(id: "server-only")])

        let ids = Set(store.sessions.map(\.id))
        #expect(ids.contains("recent"))
    }

    @Test func snapshotDropsOldStopped() {
        let store = SessionStore()
        store.switchServer(to: "srv1")
        store.upsert(makeTestSession(
            id: "old",
            status: .stopped,
            createdAt: Date(timeIntervalSinceNow: -600)
        ))

        store.applyServerSnapshot([makeTestSession(id: "server-only")])

        let ids = Set(store.sessions.map(\.id))
        #expect(!ids.contains("old"))
    }

    // MARK: - Freshness

    @Test func freshnessPerServer() {
        let store = SessionStore()
        store.switchServer(to: "srv1")
        store.markSyncSucceeded()

        store.switchServer(to: "srv2")
        #expect(store.lastSuccessfulSyncAt == nil)

        store.switchServer(to: "srv1")
        #expect(store.lastSuccessfulSyncAt != nil)
    }

    @Test func syncLifecycle() {
        let store = SessionStore()
        store.switchServer(to: "srv1")

        store.markSyncStarted()
        #expect(store.isSyncing == true)

        store.markSyncSucceeded()
        #expect(store.isSyncing == false)
        #expect(store.lastSyncFailed == false)
        #expect(store.lastSuccessfulSyncAt != nil)
    }

    @Test func syncFailure() {
        let store = SessionStore()
        store.switchServer(to: "srv1")
        store.markSyncStarted()
        store.markSyncFailed()
        #expect(store.isSyncing == false)
        #expect(store.lastSyncFailed == true)
    }

    // MARK: - Convenience

    @Test func activeSessionLookup() {
        let store = SessionStore()
        store.switchServer(to: "srv1")
        store.upsert(makeTestSession(id: "s1"))
        store.activeSessionId = "s1"
        #expect(store.activeSession?.id == "s1")
    }

    @Test func workspaceIdForSession() {
        let store = SessionStore()
        store.switchServer(to: "srv1")
        store.upsert(makeTestSession(id: "s1", workspaceId: "w42"))
        #expect(store.workspaceId(for: "s1") == "w42")
        #expect(store.workspaceId(for: "missing") == nil)
    }

    @Test func sortByLastActivity() {
        let store = SessionStore()
        store.switchServer(to: "srv1")
        store.upsert(makeTestSession(id: "older", lastActivity: Date(timeIntervalSince1970: 100)))
        store.upsert(makeTestSession(id: "newer", lastActivity: Date(timeIntervalSince1970: 200)))
        store.sort()
        #expect(store.sessions[0].id == "newer")
    }
}

// MARK: - Turn-ended tracking (stable sort key)

@Suite("SessionStore Turn-Ended Tracking")
@MainActor
struct SessionStoreTurnEndedTests {

    @Test func recordAndReadTurnEndedDate() {
        let store = SessionStore()
        store.switchServer(to: "srv1")

        let date = Date(timeIntervalSince1970: 5000)
        store.recordTurnEnded(sessionId: "s1", at: date)

        #expect(store.turnEndedDate(for: "s1") == date)
    }

    @Test func turnEndedDateNilForUnknownSession() {
        let store = SessionStore()
        store.switchServer(to: "srv1")

        #expect(store.turnEndedDate(for: "missing") == nil)
    }

    @Test func turnEndedDatePartitionedByServer() {
        let store = SessionStore()

        store.switchServer(to: "srv1")
        let date1 = Date(timeIntervalSince1970: 1000)
        store.recordTurnEnded(sessionId: "s1", at: date1)

        store.switchServer(to: "srv2")
        let date2 = Date(timeIntervalSince1970: 2000)
        store.recordTurnEnded(sessionId: "s1", at: date2)

        // srv2 is active — should see srv2's date
        #expect(store.turnEndedDate(for: "s1") == date2)

        // Switch back — should see srv1's date
        store.switchServer(to: "srv1")
        #expect(store.turnEndedDate(for: "s1") == date1)
    }

    @Test func removeClearsTurnEndedDate() {
        let store = SessionStore()
        store.switchServer(to: "srv1")
        store.upsert(makeTestSession(id: "s1"))
        store.recordTurnEnded(sessionId: "s1")

        store.remove(id: "s1")
        #expect(store.turnEndedDate(for: "s1") == nil)
    }

    @Test func removeServerClearsTurnEndedDates() {
        let store = SessionStore()
        store.switchServer(to: "srv1")
        store.recordTurnEnded(sessionId: "s1")
        store.recordTurnEnded(sessionId: "s2")

        store.switchServer(to: "srv2")
        store.removeServer("srv1")

        store.switchServer(to: "srv1")
        #expect(store.turnEndedDate(for: "s1") == nil)
        #expect(store.turnEndedDate(for: "s2") == nil)
    }

    @Test func laterTurnEndedOverwritesEarlier() {
        let store = SessionStore()
        store.switchServer(to: "srv1")

        let early = Date(timeIntervalSince1970: 1000)
        let late = Date(timeIntervalSince1970: 2000)

        store.recordTurnEnded(sessionId: "s1", at: early)
        store.recordTurnEnded(sessionId: "s1", at: late)

        #expect(store.turnEndedDate(for: "s1") == late)
    }
}

// MARK: - Context summary clearing

@Suite("SessionStore Context Summary")
@MainActor
struct SessionStoreContextSummaryTests {

    @Test func clearContextSummaryRemovesFromSession() {
        let store = SessionStore()
        store.switchServer(to: "srv1")
        var session = makeTestSession(id: "s1")
        session.contextSummary = [
            ContextSummary(kind: "file_diff", path: "a.swift", addedLines: 3, removedLines: 1),
        ]
        store.upsert(session)

        #expect(store.session(id: "s1")?.contextSummary != nil)

        store.clearContextSummary(for: "s1")

        #expect(store.session(id: "s1")?.contextSummary == nil)
    }

    @Test func clearContextSummaryNoopForMissingSession() {
        let store = SessionStore()
        store.switchServer(to: "srv1")

        // Should not crash
        store.clearContextSummary(for: "nonexistent")
    }

    @Test func clearContextSummaryNoopWhenAlreadyNil() {
        let store = SessionStore()
        store.switchServer(to: "srv1")
        store.upsert(makeTestSession(id: "s1"))

        #expect(store.session(id: "s1")?.contextSummary == nil)

        store.clearContextSummary(for: "s1")

        #expect(store.session(id: "s1")?.contextSummary == nil)
    }
}
