//
//  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: String
    let game: Game
    let stadium: Stadium
    let homeTeam: Team
    let awayTeam: Team
    let confidence: PhotoMatchConfidence

    // Scraped score components (stored separately for flexible formatting)
    let scrapedHomeScore: Int?
    let scrapedAwayScore: Int?

    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
        self.scrapedHomeScore = nil
        self.scrapedAwayScore = nil
    }

    /// Initialize from a scraped historical game
    init(scrapedGame: ScrapedGame, stadium: Stadium) {
        let matchId = UUID()
        self.id = "scraped_match_\(matchId.uuidString)"
        self.stadium = stadium

        // Generate synthetic IDs for scraped games
        let syntheticHomeTeamId = "scraped_team_\(UUID().uuidString)"
        let syntheticAwayTeamId = "scraped_team_\(UUID().uuidString)"
        let syntheticGameId = "scraped_game_\(matchId.uuidString)"

        // Create synthetic Team objects from scraped names
        // Scraped names already include city (e.g., "Chicago Cubs"), so we use empty city
        // to avoid duplication in fullName computed property
        self.homeTeam = Team(
            id: syntheticHomeTeamId,
            name: scrapedGame.homeTeam,
            abbreviation: String(scrapedGame.homeTeam.suffix(3)).uppercased(),
            sport: scrapedGame.sport,
            city: "",
            stadiumId: stadium.id
        )

        self.awayTeam = Team(
            id: syntheticAwayTeamId,
            name: scrapedGame.awayTeam,
            abbreviation: String(scrapedGame.awayTeam.suffix(3)).uppercased(),
            sport: scrapedGame.sport,
            city: "",
            stadiumId: stadium.id
        )

        // Create synthetic Game object
        let year = Calendar.current.component(.year, from: scrapedGame.date)
        self.game = Game(
            id: syntheticGameId,
            homeTeamId: self.homeTeam.id,
            awayTeamId: self.awayTeam.id,
            stadiumId: stadium.id,
            dateTime: scrapedGame.date,
            sport: scrapedGame.sport,
            season: "\(year)"
        )

        // High confidence since we found the exact game from web
        self.confidence = PhotoMatchConfidence(
            spatial: .high,
            temporal: .exactDay
        )

        // Store scraped scores separately for flexible formatting
        self.scrapedHomeScore = scrapedGame.homeScore
        self.scrapedAwayScore = scrapedGame.awayScore
    }

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

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

    /// Final score in "away-home" format for storage (e.g., "5-3")
    var formattedFinalScore: String? {
        guard let home = scrapedHomeScore, let away = scrapedAwayScore else { return nil }
        return "\(away)-\(home)"
    }

    /// Score for display with team names (e.g., "Pirates 5\nCubs 3")
    var displayScore: String? {
        guard let home = scrapedHomeScore, let away = scrapedAwayScore else { return nil }
        return "\(awayTeam.fullName) \(away)\n\(homeTeam.fullName) \(home)"
    }

    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 {
        print("🎯 [GameMatcher] Processing photo for import")
        print("🎯 [GameMatcher]   - hasValidDate: \(metadata.hasValidDate)")
        print("🎯 [GameMatcher]   - captureDate: \(metadata.captureDate?.description ?? "nil")")
        print("🎯 [GameMatcher]   - hasValidLocation: \(metadata.hasValidLocation)")
        if let coords = metadata.coordinates {
            print("🎯 [GameMatcher]   - coordinates: \(coords.latitude), \(coords.longitude)")
        }

        // Get stadium matches regardless of game matching
        var stadiumMatches: [StadiumMatch] = []
        if let coordinates = metadata.coordinates {
            stadiumMatches = proximityMatcher.findNearbyStadiums(
                coordinates: coordinates,
                sport: sport
            )
            print("🎯 [GameMatcher]   - nearby stadiums found: \(stadiumMatches.count)")
            for match in stadiumMatches.prefix(3) {
                print("🎯 [GameMatcher]     • \(match.stadium.name) (\(String(format: "%.1f", match.distance / 1609.34)) mi)")
            }
        } else {
            print("🎯 [GameMatcher]   - no coordinates, skipping stadium proximity search")
        }

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

        switch matchResult {
        case .singleMatch(let match):
            print("🎯 [GameMatcher]   - result: singleMatch - \(match.homeTeam.fullName) vs \(match.awayTeam.fullName)")
        case .multipleMatches(let matches):
            print("🎯 [GameMatcher]   - result: multipleMatches (\(matches.count) games)")
        case .noMatches(let reason):
            print("🎯 [GameMatcher]   - result: noMatches - \(reason)")

            // FALLBACK: Try scraping historical game data if we have stadium + date
            // Try all nearby stadiums (important for multi-sport venues like American Airlines Center)
            if let captureDate = metadata.captureDate, !stadiumMatches.isEmpty {
                print("🎯 [GameMatcher]   - Trying historical scraper fallback...")

                for stadiumMatch in stadiumMatches {
                    let stadium = stadiumMatch.stadium
                    print("🎯 [GameMatcher]   - Trying \(stadium.name) (\(stadium.sport.rawValue))...")

                    if let scrapedGame = await HistoricalGameScraper.shared.scrapeGame(
                        stadium: stadium,
                        date: captureDate
                    ) {
                        print("🎯 [GameMatcher]   - ✅ Scraper found: \(scrapedGame.awayTeam) @ \(scrapedGame.homeTeam)")

                        // Convert ScrapedGame to a match result
                        matchResult = .singleMatch(GameMatchCandidate(
                            scrapedGame: scrapedGame,
                            stadium: stadium
                        ))
                        break  // Found a game, stop searching
                    }
                }

                if case .noMatches = matchResult {
                    print("🎯 [GameMatcher]   - Scraper found no game at any stadium")
                }
            }
        }

        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.filterGames(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
        )
    }
}
