//
//  TripPoll.swift
//  SportsTime
//
//  Domain models for group trip polling and voting.
//
//  - Expected Behavior:
//    - generateShareCode() returns 6 uppercase alphanumeric characters (no O/I/L/0/1)
//    - computeTripHash() produces deterministic hash from stops, games, and dates
//    - PollVote.calculateScores uses Borda count: points = tripCount - rank
//    - PollResults.tripScores sums all votes and sorts descending by score
//    - scorePercentage returns score/maxScore, 0 when maxScore is 0
//
//  - Invariants:
//    - shareCode is exactly 6 characters
//    - tripVersions.count == tripSnapshots.count
//    - Borda points range from 1 (last place) to tripCount (first place)
//    - tripScores contains all trip indices
//

import Foundation

// MARK: - Trip Poll

struct TripPoll: Identifiable, Codable, Hashable {
    let id: UUID
    var title: String
    let ownerId: String
    let shareCode: String
    var tripSnapshots: [Trip]
    var tripVersions: [String]
    let createdAt: Date
    var modifiedAt: Date

    init(
        id: UUID = UUID(),
        title: String,
        ownerId: String,
        shareCode: String = TripPoll.generateShareCode(),
        tripSnapshots: [Trip],
        createdAt: Date = Date(),
        modifiedAt: Date = Date()
    ) {
        self.id = id
        self.title = title
        self.ownerId = ownerId
        self.shareCode = shareCode
        self.tripSnapshots = tripSnapshots
        self.tripVersions = tripSnapshots.map { TripPoll.computeTripHash($0) }
        self.createdAt = createdAt
        self.modifiedAt = modifiedAt
    }

    // MARK: - Share Code Generation

    private static let shareCodeCharacters = Array("ABCDEFGHJKMNPQRSTUVWXYZ23456789")

    static func generateShareCode() -> String {
        String((0..<6).map { _ in shareCodeCharacters.randomElement()! })
    }

    // MARK: - Trip Hash

    static func computeTripHash(_ trip: Trip) -> String {
        var hasher = Hasher()
        hasher.combine(trip.stops.map { $0.city })
        hasher.combine(trip.stops.flatMap { $0.games })
        hasher.combine(trip.preferences.startDate)
        hasher.combine(trip.preferences.endDate)
        return String(hasher.finalize())
    }

    // MARK: - Deep Link URL

    var shareURL: URL {
        URL(string: "sportstime://poll/\(shareCode)")!
    }
}

// MARK: - Poll Vote

struct PollVote: Identifiable, Codable, Hashable {
    let id: UUID
    let pollId: UUID
    let odg: String  // voter's userRecordID
    var rankings: [Int]  // trip indices in preference order
    let votedAt: Date
    var modifiedAt: Date

    init(
        id: UUID = UUID(),
        pollId: UUID,
        odg: String,
        rankings: [Int],
        votedAt: Date = Date(),
        modifiedAt: Date = Date()
    ) {
        self.id = id
        self.pollId = pollId
        self.odg = odg
        self.rankings = rankings
        self.votedAt = votedAt
        self.modifiedAt = modifiedAt
    }

    /// Calculate Borda count scores from rankings
    /// Returns array where index = trip index, value = score
    static func calculateScores(rankings: [Int], tripCount: Int) -> [Int] {
        var scores = Array(repeating: 0, count: tripCount)
        for (rank, tripIndex) in rankings.enumerated() {
            guard tripIndex < tripCount else { continue }
            let points = tripCount - rank
            scores[tripIndex] = points
        }
        return scores
    }
}

// MARK: - Poll Results

struct PollResults {
    let poll: TripPoll
    let votes: [PollVote]

    var voterCount: Int { votes.count }

    var tripScores: [(tripIndex: Int, score: Int)] {
        guard !votes.isEmpty else {
            return poll.tripSnapshots.indices.map { ($0, 0) }
        }

        var totalScores = Array(repeating: 0, count: poll.tripSnapshots.count)
        for vote in votes {
            let scores = PollVote.calculateScores(rankings: vote.rankings, tripCount: poll.tripSnapshots.count)
            for (index, score) in scores.enumerated() {
                totalScores[index] += score
            }
        }

        return totalScores.enumerated()
            .map { ($0.offset, $0.element) }
            .sorted { $0.score > $1.score }
    }

    var maxScore: Int {
        tripScores.first?.score ?? 0
    }

    func scorePercentage(for tripIndex: Int) -> Double {
        guard maxScore > 0 else { return 0 }
        let score = tripScores.first { $0.tripIndex == tripIndex }?.score ?? 0
        return Double(score) / Double(maxScore)
    }
}
