//
//  ScoreResolutionCache.swift
//  SportsTime
//
//  Manages caching of resolved game scores using SwiftData.
//  Historical scores never change, so they can be cached indefinitely.
//

import Foundation
import SwiftData

// MARK: - Score Resolution Cache

@MainActor
final class ScoreResolutionCache {

    // MARK: - Properties

    private let modelContext: ModelContext

    // Cache configuration
    private static let recentGameCacheDuration: TimeInterval = 24 * 60 * 60  // 24 hours
    private static let failedLookupCacheDuration: TimeInterval = 7 * 24 * 60 * 60  // 7 days
    private static let historicalAgeThreshold: TimeInterval = 30 * 24 * 60 * 60  // 30 days

    // MARK: - Initialization

    init(modelContext: ModelContext) {
        self.modelContext = modelContext
    }

    // MARK: - Cache Operations

    /// Get cached score for a game query
    func getCached(query: HistoricalGameQuery) -> CachedGameScore? {
        guard let homeAbbrev = query.homeTeamAbbrev,
              let awayAbbrev = query.awayTeamAbbrev else {
            return nil
        }

        let cacheKey = CachedGameScore.generateKey(
            sport: query.sport,
            date: query.date,
            homeAbbrev: homeAbbrev,
            awayAbbrev: awayAbbrev
        )

        let descriptor = FetchDescriptor<CachedGameScore>(
            predicate: #Predicate { $0.cacheKey == cacheKey }
        )

        do {
            let results = try modelContext.fetch(descriptor)
            if let cached = results.first {
                // Check if expired
                if cached.isExpired {
                    // Delete expired entry
                    modelContext.delete(cached)
                    try? modelContext.save()
                    return nil
                }
                return cached
            }
        } catch {
            // Fetch failed, return nil
        }

        return nil
    }

    /// Convert cached score to HistoricalGameResult
    func getCachedResult(query: HistoricalGameQuery) -> HistoricalGameResult? {
        guard let cached = getCached(query: query) else {
            return nil
        }

        return HistoricalGameResult(
            sport: cached.sportEnum ?? query.sport,
            gameDate: cached.gameDate,
            homeTeamAbbrev: cached.homeTeamAbbrev,
            awayTeamAbbrev: cached.awayTeamAbbrev,
            homeTeamName: cached.homeTeamName,
            awayTeamName: cached.awayTeamName,
            homeScore: cached.homeScore,
            awayScore: cached.awayScore,
            source: cached.scoreSource,
            providerName: "cache"
        )
    }

    /// Cache a resolved game result
    func cache(result: HistoricalGameResult, query: HistoricalGameQuery) {
        let cacheKey = CachedGameScore.generateKey(
            sport: result.sport,
            date: result.gameDate,
            homeAbbrev: result.homeTeamAbbrev,
            awayAbbrev: result.awayTeamAbbrev
        )

        // Check if already cached
        let descriptor = FetchDescriptor<CachedGameScore>(
            predicate: #Predicate { $0.cacheKey == cacheKey }
        )

        do {
            let existing = try modelContext.fetch(descriptor)
            if let existingEntry = existing.first {
                // Update existing entry
                existingEntry.homeScore = result.homeScore
                existingEntry.awayScore = result.awayScore
                existingEntry.sourceRaw = result.source.rawValue
                existingEntry.fetchedAt = Date()
                existingEntry.expiresAt = calculateExpiration(for: result.gameDate)
            } else {
                // Create new entry
                let cached = CachedGameScore(
                    cacheKey: cacheKey,
                    sport: result.sport,
                    gameDate: result.gameDate,
                    homeTeamAbbrev: result.homeTeamAbbrev,
                    awayTeamAbbrev: result.awayTeamAbbrev,
                    homeTeamName: result.homeTeamName,
                    awayTeamName: result.awayTeamName,
                    homeScore: result.homeScore,
                    awayScore: result.awayScore,
                    source: result.source,
                    expiresAt: calculateExpiration(for: result.gameDate)
                )
                modelContext.insert(cached)
            }

            try modelContext.save()
        } catch {
            // Cache save failed, continue without caching
        }
    }

    /// Cache a failed lookup to avoid repeated failures
    func cacheFailedLookup(query: HistoricalGameQuery) {
        guard let homeAbbrev = query.homeTeamAbbrev,
              let awayAbbrev = query.awayTeamAbbrev else {
            return
        }

        let cacheKey = CachedGameScore.generateKey(
            sport: query.sport,
            date: query.date,
            homeAbbrev: homeAbbrev,
            awayAbbrev: awayAbbrev
        )

        // Check if already cached
        let descriptor = FetchDescriptor<CachedGameScore>(
            predicate: #Predicate { $0.cacheKey == cacheKey }
        )

        do {
            let existing = try modelContext.fetch(descriptor)
            if existing.isEmpty {
                // Create failed lookup entry (no scores)
                let cached = CachedGameScore(
                    cacheKey: cacheKey,
                    sport: query.sport,
                    gameDate: query.date,
                    homeTeamAbbrev: homeAbbrev,
                    awayTeamAbbrev: awayAbbrev,
                    homeTeamName: homeAbbrev,
                    awayTeamName: awayAbbrev,
                    homeScore: nil,
                    awayScore: nil,
                    source: .api,
                    expiresAt: Date().addingTimeInterval(Self.failedLookupCacheDuration)
                )
                modelContext.insert(cached)
                try modelContext.save()
            }
        } catch {
            // Ignore cache failures
        }
    }

    /// Remove a cached entry
    func invalidate(query: HistoricalGameQuery) {
        guard let homeAbbrev = query.homeTeamAbbrev,
              let awayAbbrev = query.awayTeamAbbrev else {
            return
        }

        let cacheKey = CachedGameScore.generateKey(
            sport: query.sport,
            date: query.date,
            homeAbbrev: homeAbbrev,
            awayAbbrev: awayAbbrev
        )

        let descriptor = FetchDescriptor<CachedGameScore>(
            predicate: #Predicate { $0.cacheKey == cacheKey }
        )

        do {
            let results = try modelContext.fetch(descriptor)
            for entry in results {
                modelContext.delete(entry)
            }
            try modelContext.save()
        } catch {
            // Ignore deletion failures
        }
    }

    /// Clean up expired cache entries
    func cleanupExpired() {
        let now = Date()

        // Can't use date comparison directly in predicate with non-nil check
        // Fetch all and filter
        let descriptor = FetchDescriptor<CachedGameScore>()

        do {
            let allCached = try modelContext.fetch(descriptor)
            var deletedCount = 0

            for entry in allCached {
                if let expiresAt = entry.expiresAt, expiresAt < now {
                    modelContext.delete(entry)
                    deletedCount += 1
                }
            }

            if deletedCount > 0 {
                try modelContext.save()
            }
        } catch {
            // Cleanup failed, will try again later
        }
    }

    /// Get cache statistics
    func getCacheStats() -> CacheStats {
        let descriptor = FetchDescriptor<CachedGameScore>()

        do {
            let all = try modelContext.fetch(descriptor)
            let now = Date()

            var withScores = 0
            var withoutScores = 0
            var expired = 0
            var bySport: [Sport: Int] = [:]

            for entry in all {
                // Count by sport
                if let sport = entry.sportEnum {
                    bySport[sport, default: 0] += 1
                }

                // Count with/without scores
                if entry.homeScore != nil && entry.awayScore != nil {
                    withScores += 1
                } else {
                    withoutScores += 1
                }

                // Count expired
                if let expiresAt = entry.expiresAt, expiresAt < now {
                    expired += 1
                }
            }

            return CacheStats(
                totalEntries: all.count,
                entriesWithScores: withScores,
                entriesWithoutScores: withoutScores,
                expiredEntries: expired,
                entriesBySport: bySport
            )
        } catch {
            return CacheStats(
                totalEntries: 0,
                entriesWithScores: 0,
                entriesWithoutScores: 0,
                expiredEntries: 0,
                entriesBySport: [:]
            )
        }
    }

    // MARK: - Private Helpers

    private func calculateExpiration(for gameDate: Date) -> Date? {
        let now = Date()
        let gameAge = now.timeIntervalSince(gameDate)

        if gameAge > Self.historicalAgeThreshold {
            // Historical games never expire
            return nil
        } else {
            // Recent games expire after 24 hours
            return now.addingTimeInterval(Self.recentGameCacheDuration)
        }
    }
}

// MARK: - Cache Statistics

struct CacheStats {
    let totalEntries: Int
    let entriesWithScores: Int
    let entriesWithoutScores: Int
    let expiredEntries: Int
    let entriesBySport: [Sport: Int]
}
