//
//  GameMatcher.swift
//  SportsTime
//
//  Deterministic game matching from photo metadata.
//

import Foundation
import CoreLocation

// MARK: - No Match Reason

enum NoMatchReason: Sendable {
    case noStadiumNearby
    case noGamesOnDate
    case metadataMissing(MetadataMissingReason)

    enum MetadataMissingReason: Sendable {
        case noLocation
        case noDate
        case noBoth
    }

    var description: String {
        switch self {
        case .noStadiumNearby:
            return "No stadium found nearby"
        case .noGamesOnDate:
            return "No games found on this date"
        case .metadataMissing(let reason):
            switch reason {
            case .noLocation:
                return "Photo has no location data"
            case .noDate:
                return "Photo has no date information"
            case .noBoth:
                return "Photo has no location or date data"
            }
        }
    }
}

// MARK: - Game Match Result

struct GameMatchCandidate: Identifiable, Sendable {
    let id: UUID
    let game: Game
    let stadium: Stadium
    let homeTeam: Team
    let awayTeam: Team
    let confidence: PhotoMatchConfidence

    init(game: Game, stadium: Stadium, homeTeam: Team, awayTeam: Team, confidence: PhotoMatchConfidence) {
        self.id = game.id
        self.game = game
        self.stadium = stadium
        self.homeTeam = homeTeam
        self.awayTeam = awayTeam
        self.confidence = confidence
    }

    var matchupDescription: String {
        "\(awayTeam.abbreviation) @ \(homeTeam.abbreviation)"
    }

    var fullMatchupDescription: String {
        "\(awayTeam.fullName) at \(homeTeam.fullName)"
    }

    var gameDateTime: String {
        let formatter = DateFormatter()
        formatter.dateStyle = .medium
        formatter.timeStyle = .short
        return formatter.string(from: game.dateTime)
    }
}

enum GameMatchResult: Sendable {
    case singleMatch(GameMatchCandidate)           // Auto-select
    case multipleMatches([GameMatchCandidate])     // User selects (doubleheader, nearby stadiums)
    case noMatches(NoMatchReason)                  // Manual entry required

    var hasMatch: Bool {
        switch self {
        case .singleMatch, .multipleMatches:
            return true
        case .noMatches:
            return false
        }
    }
}

// MARK: - Photo Import Result

struct PhotoImportCandidate: Identifiable, Sendable {
    let id: UUID
    let metadata: PhotoMetadata
    let matchResult: GameMatchResult
    let stadiumMatches: [StadiumMatch]

    init(metadata: PhotoMetadata, matchResult: GameMatchResult, stadiumMatches: [StadiumMatch]) {
        self.id = UUID()
        self.metadata = metadata
        self.matchResult = matchResult
        self.stadiumMatches = stadiumMatches
    }

    /// Best stadium match if available
    var bestStadiumMatch: StadiumMatch? {
        stadiumMatches.first
    }

    /// Whether this can be auto-processed without user input
    var canAutoProcess: Bool {
        if case .singleMatch(let candidate) = matchResult {
            return candidate.confidence.combined == .autoSelect
        }
        return false
    }
}

// MARK: - Game Matcher

@MainActor
final class GameMatcher {
    static let shared = GameMatcher()

    private let dataProvider = AppDataProvider.shared
    private let proximityMatcher = StadiumProximityMatcher.shared

    private init() {}

    // MARK: - Primary Matching

    /// Match photo metadata to a game
    /// Uses deterministic rules - never guesses
    func matchGame(
        metadata: PhotoMetadata,
        sport: Sport? = nil
    ) async -> GameMatchResult {
        // 1. Check for required metadata
        guard metadata.hasValidLocation else {
            let reason: NoMatchReason.MetadataMissingReason = metadata.hasValidDate ? .noLocation : .noBoth
            return .noMatches(.metadataMissing(reason))
        }

        guard metadata.hasValidDate, let photoDate = metadata.captureDate else {
            return .noMatches(.metadataMissing(.noDate))
        }

        guard let coordinates = metadata.coordinates else {
            return .noMatches(.metadataMissing(.noLocation))
        }

        // 2. Find nearby stadiums
        let stadiumMatches = proximityMatcher.findNearbyStadiums(
            coordinates: coordinates,
            sport: sport
        )

        guard !stadiumMatches.isEmpty else {
            return .noMatches(.noStadiumNearby)
        }

        // 3. Find games at those stadiums on/around that date
        var candidates: [GameMatchCandidate] = []

        for stadiumMatch in stadiumMatches {
            let games = await findGames(
                at: stadiumMatch.stadium,
                around: photoDate,
                sport: sport
            )

            for game in games {
                // Look up teams
                guard let homeTeam = dataProvider.teams.first(where: { $0.id == game.homeTeamId }),
                      let awayTeam = dataProvider.teams.first(where: { $0.id == game.awayTeamId }) else {
                    continue
                }

                // Calculate confidence
                let confidence = proximityMatcher.calculateMatchConfidence(
                    stadiumMatch: stadiumMatch,
                    photoDate: photoDate,
                    gameDate: game.dateTime
                )

                // Only include if temporal confidence is acceptable
                if confidence.temporal != .outOfRange {
                    candidates.append(GameMatchCandidate(
                        game: game,
                        stadium: stadiumMatch.stadium,
                        homeTeam: homeTeam,
                        awayTeam: awayTeam,
                        confidence: confidence
                    ))
                }
            }
        }

        // 4. Return based on matches found
        if candidates.isEmpty {
            return .noMatches(.noGamesOnDate)
        } else if candidates.count == 1 {
            return .singleMatch(candidates[0])
        } else {
            // Sort by confidence (best first)
            let sorted = candidates.sorted { c1, c2 in
                c1.confidence.combined > c2.confidence.combined
            }
            return .multipleMatches(sorted)
        }
    }

    // MARK: - Full Import Processing

    /// Process a photo for import, returning full match context
    func processPhotoForImport(
        metadata: PhotoMetadata,
        sport: Sport? = nil
    ) async -> PhotoImportCandidate {
        // Get stadium matches regardless of game matching
        var stadiumMatches: [StadiumMatch] = []
        if let coordinates = metadata.coordinates {
            stadiumMatches = proximityMatcher.findNearbyStadiums(
                coordinates: coordinates,
                sport: sport
            )
        }

        let matchResult = await matchGame(metadata: metadata, sport: sport)

        return PhotoImportCandidate(
            metadata: metadata,
            matchResult: matchResult,
            stadiumMatches: stadiumMatches
        )
    }

    /// Process multiple photos for import
    func processPhotosForImport(
        _ metadataList: [PhotoMetadata],
        sport: Sport? = nil
    ) async -> [PhotoImportCandidate] {
        var results: [PhotoImportCandidate] = []

        for metadata in metadataList {
            let candidate = await processPhotoForImport(metadata: metadata, sport: sport)
            results.append(candidate)
        }

        return results
    }

    // MARK: - Private Helpers

    /// Find games at a stadium around a given date (±1 day for timezone/tailgating)
    private func findGames(
        at stadium: Stadium,
        around date: Date,
        sport: Sport?
    ) async -> [Game] {
        let calendar = Calendar.current

        // Search window: ±1 day
        guard let startDate = calendar.date(byAdding: .day, value: -1, to: date),
              let endDate = calendar.date(byAdding: .day, value: 2, to: date) else {
            return []
        }

        // Determine which sports to query
        let sports: Set<Sport> = sport != nil ? [sport!] : Set(Sport.allCases)

        do {
            let allGames = try await dataProvider.fetchGames(sports: sports, startDate: startDate, endDate: endDate)

            // Filter by stadium
            let games = allGames.filter { $0.stadiumId == stadium.id }

            return games
        } catch {
            return []
        }
    }
}

// MARK: - Batch Processing Helpers

extension GameMatcher {
    /// Separate photos into categories for UI
    struct CategorizedImports: Sendable {
        let autoProcessable: [PhotoImportCandidate]
        let needsConfirmation: [PhotoImportCandidate]
        let needsManualEntry: [PhotoImportCandidate]
    }

    nonisolated func categorizeImports(_ candidates: [PhotoImportCandidate]) -> CategorizedImports {
        var auto: [PhotoImportCandidate] = []
        var confirm: [PhotoImportCandidate] = []
        var manual: [PhotoImportCandidate] = []

        for candidate in candidates {
            switch candidate.matchResult {
            case .singleMatch(let match):
                if match.confidence.combined == .autoSelect {
                    auto.append(candidate)
                } else {
                    confirm.append(candidate)
                }
            case .multipleMatches:
                confirm.append(candidate)
            case .noMatches:
                manual.append(candidate)
            }
        }

        return CategorizedImports(
            autoProcessable: auto,
            needsConfirmation: confirm,
            needsManualEntry: manual
        )
    }
}
