//
//  StadiumProximityMatcher.swift
//  SportsTime
//
//  Service for matching GPS coordinates to nearby stadiums.
//

import Foundation
import CoreLocation

// MARK: - Match Confidence

enum MatchConfidence: Sendable {
    case high      // < 500m from stadium center
    case medium    // 500m - 2km
    case low       // 2km - 5km
    case none      // > 5km or no coordinates

    nonisolated var description: String {
        switch self {
        case .high: return "High (within 500m)"
        case .medium: return "Medium (500m - 2km)"
        case .low: return "Low (2km - 5km)"
        case .none: return "No match"
        }
    }

    /// Should auto-select this match without user confirmation?
    nonisolated var shouldAutoSelect: Bool {
        switch self {
        case .high: return true
        default: return false
        }
    }
}

// Explicit nonisolated Equatable and Comparable conformance
extension MatchConfidence: Equatable {
    nonisolated static func == (lhs: MatchConfidence, rhs: MatchConfidence) -> Bool {
        switch (lhs, rhs) {
        case (.high, .high), (.medium, .medium), (.low, .low), (.none, .none):
            return true
        default:
            return false
        }
    }
}

extension MatchConfidence: Comparable {
    nonisolated static func < (lhs: MatchConfidence, rhs: MatchConfidence) -> Bool {
        let order: [MatchConfidence] = [.none, .low, .medium, .high]
        guard let lhsIndex = order.firstIndex(of: lhs),
              let rhsIndex = order.firstIndex(of: rhs) else {
            return false
        }
        return lhsIndex < rhsIndex
    }
}

// MARK: - Stadium Match

struct StadiumMatch: Identifiable, Sendable {
    let id: String
    let stadium: Stadium
    let distance: CLLocationDistance
    let confidence: MatchConfidence

    init(stadium: Stadium, distance: CLLocationDistance) {
        self.id = stadium.id
        self.stadium = stadium
        self.distance = distance
        self.confidence = Self.calculateConfidence(for: distance)
    }

    var formattedDistance: String {
        if distance < 1000 {
            return String(format: "%.0fm away", distance)
        } else {
            return String(format: "%.1f km away", distance / 1000)
        }
    }

    private static func calculateConfidence(for distance: CLLocationDistance) -> MatchConfidence {
        switch distance {
        case 0..<500:
            return .high
        case 500..<2000:
            return .medium
        case 2000..<5000:
            return .low
        default:
            return .none
        }
    }
}

// MARK: - Temporal Confidence

enum TemporalConfidence: Sendable {
    case exactDay       // Same local date as game
    case adjacentDay    // ±1 day (tailgating, next morning)
    case outOfRange     // >1 day difference

    nonisolated var description: String {
        switch self {
        case .exactDay: return "Same day"
        case .adjacentDay: return "Adjacent day (±1)"
        case .outOfRange: return "Out of range"
        }
    }
}

extension TemporalConfidence: Equatable {
    nonisolated static func == (lhs: TemporalConfidence, rhs: TemporalConfidence) -> Bool {
        switch (lhs, rhs) {
        case (.exactDay, .exactDay), (.adjacentDay, .adjacentDay), (.outOfRange, .outOfRange):
            return true
        default:
            return false
        }
    }
}

extension TemporalConfidence: Comparable {
    nonisolated static func < (lhs: TemporalConfidence, rhs: TemporalConfidence) -> Bool {
        let order: [TemporalConfidence] = [.outOfRange, .adjacentDay, .exactDay]
        guard let lhsIndex = order.firstIndex(of: lhs),
              let rhsIndex = order.firstIndex(of: rhs) else {
            return false
        }
        return lhsIndex < rhsIndex
    }
}

// MARK: - Combined Confidence

enum CombinedConfidence: Sendable {
    case autoSelect     // High spatial + exactDay → auto-select
    case userConfirm    // Medium spatial OR adjacentDay → user confirms
    case manualOnly     // Low spatial OR outOfRange → manual entry

    nonisolated var description: String {
        switch self {
        case .autoSelect: return "Auto-select"
        case .userConfirm: return "Needs confirmation"
        case .manualOnly: return "Manual entry required"
        }
    }

    nonisolated static func combine(spatial: MatchConfidence, temporal: TemporalConfidence) -> CombinedConfidence {
        // Low spatial or out of range → manual only
        switch spatial {
        case .low, .none:
            return .manualOnly
        default:
            break
        }

        switch temporal {
        case .outOfRange:
            return .manualOnly
        default:
            break
        }

        // High spatial + exact day → auto-select
        switch (spatial, temporal) {
        case (.high, .exactDay):
            return .autoSelect
        default:
            break
        }

        // Everything else needs user confirmation
        return .userConfirm
    }
}

extension CombinedConfidence: Equatable {
    nonisolated static func == (lhs: CombinedConfidence, rhs: CombinedConfidence) -> Bool {
        switch (lhs, rhs) {
        case (.autoSelect, .autoSelect), (.userConfirm, .userConfirm), (.manualOnly, .manualOnly):
            return true
        default:
            return false
        }
    }
}

extension CombinedConfidence: Comparable {
    nonisolated static func < (lhs: CombinedConfidence, rhs: CombinedConfidence) -> Bool {
        let order: [CombinedConfidence] = [.manualOnly, .userConfirm, .autoSelect]
        guard let lhsIndex = order.firstIndex(of: lhs),
              let rhsIndex = order.firstIndex(of: rhs) else {
            return false
        }
        return lhsIndex < rhsIndex
    }
}

// MARK: - Photo Match Confidence

struct PhotoMatchConfidence: Sendable {
    let spatial: MatchConfidence
    let temporal: TemporalConfidence
    let combined: CombinedConfidence

    nonisolated init(spatial: MatchConfidence, temporal: TemporalConfidence) {
        self.spatial = spatial
        self.temporal = temporal
        self.combined = CombinedConfidence.combine(spatial: spatial, temporal: temporal)
    }
}

// MARK: - Proximity Constants

/// Configuration constants for stadium proximity matching (outside @MainActor for default parameter use)
enum ProximityConstants: Sendable {
    static let highConfidenceRadius: CLLocationDistance = 500      // 500m
    static let mediumConfidenceRadius: CLLocationDistance = 2000   // 2km
    static let searchRadius: CLLocationDistance = 5000             // 5km default
    static let dateToleranceDays: Int = 1                          // ±1 day for timezone/tailgating
}

// MARK: - Stadium Proximity Matcher

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

    private let dataProvider = AppDataProvider.shared

    private init() {}

    // MARK: - Stadium Matching

    /// Find stadiums within radius of coordinates
    func findNearbyStadiums(
        coordinates: CLLocationCoordinate2D,
        radius: CLLocationDistance = ProximityConstants.searchRadius,
        sport: Sport? = nil
    ) -> [StadiumMatch] {
        let photoLocation = CLLocation(latitude: coordinates.latitude, longitude: coordinates.longitude)

        var stadiums = dataProvider.stadiums

        // Filter by sport if specified
        if let sport = sport {
            let sportTeams = dataProvider.teams.filter { $0.sport == sport }
            let stadiumIds = Set(sportTeams.map { $0.stadiumId })
            stadiums = stadiums.filter { stadiumIds.contains($0.id) }
        }

        // Calculate distances and filter by radius
        var matches: [StadiumMatch] = []

        for stadium in stadiums {
            let stadiumLocation = CLLocation(latitude: stadium.latitude, longitude: stadium.longitude)
            let distance = photoLocation.distance(from: stadiumLocation)

            if distance <= radius {
                matches.append(StadiumMatch(stadium: stadium, distance: distance))
            }
        }

        // Sort by distance (closest first)
        return matches.sorted { $0.distance < $1.distance }
    }

    /// Find best matching stadium (single result)
    func findBestMatch(
        coordinates: CLLocationCoordinate2D,
        sport: Sport? = nil
    ) -> StadiumMatch? {
        let matches = findNearbyStadiums(coordinates: coordinates, sport: sport)
        return matches.first
    }

    /// Check if coordinates are near any stadium
    func isNearStadium(
        coordinates: CLLocationCoordinate2D,
        radius: CLLocationDistance = ProximityConstants.searchRadius
    ) -> Bool {
        let matches = findNearbyStadiums(coordinates: coordinates, radius: radius)
        return !matches.isEmpty
    }

    // MARK: - Temporal Matching

    /// Calculate temporal confidence between photo date and game date
    nonisolated func calculateTemporalConfidence(photoDate: Date, gameDate: Date) -> TemporalConfidence {
        let calendar = Calendar.current

        // Normalize to day boundaries
        let photoDay = calendar.startOfDay(for: photoDate)
        let gameDay = calendar.startOfDay(for: gameDate)

        let daysDifference = abs(calendar.dateComponents([.day], from: photoDay, to: gameDay).day ?? Int.max)

        switch daysDifference {
        case 0:
            return .exactDay
        case 1:
            return .adjacentDay
        default:
            return .outOfRange
        }
    }

    /// Calculate combined confidence for a photo-stadium-game match
    nonisolated func calculateMatchConfidence(
        stadiumMatch: StadiumMatch,
        photoDate: Date?,
        gameDate: Date?
    ) -> PhotoMatchConfidence {
        let spatial = stadiumMatch.confidence

        let temporal: TemporalConfidence
        if let photoDate = photoDate, let gameDate = gameDate {
            temporal = calculateTemporalConfidence(photoDate: photoDate, gameDate: gameDate)
        } else {
            // Missing date information
            temporal = .outOfRange
        }

        return PhotoMatchConfidence(spatial: spatial, temporal: temporal)
    }
}

// MARK: - Batch Processing

extension StadiumProximityMatcher {
    /// Find matches for multiple photos
    func findMatchesForPhotos(
        _ metadata: [PhotoMetadata],
        sport: Sport? = nil
    ) -> [(metadata: PhotoMetadata, matches: [StadiumMatch])] {
        var results: [(metadata: PhotoMetadata, matches: [StadiumMatch])] = []

        for photo in metadata {
            if let coordinates = photo.coordinates {
                let matches = findNearbyStadiums(coordinates: coordinates, sport: sport)
                results.append((metadata: photo, matches: matches))
            } else {
                // No coordinates - empty matches
                results.append((metadata: photo, matches: []))
            }
        }

        return results
    }
}
