//
//  PlayerRegistration.swift
//  Padel Tournament
//
//  Created by razmig on 10/03/2024.
//

import Foundation
import LeStorage

@Observable
class PlayerRegistration: ModelObject, Storable {
    static func resourceName() -> String { "player-registrations" }
    static func tokenExemptedMethods() -> [HTTPMethod] { return [] }

    var id: String = Store.randomId()
    var teamRegistration: String?
    var firstName: String
    var lastName: String
    var licenceId: String?
    var rank: Int?
    var paymentType: PlayerPaymentType?
    var sex: PlayerSexType?
    
    var tournamentPlayed: Int?
    var points: Double?
    var clubName: String?
    var ligueName: String?
    var assimilation: String?
    
    var phoneNumber: String?
    var email: String?
    var birthdate: String?

    var computedRank: Int = 0
    var source: PlayerDataSource?
    
    var hasArrived: Bool = false
    
    init(teamRegistration: String? = nil, firstName: String, lastName: String, licenceId: String? = nil, rank: Int? = nil, paymentType: PlayerPaymentType? = nil, sex: PlayerSexType? = nil, tournamentPlayed: Int? = nil, points: Double? = nil, clubName: String? = nil, ligueName: String? = nil, assimilation: String? = nil, phoneNumber: String? = nil, email: String? = nil, birthdate: String? = nil, computedRank: Int = 0, source: PlayerDataSource? = nil, hasArrived: Bool = false) {
        self.teamRegistration = teamRegistration
        self.firstName = firstName
        self.lastName = lastName
        self.licenceId = licenceId
        self.rank = rank
        self.paymentType = paymentType
        self.sex = sex
        self.tournamentPlayed = tournamentPlayed
        self.points = points
        self.clubName = clubName
        self.ligueName = ligueName
        self.assimilation = assimilation
        self.phoneNumber = phoneNumber
        self.email = email
        self.birthdate = birthdate
        self.computedRank = computedRank
        self.source = source
        self.hasArrived = hasArrived
    }
    
    internal init(importedPlayer: ImportedPlayer) {
        self.teamRegistration = ""
        self.firstName = (importedPlayer.firstName ?? "").trimmed.capitalized
        self.lastName = (importedPlayer.lastName ?? "").trimmed.uppercased()
        self.licenceId = importedPlayer.license ?? nil
        self.rank = Int(importedPlayer.rank)
        self.sex = importedPlayer.male ? .male : .female
        self.tournamentPlayed = importedPlayer.tournamentPlayed
        self.points = importedPlayer.getPoints()
        self.clubName = importedPlayer.clubName
        self.ligueName = importedPlayer.ligueName
        self.assimilation = importedPlayer.assimilation
        self.source = .frenchFederation
    }
    
    internal init(federalData: [String], sex: Int, sexUnknown: Bool) {
        lastName = federalData[0].trimmed.uppercased()
        firstName = federalData[1].trimmed.capitalized
        birthdate = federalData[2]
        licenceId = federalData[3]
        clubName = federalData[4]
        rank = Int(federalData[5])
        email = federalData[6]
        phoneNumber = federalData[7]
        source = .beachPadel
        if sexUnknown {
            if sex == 1 && FileImportManager.shared.foundInWomenData(license: federalData[3]) {
                self.sex = .female
            } else if  FileImportManager.shared.foundInMenData(license: federalData[3]) {
                self.sex = .male
            } else {
                self.sex = nil
            }
        } else {
            self.sex = PlayerSexType(rawValue: sex)
        }
    }

    var computedAge: Int? {
        if let birthdate {
            let components = birthdate.components(separatedBy: "/")
            if components.count == 3 {
                if let year = Calendar.current.dateComponents([.year], from: Date()).year, let age = components.last, let ageInt = Int(age) {
                    if age.count == 2 { //si l'année est sur 2 chiffres dans le fichier
                        if ageInt < 23 {
                            return year - 2000 - ageInt
                        } else {
                            return year - 2000 + 100 - ageInt
                        }
                    } else { //si l'année est représenté sur 4 chiffres
                        return year - ageInt
                    }
                }
            }
        }
        return nil
    }
    
    func pasteData() -> String {
        [firstName.capitalized, lastName.capitalized, licenceId].compactMap({ $0 }).joined(separator: " ")
    }
    
    func isPlaying() -> Bool {
        team()?.isPlaying() == true
    }
    
    func contains(_ searchField: String) -> Bool {
        firstName.localizedCaseInsensitiveContains(searchField) || lastName.localizedCaseInsensitiveContains(searchField)
    }
    
    func isSameAs(_ player: PlayerRegistration) -> Bool {
        firstName.localizedCaseInsensitiveCompare(player.firstName) == .orderedSame &&
        lastName.localizedCaseInsensitiveCompare(player.lastName) == .orderedSame
    }

    func tournament() -> Tournament? {
        guard let tournament = team()?.tournament else { return nil }
        return Store.main.findById(tournament)
    }
    
    func team() -> TeamRegistration? {
        guard let teamRegistration else { return nil }
        return Store.main.findById(teamRegistration)
    }

    func hasPaid() -> Bool {
        paymentType != nil
    }

    func playerLabel(_ displayStyle: DisplayStyle = .wide) -> String {
        switch displayStyle {
        case .wide, .title:
            return lastName.trimmed.capitalized + " " + firstName.trimmed.capitalized
        case .short:
            let names = lastName.components(separatedBy: .whitespaces)
            if lastName.components(separatedBy: .whitespaces).count > 1 {
                if let firstLongWord = names.first(where: { $0.count > 3 }) {
                    return firstLongWord.trimmed.capitalized.trunc(length: 10) + " " + firstName.trimmed.prefix(1).capitalized + "."
                }
            }
            return lastName.trimmed.capitalized.trunc(length: 10) + " " + firstName.trimmed.prefix(1).capitalized + "."
        }
    }
    
    func isImported() -> Bool {
        source == .beachPadel
    }
    
    func isValidLicenseNumber(year: Int) -> Bool {
        guard let licenceId else { return false }
        guard licenceId.isLicenseNumber else { return false }
        guard licenceId.suffix(6) == "(\(year))" else { return false }
        return true
    }
    
    @objc
    var canonicalName: String {
        playerLabel().folding(options: .diacriticInsensitive, locale: .current).lowercased()
    }

    func rankLabel(_ displayStyle: DisplayStyle = .wide) -> String {
        if let rank, rank > 0 {
            if rank != computedRank {
                return computedRank.formatted() + " (" + rank.formatted() + ")"
            } else {
                return rank.formatted()
            }
        } else {
            return "non classé" + (isMalePlayer() ? "" : "e")
        }
    }
    
    func getRank() -> Int {
        computedRank
    }
    
    @MainActor
    func updateRank(from sources: [CSVParser], lastRank: Int) async throws {
        if let value = try await history(from: sources)?.rank {
            rank = value
        } else {
            rank = lastRank
        }
    }
    
    func history(from sources: [CSVParser]) async throws -> Line? {
        guard let license = licenceId?.strippedLicense else {
            return try await historyFromName(from: sources)
        }

        return await withTaskGroup(of: Line?.self) { group in
            for source in sources.filter({ $0.maleData == isMalePlayer() }) {
                group.addTask {
                    guard !Task.isCancelled else { print("Cancelled"); return nil }

                    return try? await source.first(where: { line in
                        line.rawValue.contains(";\(license);")
                    })
                }
            }
            
            if let first = await group.first(where: { $0 != nil }) {
                group.cancelAll()
                return first
            } else {
                return nil
            }
        }
    }
    
    func historyFromName(from sources: [CSVParser]) async throws -> Line? {
        return await withTaskGroup(of: Line?.self) { group in
            for source in sources.filter({ $0.maleData == isMalePlayer() }) {
                group.addTask { [lastName, firstName] in
                    guard !Task.isCancelled else { print("Cancelled"); return nil }

                    return try? await source.first(where: { line in
                        line.rawValue.canonicalVersionWithPunctuation.contains(";\(lastName.canonicalVersionWithPunctuation);\(firstName.canonicalVersionWithPunctuation);")
                    })
                }
            }
            
            if let first = await group.first(where: { $0 != nil }) {
                group.cancelAll()
                return first
            } else {
                return nil
            }
        }
    }

    func setComputedRank(in tournament: Tournament) {
        let currentRank = rank ?? tournament.unrankValue(for: isMalePlayer()) ?? 100_000
        switch tournament.tournamentCategory {
        case .men:
            computedRank = isMalePlayer() ? currentRank : currentRank + PlayerRegistration.addon(for: currentRank, manMax: tournament.maleUnrankedValue ?? 0, womanMax: tournament.femaleUnrankedValue ?? 0)
        default:
            computedRank = currentRank
        }
    }

    func isMalePlayer() -> Bool {
        sex == .male
    }
    
    func validateLicenceId(_ year: Int) {
        if let currentLicenceId = licenceId {
            if currentLicenceId.trimmed.hasSuffix("(\(year-1))") {
                self.licenceId = currentLicenceId.replacingOccurrences(of: "\(year-1)", with: "\(year)")
            } else if let computedLicense = currentLicenceId.strippedLicense {
                self.licenceId = computedLicense + " (\(year))"
            }
        }
    }

    enum CodingKeys: String, CodingKey {
        case _id = "id"
        case _teamRegistration = "teamRegistration"
        case _firstName = "firstName"
        case _lastName = "lastName"
        case _licenceId = "licenceId"
        case _rank = "rank"
        case _paymentType = "paymentType"
        case _sex = "sex"
        case _tournamentPlayed = "tournamentPlayed"
        case _points = "points"
        case _clubName = "clubName"
        case _ligueName = "ligueName"
        case _assimilation = "assimilation"
        case _birthdate = "birthdate"
        case _phoneNumber = "phoneNumber"
        case _email = "email"
        case _computedRank = "computedRank"
        case _source = "source"
        case _hasArrived = "hasArrived"

    }
    
    func encode(to encoder: Encoder) throws {
        var container = encoder.container(keyedBy: CodingKeys.self)
        
        try container.encode(id, forKey: ._id)
        
        if let teamRegistration = teamRegistration {
            try container.encode(teamRegistration, forKey: ._teamRegistration)
        } else {
            try container.encodeNil(forKey: ._teamRegistration)
        }
        
        try container.encode(firstName, forKey: ._firstName)
        try container.encode(lastName, forKey: ._lastName)
        
        if let licenceId = licenceId {
            try container.encode(licenceId, forKey: ._licenceId)
        } else {
            try container.encodeNil(forKey: ._licenceId)
        }
        
        if let rank = rank {
            try container.encode(rank, forKey: ._rank)
        } else {
            try container.encodeNil(forKey: ._rank)
        }
        
        if let paymentType = paymentType {
            try container.encode(paymentType, forKey: ._paymentType)
        } else {
            try container.encodeNil(forKey: ._paymentType)
        }
        
        if let sex = sex {
            try container.encode(sex, forKey: ._sex)
        } else {
            try container.encodeNil(forKey: ._sex)
        }
        
        if let tournamentPlayed = tournamentPlayed {
            try container.encode(tournamentPlayed, forKey: ._tournamentPlayed)
        } else {
            try container.encodeNil(forKey: ._tournamentPlayed)
        }
        
        if let points = points {
            try container.encode(points, forKey: ._points)
        } else {
            try container.encodeNil(forKey: ._points)
        }
        
        if let clubName = clubName {
            try container.encode(clubName, forKey: ._clubName)
        } else {
            try container.encodeNil(forKey: ._clubName)
        }
        
        if let ligueName = ligueName {
            try container.encode(ligueName, forKey: ._ligueName)
        } else {
            try container.encodeNil(forKey: ._ligueName)
        }
        
        if let assimilation = assimilation {
            try container.encode(assimilation, forKey: ._assimilation)
        } else {
            try container.encodeNil(forKey: ._assimilation)
        }
        
        if let phoneNumber = phoneNumber {
            try container.encode(phoneNumber, forKey: ._phoneNumber)
        } else {
            try container.encodeNil(forKey: ._phoneNumber)
        }
        
        if let email = email {
            try container.encode(email, forKey: ._email)
        } else {
            try container.encodeNil(forKey: ._email)
        }
        
        if let birthdate = birthdate {
            try container.encode(birthdate, forKey: ._birthdate)
        } else {
            try container.encodeNil(forKey: ._birthdate)
        }
        
        try container.encode(computedRank, forKey: ._computedRank)
        
        if let source = source {
            try container.encode(source, forKey: ._source)
        } else {
            try container.encodeNil(forKey: ._source)
        }
        
        try container.encode(hasArrived, forKey: ._hasArrived)
    }

    enum PlayerDataSource: Int, Codable {
        case frenchFederation = 0
        case beachPadel = 1
    }
    
    enum PlayerSexType: Int, Hashable, CaseIterable, Identifiable, Codable {
        init?(rawValue: Int?) {
            guard let value = rawValue else { return nil }
            self.init(rawValue: value)
        }

        var id: Self {
            self
        }
        
        case female = 0
        case male = 1
    }
    
    enum PlayerPaymentType: Int, CaseIterable, Identifiable, Codable {
        init?(rawValue: Int?) {
            guard let value = rawValue else { return nil }
            self.init(rawValue: value)
        }

        var id: Self {
            self
        }
        
        case cash = 0
        case lydia = 1
        case gift = 2
        case check = 3
        case paylib = 4
        case bankTransfer = 5
        case clubHouse = 6
        case creditCard = 7
        case forfeit = 8
        
        func localizedLabel(_ displayStyle: DisplayStyle = .wide) -> String {
            switch self {
            case .check:
                return "Chèque"
            case .cash:
                return "Cash"
            case .lydia:
                return "Lydia"
            case .paylib:
                return "Paylib"
            case .bankTransfer:
                return "Virement"
            case .clubHouse:
                return "Clubhouse"
            case .creditCard:
                return "CB"
            case .forfeit:
                return "Forfait"
            case .gift:
                return "Offert"
            }
        }
    }
    
    static func addon(for playerRank: Int, manMax: Int, womanMax: Int) -> Int {
        switch playerRank {
        case 0: return 0
        case womanMax: return manMax - womanMax
        case manMax: return 0
        case 1...10: return 400
        case 11...30: return 1000
        case 31...60: return 2000
        case 61...100: return 3000
        case 101...200: return 8000
        case 201...500: return 12000
        default:
            return 15000
        }
    }
    
    func insertOnServer() throws {
        try DataStore.shared.playerRegistrations.writeChangeAndInsertOnServer(instance: self)
    }

}

extension PlayerRegistration: Hashable {
    static func == (lhs: PlayerRegistration, rhs: PlayerRegistration) -> Bool {
        lhs.id == rhs.id
    }
    
    func hash(into hasher: inout Hasher) {
        hasher.combine(id)
    }
}

extension PlayerRegistration: PlayerHolder {
    func getAssimilatedAsMaleRank() -> Int? {
        nil
    }
    
    func getFirstName() -> String {
        firstName
    }
    
    func getLastName() -> String {
        lastName
    }
    
    func getPoints() -> Double? {
        self.points
    }
    
    func getRank() -> Int? {
        rank
    }
    
    func isUnranked() -> Bool {
        rank == nil
    }
    
    func formattedRank() -> String {
        self.rankLabel()
    }
    
    func formattedLicense() -> String {
        if let licenceId { return licenceId.computedLicense }
        return "aucune licence"
    }
    
    var male: Bool {
        isMalePlayer()
    }
}
