//
//  ScoreResolutionCacheTests.swift
//  SportsTimeTests
//
//  TDD specification tests for ScoreResolutionCache types.
//

import Testing
import Foundation
@testable import SportsTime

// MARK: - CacheStats Tests

@Suite("CacheStats")
struct CacheStatsTests {

    // MARK: - Test Data

    private func makeStats(
        totalEntries: Int = 100,
        entriesWithScores: Int = 80,
        entriesWithoutScores: Int = 20,
        expiredEntries: Int = 5,
        entriesBySport: [Sport: Int] = [.mlb: 50, .nba: 30, .nhl: 20]
    ) -> CacheStats {
        CacheStats(
            totalEntries: totalEntries,
            entriesWithScores: entriesWithScores,
            entriesWithoutScores: entriesWithoutScores,
            expiredEntries: expiredEntries,
            entriesBySport: entriesBySport
        )
    }

    // MARK: - Specification Tests: Properties

    @Test("CacheStats: stores totalEntries")
    func cacheStats_totalEntries() {
        let stats = makeStats(totalEntries: 150)
        #expect(stats.totalEntries == 150)
    }

    @Test("CacheStats: stores entriesWithScores")
    func cacheStats_entriesWithScores() {
        let stats = makeStats(entriesWithScores: 75)
        #expect(stats.entriesWithScores == 75)
    }

    @Test("CacheStats: stores entriesWithoutScores")
    func cacheStats_entriesWithoutScores() {
        let stats = makeStats(entriesWithoutScores: 25)
        #expect(stats.entriesWithoutScores == 25)
    }

    @Test("CacheStats: stores expiredEntries")
    func cacheStats_expiredEntries() {
        let stats = makeStats(expiredEntries: 10)
        #expect(stats.expiredEntries == 10)
    }

    @Test("CacheStats: stores entriesBySport")
    func cacheStats_entriesBySport() {
        let bySport: [Sport: Int] = [.mlb: 40, .nba: 60]
        let stats = makeStats(entriesBySport: bySport)
        #expect(stats.entriesBySport[.mlb] == 40)
        #expect(stats.entriesBySport[.nba] == 60)
    }

    // MARK: - Edge Cases

    @Test("CacheStats: handles empty cache")
    func cacheStats_emptyCache() {
        let stats = makeStats(
            totalEntries: 0,
            entriesWithScores: 0,
            entriesWithoutScores: 0,
            expiredEntries: 0,
            entriesBySport: [:]
        )
        #expect(stats.totalEntries == 0)
        #expect(stats.entriesBySport.isEmpty)
    }

    @Test("CacheStats: handles all expired")
    func cacheStats_allExpired() {
        let stats = makeStats(totalEntries: 100, expiredEntries: 100)
        #expect(stats.expiredEntries == stats.totalEntries)
    }

    @Test("CacheStats: handles all with scores")
    func cacheStats_allWithScores() {
        let stats = makeStats(totalEntries: 100, entriesWithScores: 100, entriesWithoutScores: 0)
        #expect(stats.entriesWithScores == stats.totalEntries)
        #expect(stats.entriesWithoutScores == 0)
    }

    @Test("CacheStats: handles single sport")
    func cacheStats_singleSport() {
        let stats = makeStats(entriesBySport: [.mlb: 100])
        #expect(stats.entriesBySport.count == 1)
        #expect(stats.entriesBySport[.mlb] == 100)
    }

    // MARK: - Invariant Tests

    /// - Invariant: entriesWithScores + entriesWithoutScores == totalEntries
    @Test("Invariant: scores split equals total")
    func invariant_scoresSplitEqualsTotal() {
        let stats = makeStats(totalEntries: 100, entriesWithScores: 80, entriesWithoutScores: 20)
        #expect(stats.entriesWithScores + stats.entriesWithoutScores == stats.totalEntries)
    }

    /// - Invariant: expiredEntries <= totalEntries
    @Test("Invariant: expired entries cannot exceed total")
    func invariant_expiredCannotExceedTotal() {
        let stats = makeStats(totalEntries: 100, expiredEntries: 50)
        #expect(stats.expiredEntries <= stats.totalEntries)
    }

    /// - Invariant: sum of entriesBySport <= totalEntries
    @Test("Invariant: sport entries sum does not exceed total")
    func invariant_sportEntriesSumDoesNotExceedTotal() {
        let bySport: [Sport: Int] = [.mlb: 30, .nba: 40, .nhl: 30]
        let stats = makeStats(totalEntries: 100, entriesBySport: bySport)
        let sportSum = bySport.values.reduce(0, +)
        #expect(sportSum <= stats.totalEntries)
    }
}

// MARK: - Cache Expiration Behavior Tests

@Suite("Cache Expiration Behavior")
struct CacheExpirationBehaviorTests {

    // These tests document the expected cache expiration behavior
    // based on ScoreResolutionCache.calculateExpiration

    // MARK: - Specification Tests: Cache Durations

    /// - Expected Behavior: Recent games (< 30 days old) expire after 24 hours
    @Test("Expiration: recent games expire after 24 hours")
    func expiration_recentGames() {
        // Games less than 30 days old should expire after 24 hours
        let recentGameCacheDuration: TimeInterval = 24 * 60 * 60
        #expect(recentGameCacheDuration == 86400) // 24 hours in seconds
    }

    /// - Expected Behavior: Historical games (> 30 days old) never expire (nil)
    @Test("Expiration: historical games never expire")
    func expiration_historicalGames() {
        // Games older than 30 days should have nil expiration (never expire)
        let historicalAgeThreshold: TimeInterval = 30 * 24 * 60 * 60
        #expect(historicalAgeThreshold == 2592000) // 30 days in seconds
    }

    /// - Expected Behavior: Failed lookups expire after 7 days
    @Test("Expiration: failed lookups expire after 7 days")
    func expiration_failedLookups() {
        let failedLookupCacheDuration: TimeInterval = 7 * 24 * 60 * 60
        #expect(failedLookupCacheDuration == 604800) // 7 days in seconds
    }

    // MARK: - Invariant Tests

    /// - Invariant: Historical threshold > recent cache duration
    @Test("Invariant: historical threshold exceeds recent cache duration")
    func invariant_historicalExceedsRecent() {
        let recentCacheDuration: TimeInterval = 24 * 60 * 60
        let historicalThreshold: TimeInterval = 30 * 24 * 60 * 60

        #expect(historicalThreshold > recentCacheDuration)
    }

    /// - Invariant: Failed lookup duration > recent cache duration
    @Test("Invariant: failed lookup duration exceeds recent cache duration")
    func invariant_failedLookupExceedsRecent() {
        let recentCacheDuration: TimeInterval = 24 * 60 * 60
        let failedLookupDuration: TimeInterval = 7 * 24 * 60 * 60

        #expect(failedLookupDuration > recentCacheDuration)
    }
}
