//
//  Trip.swift
//  SportsTime
//

import Foundation

struct Trip: Identifiable, Codable, Hashable {
    let id: UUID
    var name: String
    let createdAt: Date
    var updatedAt: Date
    let preferences: TripPreferences
    var stops: [TripStop]
    var travelSegments: [TravelSegment]
    var totalGames: Int
    var totalDistanceMeters: Double
    var totalDrivingSeconds: Double
    var score: TripScore?

    init(
        id: UUID = UUID(),
        name: String,
        createdAt: Date = Date(),
        updatedAt: Date = Date(),
        preferences: TripPreferences,
        stops: [TripStop] = [],
        travelSegments: [TravelSegment] = [],
        totalGames: Int = 0,
        totalDistanceMeters: Double = 0,
        totalDrivingSeconds: Double = 0,
        score: TripScore? = nil
    ) {
        self.id = id
        self.name = name
        self.createdAt = createdAt
        self.updatedAt = updatedAt
        self.preferences = preferences
        self.stops = stops
        self.travelSegments = travelSegments
        self.totalGames = totalGames
        self.totalDistanceMeters = totalDistanceMeters
        self.totalDrivingSeconds = totalDrivingSeconds
        self.score = score
    }

    var totalDistanceMiles: Double { totalDistanceMeters * 0.000621371 }
    var totalDrivingHours: Double { totalDrivingSeconds / 3600.0 }

    var tripDuration: Int {
        guard let first = stops.first, let last = stops.last else { return 0 }
        let days = Calendar.current.dateComponents([.day], from: first.arrivalDate, to: last.departureDate).day ?? 0
        return max(1, days + 1)
    }

    var averageDrivingHoursPerDay: Double {
        guard tripDuration > 0 else { return 0 }
        return totalDrivingHours / Double(tripDuration)
    }

    var cities: [String] {
        // Deduplicate while preserving order
        var seen: Set<String> = []
        return stops.compactMap { stop in
            guard !seen.contains(stop.city) else { return nil }
            seen.insert(stop.city)
            return stop.city
        }
    }
    var uniqueSports: Set<Sport> { preferences.sports }
    var startDate: Date { stops.first?.arrivalDate ?? preferences.startDate }
    var endDate: Date { stops.last?.departureDate ?? preferences.endDate }

    var formattedDateRange: String {
        let formatter = DateFormatter()
        formatter.dateFormat = "MMM d, yyyy"
        return "\(formatter.string(from: startDate)) - \(formatter.string(from: endDate))"
    }

    var formattedTotalDistance: String { String(format: "%.0f miles", totalDistanceMiles) }

    var formattedTotalDriving: String {
        let hours = Int(totalDrivingHours)
        let minutes = Int((totalDrivingHours - Double(hours)) * 60)
        return "\(hours)h \(minutes)m"
    }

    func itineraryDays() -> [ItineraryDay] {
        var days: [ItineraryDay] = []
        let calendar = Calendar.current

        guard let firstDate = stops.first?.arrivalDate else { return days }

        // Find the last day with actual activity (last game date or last arrival)
        // Departure date is the day AFTER the last game, so we use day before departure
        let lastActivityDate: Date
        if let lastDeparture = stops.last?.departureDate {
            // Last activity is day before departure (departure is when you leave)
            lastActivityDate = calendar.date(byAdding: .day, value: -1, to: lastDeparture) ?? lastDeparture
        } else {
            lastActivityDate = stops.last?.arrivalDate ?? firstDate
        }

        var currentDate = calendar.startOfDay(for: firstDate)
        let endDateNormalized = calendar.startOfDay(for: lastActivityDate)
        var dayNumber = 1

        while currentDate <= endDateNormalized {
            let stopsForDay = stops.filter { stop in
                let arrivalDay = calendar.startOfDay(for: stop.arrivalDate)
                let departureDay = calendar.startOfDay(for: stop.departureDate)
                return currentDate >= arrivalDay && currentDate <= departureDay
            }

            // Travel segments are location-based, not date-based
            // The view handles inserting travel between cities when locations differ
            days.append(ItineraryDay(
                dayNumber: dayNumber,
                date: currentDate,
                stops: stopsForDay,
                travelSegments: []  // Travel handled by view based on location changes
            ))

            currentDate = calendar.date(byAdding: .day, value: 1, to: currentDate)!
            dayNumber += 1
        }
        return days
    }
}

// MARK: - Trip Score

struct TripScore: Codable, Hashable {
    let overallScore: Double
    let gameQualityScore: Double
    let routeEfficiencyScore: Double
    let leisureBalanceScore: Double
    let preferenceAlignmentScore: Double

    var formattedOverallScore: String { String(format: "%.0f", overallScore) }

    var scoreGrade: String {
        switch overallScore {
        case 90...100: return "A+"
        case 85..<90: return "A"
        case 80..<85: return "A-"
        case 75..<80: return "B+"
        case 70..<75: return "B"
        case 65..<70: return "B-"
        case 60..<65: return "C+"
        case 55..<60: return "C"
        default: return "C-"
        }
    }
}

// MARK: - Itinerary Day

struct ItineraryDay: Identifiable, Hashable {
    let id = UUID()
    let dayNumber: Int
    let date: Date
    let stops: [TripStop]
    let travelSegments: [TravelSegment]

    var formattedDate: String {
        let formatter = DateFormatter()
        formatter.dateFormat = "EEEE, MMM d"
        return formatter.string(from: date)
    }

    var isRestDay: Bool { stops.first?.isRestDay ?? false }
    var hasTravelSegment: Bool { !travelSegments.isEmpty }
    var gameIds: [UUID] { stops.flatMap { $0.games } }
    var hasGames: Bool { !gameIds.isEmpty }
    var primaryCity: String? { stops.first?.city }
    var totalDrivingHours: Double { travelSegments.reduce(0) { $0 + $1.durationHours } }
}

// MARK: - Trip Status

enum TripStatus: String, Codable {
    case draft
    case planned
    case inProgress
    case completed
    case cancelled
}
