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

import Foundation
import LeStorage

@Observable
final class PlayerRegistration: BasePlayerRegistration, SideStorable {

    func localizedSourceLabel() -> String {
        switch source {
        case .frenchFederation:
            return "base fédérale"
        case .beachPadel:
            return "beach-padel"
        case nil:
            if registeredOnline {
                return "à vérifier vous-même"
            } else {
                return "créé par vous-même"
            }
        }
    }
    
    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: PlayerRegistration.PlayerDataSource? = nil, hasArrived: Bool = false) {
        super.init()
        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) {
        super.init()
        self.teamRegistration = ""
        self.firstName = (importedPlayer.firstName ?? "").prefixTrimmed(50).capitalized
        self.lastName = (importedPlayer.lastName ?? "").prefixTrimmed(50).uppercased()
        self.licenceId = importedPlayer.license?.prefixTrimmed(50) ?? nil
        self.rank = Int(importedPlayer.rank)
        self.sex = importedPlayer.male ? .male : .female
        self.tournamentPlayed = importedPlayer.tournamentPlayed
        self.points = importedPlayer.getPoints()
        self.clubName = importedPlayer.clubName?.prefixTrimmed(200)
        self.ligueName = importedPlayer.ligueName?.prefixTrimmed(200)
        self.assimilation = importedPlayer.assimilation?.prefixTrimmed(50)
        self.source = .frenchFederation
        self.birthdate = importedPlayer.birthYear?.prefixTrimmed(50)
    }
    
    internal init?(federalData: [String], sex: Int, sexUnknown: Bool) {
        super.init()
        let _lastName = federalData[0].trimmed.uppercased()
        let _firstName = federalData[1].trimmed.capitalized
        if _lastName.isEmpty && _firstName.isEmpty { return nil }
        lastName = _lastName.prefixTrimmed(50)
        firstName = _firstName.prefixTrimmed(50)
        birthdate = federalData[2].formattedAsBirthdate().prefixTrimmed(50)
        licenceId = federalData[3].prefixTrimmed(50)
        clubName = federalData[4].prefixTrimmed(200)
        let stringRank = federalData[5]
        if stringRank.isEmpty {
            rank = nil
        } else {
            rank = Int(stringRank)
        }
        let _email = federalData[6]
        if _email.isEmpty == false {
            self.email = _email.prefixTrimmed(50)
        }
        let _phoneNumber = federalData[7]
        if _phoneNumber.isEmpty == false {
            self.phoneNumber = _phoneNumber.prefixTrimmed(50)
        }
        
        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)
        }
    }
    
    required init(from decoder: any Decoder) throws {
        try super.init(from: decoder)
    }
    
    required public init() {
        super.init()
    }
    
    var tournamentStore: TournamentStore? {
        guard let storeId else {
            fatalError("missing store id for \(String(describing: type(of: self)))")
        }
        return TournamentLibrary.shared.store(tournamentId: storeId)
//        if let store = self.store as? TournamentStore {
//            return store
//        }
    }

    var computedAge: Int? {
        if let birthdate {
            let components = birthdate.components(separatedBy: "/")
            if let age = components.last, let ageInt = Int(age) {
                let year = Calendar.current.getSportAge()
                
                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(_ exportFormat: ExportFormat = .rawText) -> String {
        switch exportFormat {
        case .rawText:
            return [firstName.capitalized, lastName.capitalized, licenceId].compactMap({ $0 }).joined(separator: exportFormat.separator())
        case .csv:
            return [lastName.uppercased() + " " + firstName.capitalized].joined(separator: exportFormat.separator())
        }
    }
    
    func isPlaying() -> Bool {
        team()?.isPlaying() == true
    }
    
    func contains(_ searchField: String) -> Bool {
        let nameComponents = searchField.canonicalVersion.split(separator: " ")
        
        if nameComponents.count > 1 {
            let pairs = nameComponents.pairs()
            return pairs.contains(where: {
                (firstName.canonicalVersion.localizedCaseInsensitiveContains(String($0)) &&
                 lastName.canonicalVersion.localizedCaseInsensitiveContains(String($1))) ||
                (firstName.canonicalVersion.localizedCaseInsensitiveContains(String($1)) &&
                 lastName.canonicalVersion.localizedCaseInsensitiveContains(String($0)))
            })
        } else {
            return nameComponents.contains { component in
                firstName.canonicalVersion.localizedCaseInsensitiveContains(component) ||
                lastName.canonicalVersion.localizedCaseInsensitiveContains(component)
            }
        }
    }
    
    func isSameAs(_ player: PlayerRegistration) -> Bool {
        firstName.trimmedMultiline.canonicalVersion.localizedCaseInsensitiveCompare(player.firstName.trimmedMultiline.canonicalVersion) == .orderedSame &&
        lastName.trimmedMultiline.canonicalVersion.localizedCaseInsensitiveCompare(player.lastName.trimmedMultiline.canonicalVersion) == .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 self.tournamentStore?.teamRegistrations.findById(teamRegistration)
    }

    func isHere() -> Bool {
        hasArrived
    }

    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 unrankedOrUnknown() -> Bool {
        source == nil
    }
    
    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 updateRank(from sources: [CSVParser], lastRank: Int?) async throws {
        #if DEBUG_TIME
        let start = Date()
        defer {
            let duration = Duration.milliseconds(Date().timeIntervalSince(start) * 1_000)
            print("func updateRank()", id, duration.formatted(.units(allowed: [.seconds, .milliseconds])))
        }
        #endif

        if let dataFound = try await history(from: sources) {
            rank = dataFound.rankValue?.toInt()
            points = dataFound.points
            tournamentPlayed = dataFound.tournamentCountValue?.toInt()
        } else if let dataFound = try await historyFromName(from: sources) {
            rank = dataFound.rankValue?.toInt()
            points = dataFound.points
            tournamentPlayed = dataFound.tournamentCountValue?.toInt()
        } else {
            rank = lastRank
        }
    }

    func history(from sources: [CSVParser]) async throws -> Line? {
        #if DEBUG_TIME
        let start = Date()
        defer {
            let duration = Duration.milliseconds(Date().timeIntervalSince(start) * 1_000)
            print("func history()", id, duration.formatted(.units(allowed: [.seconds, .milliseconds])))
        }
        #endif

        guard let license = licenceId?.strippedLicense else {
            return nil // Do NOT call historyFromName here, let updateRank handle it
        }

        let filteredSources = sources.filter { $0.maleData == isMalePlayer() }
        
        return await withTaskGroup(of: Line?.self) { group in
            for source in filteredSources {
                group.addTask {
                    guard !Task.isCancelled else { return nil }
                    return try? await source.first { $0.rawValue.contains(";\(license);") }
                }
            }

            for await result in group {
                if let result {
                    group.cancelAll() // Stop other tasks as soon as we find a match
                    return result
                }
            }
            return nil
        }
    }

    func historyFromName(from sources: [CSVParser]) async throws -> Line? {
        #if DEBUG
        let start = Date()
        defer {
            let duration = Duration.milliseconds(Date().timeIntervalSince(start) * 1_000)
            print("func historyFromName()", id, duration.formatted(.units(allowed: [.seconds, .milliseconds])))
        }
        #endif

        let filteredSources = sources.filter { $0.maleData == isMalePlayer() }
        let normalizedLastName = lastName.canonicalVersionWithPunctuation
        let normalizedFirstName = firstName.canonicalVersionWithPunctuation

        return await withTaskGroup(of: Line?.self) { group in
            for source in filteredSources {
                group.addTask {
                    guard !Task.isCancelled else { print("Cancelled"); return nil }
                    return try? await source.first {
                        let lineValue = $0.rawValue.canonicalVersionWithPunctuation
                        return lineValue.contains(";\(normalizedLastName);\(normalizedFirstName);")
                    }
                }
            }

            for await result in group {
                if let result {
                    group.cancelAll() // Stop other tasks as soon as we find a match
                    return result
                }
            }
            return nil
        }
    }

    func setComputedRank(in tournament: Tournament) {        
        let currentRank = rank ?? tournament.unrankValue(for: isMalePlayer()) ?? 90_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?.computedLicense {
                self.licenceId = computedLicense + " (\(year))"
            }
        }
    }

    func hasHomonym() -> Bool {
        let federalContext = PersistenceController.shared.localContainer.viewContext
        let fetchRequest = ImportedPlayer.fetchRequest()
        let predicate = NSPredicate(format: "firstName == %@ && lastName == %@", firstName, lastName)
        fetchRequest.predicate = predicate
        
        do {
            let count = try federalContext.count(for: fetchRequest)
            return count > 1
        } catch {
            
        }
        return false
    }
    
    enum PlayerDataSource: Int, Codable {
        case frenchFederation = 0
        case beachPadel = 1
    }
    
    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
        default:
            return TournamentCategory.femaleInMaleAssimilationAddition(playerRank)
        }
    }
    
    func insertOnServer() {
        self.tournamentStore?.playerRegistrations.writeChangeAndInsertOnServer(instance: self)
    }

}

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()
    }
    
    func getBirthYear() -> Int? { 
        nil
    }
    
    func getProgression() -> Int {
        0
    }
    
    func getComputedRank() -> Int? {
        computedRank
    }
}

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
}
