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

import Foundation
import LeStorage
import SwiftUI

@Observable
final class TeamRegistration: BaseTeamRegistration, SideStorable {

    //    static func resourceName() -> String { "team-registrations" }
    //    static func tokenExemptedMethods() -> [HTTPMethod] { return [] }
    //    static func filterByStoreIdentifier() -> Bool { return true }
    //    static var relationshipNames: [String] = []
    //
    //    var id: String = Store.randomId()
    //    var lastUpdate: Date
    //    var tournament: String
    //    var groupStage: String?
    //    var registrationDate: Date?
    //    var callDate: Date?
    //    var bracketPosition: Int?
    //    var groupStagePosition: Int?
    //    var comment: String?
    //    var source: String?
    //    var sourceValue: String?
    //    var logo: String?
    //    var name: String?
    //
    //    var walkOut: Bool = false
    //    var wildCardBracket: Bool = false
    //    var wildCardGroupStage: Bool = false
    //    var weight: Int = 0
    //    var lockedWeight: Int?
    //    var confirmationDate: Date?
    //    var qualified: Bool = false
    //    var finalRanking: Int?
    //    var pointsEarned: Int?
    //
    //    var storeId: String? = nil

    init(
        tournament: String, groupStage: String? = nil, registrationDate: Date? = nil,
        callDate: Date? = nil, bracketPosition: Int? = nil, groupStagePosition: Int? = nil,
        comment: String? = nil, source: String? = nil, sourceValue: String? = nil,
        logo: String? = nil, name: String? = nil, walkOut: Bool = false,
        wildCardBracket: Bool = false, wildCardGroupStage: Bool = false, weight: Int = 0,
        lockedWeight: Int? = nil, confirmationDate: Date? = nil, qualified: Bool = false
    ) {

        super.init()

        //        self.storeId = tournament
        self.tournament = tournament
        self.groupStage = groupStage
        self.registrationDate = registrationDate ?? Date()
        self.callDate = callDate
        self.bracketPosition = bracketPosition
        self.groupStagePosition = groupStagePosition
        self.comment = comment
        self.source = source
        self.sourceValue = sourceValue
        self.logo = logo
        self.name = name
        self.walkOut = walkOut
        self.wildCardBracket = wildCardBracket
        self.wildCardGroupStage = wildCardGroupStage
        self.weight = weight
        self.lockedWeight = lockedWeight
        self.confirmationDate = confirmationDate
        self.qualified = qualified
    }

    func hasRegisteredOnline() -> Bool {
        players().anySatisfy({ $0.registeredOnline })
    }

    func unrankedOrUnknown() -> Bool {
        players().anySatisfy({ $0.source == nil })
    }

    func isOutOfTournament() -> Bool {
        walkOut
    }

    required init(from decoder: any Decoder) throws {
        try super.init(from: decoder)
    }
    
    required public init() {
        super.init()
    }
    
    var tournamentStore: TournamentStore? {
        return TournamentLibrary.shared.store(tournamentId: self.tournament)
    }

    // MARK: - Computed dependencies

    func unsortedPlayers() -> [PlayerRegistration] {
        guard let tournamentStore = self.tournamentStore else { return [] }
        return tournamentStore.playerRegistrations.filter {
            $0.teamRegistration == self.id && $0.coach == false
        }
    }

    // MARK: -

    func deleteTeamScores() {
        guard let tournamentStore = self.tournamentStore else { return }
        let ts = tournamentStore.teamScores.filter({ $0.teamRegistration == id })
        tournamentStore.teamScores.delete(contentOfs: ts)
    }

    override func deleteDependencies() {
        let unsortedPlayers = unsortedPlayers()
        for player in unsortedPlayers {
            player.deleteDependencies()
        }
        self.tournamentStore?.playerRegistrations.deleteDependencies(unsortedPlayers)

        let teamScores = teamScores()
        for teamScore in teamScores {
            teamScore.deleteDependencies()
        }
        self.tournamentStore?.teamScores.deleteDependencies(teamScores)
    }

    func hasArrived(isHere: Bool = false) {
        let unsortedPlayers = unsortedPlayers()
        unsortedPlayers.forEach({ $0.hasArrived = !isHere })
        self.tournamentStore?.playerRegistrations.addOrUpdate(contentOfs: unsortedPlayers)
    }

    func isHere() -> Bool {
        let unsortedPlayers = unsortedPlayers()
        if unsortedPlayers.isEmpty { return false }
        return unsortedPlayers.allSatisfy({ $0.hasArrived })
    }

    func isSeedable() -> Bool {
        bracketPosition == nil && groupStage == nil
    }

    func setSeedPosition(inSpot match: Match, slot: TeamPosition?, opposingSeeding: Bool) {
        var teamPosition: TeamPosition {
            if let slot {
                return slot
            } else {
                let matchIndex = match.index
                let seedRound = RoundRule.roundIndex(fromMatchIndex: matchIndex)
                let numberOfMatches = RoundRule.numberOfMatches(forRoundIndex: seedRound)
                let isUpper =
                    RoundRule.matchIndexWithinRound(fromMatchIndex: matchIndex)
                    < (numberOfMatches / 2)
                var teamPosition = slot ?? (isUpper ? .one : .two)
                if opposingSeeding {
                    teamPosition = slot ?? (isUpper ? .two : .one)
                }
                return teamPosition
            }
        }

        let seedPosition: Int = match.lockAndGetSeedPosition(atTeamPosition: teamPosition)
        tournamentObject()?.resetTeamScores(in: bracketPosition)
        self.bracketPosition = seedPosition
        if groupStagePosition != nil && qualified == false {
            qualified = true
        }
        if let tournament = tournamentObject() {
            if let index = index(in: tournament.selectedSortedTeams()) {
                let drawLog = DrawLog(
                    tournament: tournament.id, drawSeed: index, drawMatchIndex: match.index,
                    drawTeamPosition: teamPosition, drawType: .seed)
                do {
                    try tournamentStore?.drawLogs.addOrUpdate(instance: drawLog)
                } catch {
                    Logger.error(error)
                }
            }
            tournament.updateTeamScores(in: bracketPosition)
        }
    }

    func expectedSummonDate() -> Date? {
        if let groupStageStartDate = groupStageObject()?.startDate {
            return groupStageStartDate
        } else if let roundMatchStartDate = initialMatch()?.startDate {
            return roundMatchStartDate
        }
        return nil
    }

    var initialWeight: Int {
        return lockedWeight ?? weight
    }

    func called() -> Bool {
        return callDate != nil
    }

    func confirmed() -> Bool {
        return confirmationDate != nil
    }

    func getPhoneNumbers() -> [String] {
        return players().compactMap { $0.phoneNumber }.filter({ $0.isEmpty == false })
    }

    func getMail() -> [String] {
        let mails = players().compactMap({ $0.email })
        return mails
    }

    func isImported() -> Bool {
        let unsortedPlayers = unsortedPlayers()
        if unsortedPlayers.isEmpty { return false }

        return unsortedPlayers.allSatisfy({ $0.isImported() })
    }

    func isWildCard() -> Bool {
        return wildCardBracket || wildCardGroupStage
    }

    func isPlaying() -> Bool {
        return currentMatch() != nil
    }

    func currentMatch() -> Match? {
        return teamScores().compactMap { $0.matchObject() }.first(where: { $0.isRunning() })
    }

    func teamScores() -> [TeamScore] {
        guard let tournamentStore = self.tournamentStore else { return [] }
        return tournamentStore.teamScores.filter({ $0.teamRegistration == id })
    }

    func wins() -> [Match] {
        guard let tournamentStore = self.tournamentStore else { return [] }
        return tournamentStore.matches.filter({ $0.winningTeamId == id })
    }

    func loses() -> [Match] {
        guard let tournamentStore = self.tournamentStore else { return [] }
        return tournamentStore.matches.filter({ $0.losingTeamId == id })
    }

    func matches() -> [Match] {
        guard let tournamentStore = self.tournamentStore else { return [] }
        return tournamentStore.matches.filter({ $0.losingTeamId == id || $0.winningTeamId == id })
    }

    var tournamentCategory: TournamentCategory {
        tournamentObject()?.tournamentCategory ?? .men
    }

    @objc
    var canonicalName: String {
        players().map { $0.canonicalName }.joined(separator: " ")
    }

    func hasMemberOfClub(_ codeClubOrClubName: String?) -> Bool {
        guard let codeClubOrClubName else { return true }
        return unsortedPlayers().anySatisfy({
            $0.clubName?.contains(codeClubOrClubName) == true
                || $0.clubName?.contains(codeClubOrClubName) == true
        })
    }

    func updateWeight(inTournamentCategory tournamentCategory: TournamentCategory) {
        self.setWeight(from: self.players(), inTournamentCategory: tournamentCategory)
    }

    func teamLabel(
        _ displayStyle: DisplayStyle = .wide, twoLines: Bool = false, separator: String = "&"
    ) -> String {
        if let name { return name }
        return players().map { $0.playerLabel(displayStyle) }.joined(
            separator: twoLines ? "\n" : " \(separator) ")
    }

    func teamLabelRanked(displayRank: Bool, displayTeamName: Bool) -> String {
        [
            displayTeamName ? name : nil, displayRank ? seedIndex() : nil,
            displayTeamName ? (name == nil ? teamLabel() : name) : teamLabel(),
        ].compactMap({ $0 }).joined(separator: " ")
    }

    func seedIndex() -> String? {
        guard let tournament = tournamentObject() else { return nil }
        guard let index = index(in: tournament.selectedSortedTeams()) else { return nil }
        return "(\(index + 1))"
    }

    func index(in teams: [TeamRegistration]) -> Int? {
        return teams.firstIndex(where: { $0.id == id })
    }

    func formattedSeed(in teams: [TeamRegistration]? = nil) -> String {
        let selectedSortedTeams = teams ?? tournamentObject()?.selectedSortedTeams() ?? []
        if let index = index(in: selectedSortedTeams) {
            return "#\(index + 1)"
        } else {
            return "###"
        }
    }

    func contains(_ searchField: String) -> Bool {
        return unsortedPlayers().anySatisfy({ $0.contains(searchField) })
            || self.name?.localizedCaseInsensitiveContains(searchField) == true
    }

    func containsExactlyPlayerLicenses(_ playerLicenses: [String?]) -> Bool {
        let arrayOfIds: [String] = unsortedPlayers().compactMap({
            $0.licenceId?.strippedLicense?.canonicalVersion
        })
        let ids: Set<String> = Set<String>(arrayOfIds.sorted())
        let searchedIds = Set<String>(
            playerLicenses.compactMap({ $0?.strippedLicense?.canonicalVersion }).sorted())
        if ids.isEmpty || searchedIds.isEmpty { return false }
        return ids.hashValue == searchedIds.hashValue
    }

    func includes(players: [PlayerRegistration]) -> Bool {
        let unsortedPlayers = unsortedPlayers()
        guard players.count == unsortedPlayers.count else { return false }
        return players.allSatisfy { player in
            unsortedPlayers.anySatisfy { _player in
                _player.isSameAs(player)
            }
        }
    }

    func includes(player: PlayerRegistration) -> Bool {
        return unsortedPlayers().anySatisfy { _player in
            _player.isSameAs(player)
        }
    }

    func canPlay() -> Bool {
        let unsortedPlayers = unsortedPlayers()
        if unsortedPlayers.isEmpty { return false }

        return matches().isEmpty == false
            || unsortedPlayers.allSatisfy({ $0.hasPaid() || $0.hasArrived })
    }

    func availableForSeedPick() -> Bool {
        return groupStage == nil && bracketPosition == nil
    }

    func inGroupStage() -> Bool {
        return groupStagePosition != nil
    }

    func inRound() -> Bool {
        return bracketPosition != nil
    }

    func positionLabel() -> String? {
        if groupStagePosition != nil { return "Poule" }
        if let initialRound = initialRound() {
            return initialRound.roundTitle()
        } else {
            return nil
        }
    }

    func initialRoundColor() -> Color? {
        if walkOut { return Color.logoRed }
        if groupStagePosition != nil || wildCardGroupStage { return Color.blue }
        if let initialRound = initialRound(), let colorHex = RoundRule.colors[safe: initialRound.index] {
            return Color(uiColor: .init(fromHex: colorHex))
        } else if wildCardBracket {
            return Color.mint
        } else {
            return nil
        }
    }

    func resetGroupeStagePosition() {
        guard let tournamentStore = self.tournamentStore else { return }
        if let groupStage {
            let matches = tournamentStore.matches.filter({ $0.groupStage == groupStage }).map {
                $0.id
            }
            let teamScores = tournamentStore.teamScores.filter({
                $0.teamRegistration == id && matches.contains($0.match)
            })
            tournamentStore.teamScores.delete(contentOfs: teamScores)
        }
        //groupStageObject()?._matches().forEach({ $0.updateTeamScores() })
        groupStage = nil
        groupStagePosition = nil
    }

    func resetBracketPosition() {
        guard let tournamentStore = self.tournamentStore else { return }
        let matches = tournamentStore.matches.filter({ $0.groupStage == nil }).map { $0.id }
        let teamScores = tournamentStore.teamScores.filter({
            $0.teamRegistration == id && matches.contains($0.match)
        })
        tournamentStore.teamScores.delete(contentOfs: teamScores)

        self.bracketPosition = nil
    }

    func resetPositions() {
        resetGroupeStagePosition()
        resetBracketPosition()
    }

    func pasteData(_ exportFormat: ExportFormat = .rawText, _ index: Int = 0) -> String {
        switch exportFormat {
        case .rawText:
            return [playersPasteData(exportFormat), formattedInscriptionDate(exportFormat), name]
                .compactMap({ $0 }).joined(separator: exportFormat.newLineSeparator())
        case .csv:
            return [
                index.formatted(), playersPasteData(exportFormat),
                isWildCard() ? "WC" : weight.formatted(),
            ].joined(separator: exportFormat.separator())
        }
    }

    var computedRegistrationDate: Date {
        return registrationDate ?? .distantFuture
    }

    func formattedInscriptionDate(_ exportFormat: ExportFormat = .rawText) -> String? {
        guard let registrationDate else { return nil }

        let formattedDate = registrationDate.formatted(
            .dateTime.weekday().day().month().hour().minute())
        let onlineSuffix = hasRegisteredOnline() ? " en ligne" : ""

        switch exportFormat {
        case .rawText:
            return "Inscrit\(onlineSuffix) le \(formattedDate)"
        case .csv:
            return formattedDate
        }
    }

    func formattedSummonDate(_ exportFormat: ExportFormat = .rawText) -> String? {

        switch exportFormat {
        case .rawText:
            if let callDate {
                return "Convoqué le "
                    + callDate.formatted(.dateTime.weekday().day().month().hour().minute())
            } else {
                return nil
            }
        case .csv:
            if let callDate {
                return callDate.formatted(.dateTime.weekday().day().month().hour().minute())
            } else {
                return nil
            }
        }
    }

    func playersPasteData(_ exportFormat: ExportFormat = .rawText) -> String {
        switch exportFormat {
        case .rawText:
            return players().map { $0.pasteData(exportFormat) }.joined(
                separator: exportFormat.newLineSeparator())
        case .csv:
            return players().map {
                [$0.pasteData(exportFormat), isWildCard() ? "WC" : $0.computedRank.formatted()]
                    .joined(separator: exportFormat.separator())
            }.joined(separator: exportFormat.separator())
        }
    }

    func updatePlayers(
        _ players: Set<PlayerRegistration>,
        inTournamentCategory tournamentCategory: TournamentCategory
    ) {
        let previousPlayers = Set(unsortedPlayers())

        players.forEach { player in
            previousPlayers.forEach { oldPlayer in
                if player.licenceId?.strippedLicense == oldPlayer.licenceId?.strippedLicense,
                    player.licenceId?.strippedLicense != nil
                {
                    player.registeredOnline = oldPlayer.registeredOnline
                    player.coach = oldPlayer.coach
                    player.tournamentPlayed = oldPlayer.tournamentPlayed
                    player.points = oldPlayer.points
                    player.captain = oldPlayer.captain
                    player.assimilation = oldPlayer.assimilation
                    player.ligueName = oldPlayer.ligueName
                }
            }
        }

        let playersToRemove = previousPlayers.subtracting(players)
        self.tournamentStore?.playerRegistrations.delete(contentOfs: Array(playersToRemove))
        setWeight(from: Array(players), inTournamentCategory: tournamentCategory)

        players.forEach { player in
            player.teamRegistration = id
        }

        //        do {
        //            try self.tournamentStore.playerRegistrations.addOrUpdate(contentOfs: players)
        //        } catch {
        //            Logger.error(error)
        //        }
    }

    typealias TeamRange = (left: TeamRegistration?, right: TeamRegistration?)

    func replacementRange() -> TeamRange? {
        guard let tournamentObject = tournamentObject() else { return nil }
        guard let index = tournamentObject.indexOf(team: self) else { return nil }
        let selectedSortedTeams = tournamentObject.selectedSortedTeams()
        let left = selectedSortedTeams[safe: index - 1]
        let right = selectedSortedTeams[safe: index + 1]
        return (left: left, right: right)
    }

    func replacementRangeExtended() -> TeamRange? {
        guard let tournamentObject = tournamentObject() else { return nil }
        guard let groupStagePosition else { return nil }
        let selectedSortedTeams = tournamentObject.selectedSortedTeams()
        var left: TeamRegistration? = nil
        if groupStagePosition == 0 {
            left = tournamentObject.seeds().last
        } else {
            let previousHat = selectedSortedTeams.filter({
                $0.groupStagePosition == groupStagePosition - 1
            }).sorted(by: \.weight)
            left = previousHat.last
        }
        var right: TeamRegistration? = nil
        if groupStagePosition == tournamentObject.teamsPerGroupStage - 1 {
            right = nil
        } else {
            let previousHat = selectedSortedTeams.filter({
                $0.groupStagePosition == groupStagePosition + 1
            }).sorted(by: \.weight)
            right = previousHat.first
        }
        return (left: left, right: right)
    }

    typealias AreInIncreasingOrder = (PlayerRegistration, PlayerRegistration) -> Bool

    func players() -> [PlayerRegistration] {

        self.unsortedPlayers().sorted { (lhs, rhs) in
            let predicates: [AreInIncreasingOrder] = [
                { $0.sex?.rawValue ?? 0 < $1.sex?.rawValue ?? 0 },
                { $0.rank ?? Int.max < $1.rank ?? Int.max },
                { $0.lastName < $1.lastName },
                { $0.firstName < $1.firstName },
            ]

            for predicate in predicates {
                if !predicate(lhs, rhs) && !predicate(rhs, lhs) {
                    continue
                }

                return predicate(lhs, rhs)
            }

            return false
        }
    }

    func coaches() -> [PlayerRegistration] {
        guard let store = self.tournamentStore else { return [] }
        return store.playerRegistrations.filter { $0.coach }
    }

    func setWeight(
        from players: [PlayerRegistration],
        inTournamentCategory tournamentCategory: TournamentCategory
    ) {
        let significantPlayerCount = significantPlayerCount()
        let sortedPlayers = players.sorted(by: \.computedRank, order: .ascending)
        weight = (sortedPlayers.prefix(significantPlayerCount).map { $0.computedRank } + missingPlayerType(inTournamentCategory: tournamentCategory).map { unrankValue(for: $0 == 1 ? true : false ) }).prefix(significantPlayerCount).reduce(0,+)
    }

    func significantPlayerCount() -> Int {
        return tournamentObject()?.significantPlayerCount() ?? 2
    }

    func missingPlayerType(inTournamentCategory tournamentCategory: TournamentCategory) -> [Int] {
        let players = unsortedPlayers()
        if players.count >= 2 { return [] }
        let s = players.compactMap { $0.sex?.rawValue }
        var missing = tournamentCategory.mandatoryPlayerType()
        s.forEach { i in
            if let index = missing.firstIndex(of: i) {
                missing.remove(at: index)
            }
        }
        return missing
    }

    func unrankValue(for malePlayer: Bool) -> Int {
        return tournamentObject()?.unrankValue(for: malePlayer) ?? 90_000
    }

    func groupStageObject() -> GroupStage? {
        guard let groupStage else { return nil }
        return self.tournamentStore?.groupStages.findById(groupStage)
    }

    func initialRound() -> Round? {
        guard let bracketPosition else { return nil }
        let roundIndex = RoundRule.roundIndex(fromMatchIndex: bracketPosition / 2)
        return self.tournamentStore?.rounds.first(where: { $0.index == roundIndex })
    }

    func initialMatch() -> Match? {
        guard let bracketPosition else { return nil }
        guard let initialRoundObject = initialRound() else { return nil }
        return self.tournamentStore?.matches.first(where: {
            $0.round == initialRoundObject.id && $0.index == bracketPosition / 2
        })
    }

    func toggleSummonConfirmation() {
        if confirmationDate == nil { confirmationDate = Date() } else { confirmationDate = nil }
    }

    func didConfirmSummon() -> Bool {
        confirmationDate != nil
    }

    func tournamentObject() -> Tournament? {
        return Store.main.findById(tournament)
    }

    func groupStagePositionAtStep(_ step: Int) -> Int? {
        guard let groupStagePosition else { return nil }
        if step == 0 {
            return groupStagePosition
        } else if let groupStageObject = groupStageObject(), groupStageObject.hasEnded() {
            return groupStageObject.index
        }
        return nil
    }

    func wildcardLabel() -> String? {
        if isWildCard() {
            let wildcardLabel: String = ["Wildcard", (wildCardBracket ? "Tableau" : "Poule")].joined(separator: " ")
            return wildcardLabel
        } else {
            return nil
        }
    }

    var _cachedRestingTime: (Bool, Date?)?

    func restingTime() -> Date? {
        if let _cachedRestingTime { return _cachedRestingTime.1 }
        let restingTime = matches().filter({ $0.hasEnded() }).sorted(
            by: \.computedEndDateForSorting
        ).last?.endDate
        _cachedRestingTime = (true, restingTime)
        return restingTime
    }

    func resetRestingTime() {
        _cachedRestingTime = nil
    }

    var restingTimeForSorting: Date {
        restingTime()!
    }

    func teamNameLabel() -> String {
        if let name, name.isEmpty == false {
            return name
        } else {
            return "Toute l'équipe"
        }
    }

    func isDifferentPosition(_ drawMatchIndex: Int?) -> Bool {
        if let bracketPosition, let drawMatchIndex {
            return drawMatchIndex != bracketPosition
        } else if bracketPosition != nil {
            return true
        } else if drawMatchIndex != nil {
            return true
        }
        return false
    }

    func shouldDisplayRankAndWeight() -> Bool {
        unsortedPlayers().count > 0
    }
    
    func teamInitialPositionBracket() -> String? {
        let round = initialMatch()?.roundAndMatchTitle()
        if let round {
            return round
        }
        return nil
    }

    func teamInitialPositionGroupStage() -> String? {
        let groupStage = self.groupStageObject()
        let group = groupStage?.groupStageTitle(.title)
        var groupPositionLabel: String? = nil
        if let finalPosition = groupStage?.finalPosition(ofTeam: self) {
            groupPositionLabel = (finalPosition + 1).ordinalFormatted()
        } else if let groupStagePosition {
            groupPositionLabel = "\(groupStagePosition + 1)"
        }

        if let group {
            if let groupPositionLabel {
                return [group, "#\(groupPositionLabel)"].joined(separator: " ")
            } else {
                return group
            }
        }
        return nil
    }

    func qualifiedStatus(hideBracketStatus: Bool = false) -> String? {
        let teamInitialPositionBracket = teamInitialPositionBracket()
        let groupStageTitle = teamInitialPositionGroupStage()

        let base: String? = qualified ? "Qualifié" : nil
        if let groupStageTitle, let teamInitialPositionBracket, hideBracketStatus == false {
            return [base, groupStageTitle, ">", teamInitialPositionBracket].compactMap({ $0 }).joined(separator: " ")
        } else if let groupStageTitle {
            return [base, groupStageTitle].compactMap({ $0 }).joined(separator: " ")
        } else if hideBracketStatus == false {
            return teamInitialPositionBracket
        }

        return nil
    }

    func insertOnServer() {
        self.tournamentStore?.teamRegistrations.writeChangeAndInsertOnServer(instance: self)
        for playerRegistration in self.unsortedPlayers() {
            playerRegistration.insertOnServer()
        }
    }

}

enum TeamDataSource: Int, Codable {
    case beachPadel
}
