import Foundation
import Testing
@testable import Forji

// Both nested suites touch the shared `DiskCache` directory. Running them inside a
// `.serialized` parent suite prevents parallel tests from racing on the global cache
// (e.g. one test's `removeAll()` wiping another test's freshly-written file).
@Suite(.serialized)
struct DiskCacheRootTests {

    struct CoreTests {

        @Test func saveAndLoadRoundTrip() {
            DiskCache.save(["a", "b", "c"], as: "test-roundtrip")
            let items: [String]? = DiskCache.load(as: "test-roundtrip")
            #expect(items == ["a", "b", "c"])
        }

        @Test func loadMissingReturnsNil() {
            let items: [String]? = DiskCache.load(as: "nonexistent-\(UUID().uuidString)")
            #expect(items == nil)
        }

        @Test func overwriteExistingFile() {
            DiskCache.save(["old"], as: "test-overwrite")
            DiskCache.save(["new"], as: "test-overwrite")
            let items: [String]? = DiskCache.load(as: "test-overwrite")
            #expect(items == ["new"])
        }

        @Test func differentNamesAreIsolated() {
            DiskCache.save([1, 2], as: "test-alpha")
            DiskCache.save([3, 4], as: "test-beta")
            let alpha: [Int]? = DiskCache.load(as: "test-alpha")
            let beta: [Int]? = DiskCache.load(as: "test-beta")
            #expect(alpha == [1, 2])
            #expect(beta == [3, 4])
        }

        @Test func removeAllClearsEverything() {
            DiskCache.save(["x"], as: "test-remove")
            DiskCache.removeAll()
            let items: [String]? = DiskCache.load(as: "test-remove")
            #expect(items == nil)
        }

        @Test func emptyArrayReturnsNil() {
            DiskCache.save([String](), as: "test-empty")
            let items: [String]? = DiskCache.load(as: "test-empty")
            #expect(items == nil)
        }

        @Test func typeMismatchReturnsNil() {
            DiskCache.save(["text"], as: "test-mismatch")
            let wrong: [Int]? = DiskCache.load(as: "test-mismatch")
            #expect(wrong == nil)
        }
    }

    struct PaginationCacheTests {

        init() {
            DiskCache.removeAll()
        }

        @Test @MainActor func reloadAndCachePersistsItems() async {
            let pagination = PaginationState<String>(pageSize: 20)
            pagination.cacheName = "test-persist"

            await pagination.reloadAndCache { _, _ in ["a", "b", "c"] }.value

            let cached: [String]? = DiskCache.load(as: "test-persist")
            #expect(cached == ["a", "b", "c"])
        }

        @Test @MainActor func loadFromCacheRestoresItems() {
            DiskCache.save(["x", "y"], as: "test-restore")

            let pagination = PaginationState<String>(pageSize: 20)
            pagination.cacheName = "test-restore"
            pagination.loadFromCache()

            #expect(pagination.items == ["x", "y"])
            #expect(!pagination.hasLoaded)
        }

        @Test @MainActor func loadFromCacheSkipsWhenAlreadyLoaded() async {
            DiskCache.save(["cached"], as: "test-skip")

            let pagination = PaginationState<String>(pageSize: 20)
            pagination.cacheName = "test-skip"

            await pagination.reloadAndCache { _, _ in ["fresh"] }.value
            #expect(pagination.hasLoaded)

            pagination.loadFromCache()
            #expect(pagination.items == ["fresh"])
        }

        @Test @MainActor func noCacheNameSkipsPersistence() async {
            let pagination = PaginationState<String>(pageSize: 20)

            await pagination.reloadAndCache { _, _ in ["no-name"] }.value

            pagination.loadFromCache()
            #expect(pagination.items == ["no-name"])
        }

        @Test @MainActor func loadMoreAndCacheSaves() async {
            let pagination = PaginationState<String>(pageSize: 2)
            pagination.cacheName = "test-loadmore"

            await pagination.reloadAndCache { _, _ in ["a", "b"] }.value
            #expect(pagination.hasMore)

            await pagination.loadMoreAndCache { _, _ in ["c"] }

            let cached: [String]? = DiskCache.load(as: "test-loadmore")
            #expect(cached == ["a", "b", "c"])
        }

        @Test @MainActor func failedReloadDoesNotOverwriteCache() async {
            let pagination = PaginationState<String>(pageSize: 20)
            pagination.cacheName = "test-fail"

            await pagination.reloadAndCache { _, _ in ["good"] }.value

            await pagination.reloadAndCache(clearItems: true) { _, _ in
                throw URLError(.badServerResponse)
            }.value

            let cached: [String]? = DiskCache.load(as: "test-fail")
            #expect(cached == ["good"])
        }
    }
}
