//
//  AddTeamView.swift
//  PadelClub
//
//  Created by Razmig Sarkissian on 15/07/2024.
//

import SwiftUI
import LeStorage

struct AddTeamView: View {
    
    @EnvironmentObject var dataStore: DataStore
    @Environment(\.dismiss) var dismiss

    @FetchRequest(
        sortDescriptors: [],
        animation: .default)
    private var fetchPlayers: FetchedResults<ImportedPlayer>

    var tournament: Tournament
    var cancelShouldDismiss: Bool = false

    @State private var searchField: String = ""
    @State private var presentSearch: Bool = false
    @State private var presentPlayerSearch: Bool = false
    @State private var presentPlayerCreation: Bool = false
    @State private var presentImportView: Bool = false
    @State private var createdPlayers: Set<PlayerRegistration> = Set()
    @State private var createdPlayerIds: Set<String> = Set()
    @State private var editedTeam: TeamRegistration?
    @State private var pasteString: String?
    @State private var selectionSearchField: String?
    @State private var autoSelect: Bool = false
    @State private var presentationCount: Int = 0
    @State private var confirmDuplicate: Bool = false
    
    var tournamentStore: TournamentStore {
        return self.tournament.tournamentStore
    }

    init(tournament: Tournament, pasteString: String? = nil, editedTeam: TeamRegistration? = nil) {
        self.tournament = tournament
        _editedTeam = .init(wrappedValue: editedTeam)
        if let team = editedTeam {
            var createdPlayers: Set<PlayerRegistration> = Set()
            var createdPlayerIds: Set<String> = Set()

            team.unsortedPlayers().forEach { player in
                createdPlayers.insert(player)
                createdPlayerIds.insert(player.id)
            }
            
            _createdPlayers = .init(wrappedValue: createdPlayers)
            _createdPlayerIds = .init(wrappedValue: createdPlayerIds)
        }
        
        if let pasteString {
            _pasteString = .init(wrappedValue: pasteString)
            _fetchPlayers = FetchRequest<ImportedPlayer>(sortDescriptors: [NSSortDescriptor(keyPath: \ImportedPlayer.rank, ascending: true)], predicate: Self._pastePredicate(pasteField: pasteString, mostRecentDate: tournament.rankSourceDate, filterOption: tournament.tournamentCategory.playerFilterOption))
            _autoSelect = .init(wrappedValue: true)
            cancelShouldDismiss = true
        }
    }
    
    var body: some View {
        _buildingTeamView()
            .navigationBarBackButtonHidden(true)
        .alert("Cette équipe existe déjà", isPresented: $confirmDuplicate) {
            Button("Créer l'équipe quand même") {
                _createTeam(checkDuplicates: false)
            }
            
            Button("Annuler", role: .cancel) {
                confirmDuplicate = false
            }

        } message: {
            Text("Cette équipe existe déjà dans votre liste d'inscription.")
        }
        .sheet(isPresented: $presentPlayerSearch, onDismiss: {
            selectionSearchField = nil
        }) {
            NavigationStack {
                SelectablePlayerListView(allowSelection: -1, filterOption: _filterOption(), showFemaleInMaleAssimilation: tournament.tournamentCategory.showFemaleInMaleAssimilation) { players in
                    selectionSearchField = nil
                    players.forEach { player in
                        let newPlayer = PlayerRegistration(importedPlayer: player)
                        newPlayer.setComputedRank(in: tournament)
                        createdPlayers.insert(newPlayer)
                        createdPlayerIds.insert(newPlayer.id)
                    }
                } contentUnavailableAction: { searchViewModel in
                    selectionSearchField = searchViewModel.searchText
                    presentPlayerSearch = false
                    presentPlayerCreation = true
                }
            }
            .tint(.master)
        }
        .sheet(isPresented: $presentPlayerCreation) {
            PlayerPopoverView(source: _searchSource(), sex: _addPlayerSex()) { p in
                p.setComputedRank(in: tournament)
                createdPlayers.insert(p)
                createdPlayerIds.insert(p.id)
            }
            .tint(.master)
        }
        .navigationTitle(editedTeam == nil ? "Ajouter une équipe" : "Modifier l'équipe")
        .toolbar {
            ToolbarItem(placement: .cancellationAction) {
                Button("Annuler", role: .cancel) {
                    dismiss()
                }
            }

            if pasteString == nil {
                ToolbarItem(placement: .topBarTrailing) {
                    PasteButton(payloadType: String.self) { strings in
                        guard let first = strings.first else { return }
                        Task {
                            await MainActor.run {
                                fetchPlayers.nsPredicate = Self._pastePredicate(pasteField: first, mostRecentDate: SourceFileManager.shared.mostRecentDateAvailable, filterOption: _filterOption())
                                fetchPlayers.nsSortDescriptors = [NSSortDescriptor(keyPath: \ImportedPlayer.rank, ascending: true)]
                                pasteString = first
                                autoSelect = true
                            }
                        }
                    }
                    .foregroundStyle(.master)
                    .labelStyle(.iconOnly)
                    .buttonBorderShape(.capsule)
                }
            }
        }
        .navigationBarBackButtonHidden(_isEditingTeam())
        .toolbarBackground(.visible, for: .navigationBar)
        .navigationBarTitleDisplayMode(.inline)
    }
    
    private func _isEditingTeam() -> Bool {
        createdPlayerIds.isEmpty == false || editedTeam != nil || pasteString != nil
    }
    
    var unsortedPlayers: [PlayerRegistration] {
        tournament.unsortedPlayers()
    }
    
    @ViewBuilder
    private func _managementView() -> some View {
        Section {
            RowButtonView("Rechercher dans la base fédérale") {
                presentPlayerSearch = true
            }
        } footer: {
            if let rankSourceDate = tournament.rankSourceDate {
                Text("Cherchez dans la base fédérale de \(rankSourceDate.monthYearFormatted), vous y trouverez tous les joueurs ayant participé à au moins un tournoi dans les 12 derniers mois.")
            }
        }
        
        Section {
            RowButtonView("Créer un non classé / non licencié") {
                presentPlayerCreation = true
            }
        } footer: {
            Text("Si le joueur n'a pas encore de licence ou n'a pas encore participé à une compétition, vous pouvez le créer vous-même.")
        }
    }
    
    private func _addPlayerSex() -> Int {
        switch tournament.tournamentCategory {
        case .men, .unlisted:
            return 1
        case .women:
            return 0
        case .mix:
            return 1
        }

    }
    
    private func _filterOption() -> PlayerFilterOption {
        return tournament.tournamentCategory.playerFilterOption
    }
    
    private func _searchSource() -> String? {
        selectionSearchField ?? pasteString
    }
    
    static private func _pastePredicate(pasteField: String, mostRecentDate: Date?, filterOption: PlayerFilterOption) -> NSPredicate? {
        let text = pasteField.canonicalVersion

        let textStrings: [String] = text.components(separatedBy: .whitespacesAndNewlines)
        let nonEmptyStrings: [String] = textStrings.compactMap { $0.isEmpty ? nil : $0 }
        let nameComponents = nonEmptyStrings.filter({ $0 != "de" && $0 != "la" && $0 != "le" && $0.count > 1 })
        var andPredicates = [NSPredicate]()
        var orPredicates = [NSPredicate]()
        //self.wordsCount = nameComponents.count

        
        if filterOption == .male {
            andPredicates.append(NSPredicate(format: "male == YES"))
        } else if filterOption == .female {
            andPredicates.append(NSPredicate(format: "male == NO"))
        }
        
        if let mostRecentDate {
            andPredicates.append(NSPredicate(format: "importDate == %@", mostRecentDate as CVarArg))
        }

        if nameComponents.count > 1 {
            orPredicates = nameComponents.pairs().map {
                return NSPredicate(format: "(firstName BEGINSWITH[cd] %@ AND lastName BEGINSWITH[cd] %@) OR (firstName BEGINSWITH[cd] %@ AND lastName BEGINSWITH[cd] %@)", $0, $1, $1, $0) }
        } else {
            orPredicates = nameComponents.map { NSPredicate(format: "firstName contains[cd] %@ OR lastName contains[cd] %@", $0,$0) }
        }
        
        let components = text.split(separator: " ").sorted()
        let pattern = components.joined(separator: ".*")
        let canonicalFullNamePredicate = NSPredicate(format: "canonicalFullName MATCHES[c] %@", pattern)
        orPredicates.append(canonicalFullNamePredicate)
        
        let matches = text.licencesFound()
        let licensesPredicates = matches.map { NSPredicate(format: "license contains[cd] %@", $0) }
        orPredicates = orPredicates + licensesPredicates
        
        var predicate = NSCompoundPredicate(andPredicateWithSubpredicates: andPredicates)
        
        if orPredicates.isEmpty == false {
            predicate = NSCompoundPredicate(andPredicateWithSubpredicates: [predicate, NSCompoundPredicate(orPredicateWithSubpredicates: orPredicates)])
        }
        
        return predicate
    }
    
    private func _currentSelection() -> Set<PlayerRegistration> {
        var currentSelection = Set<PlayerRegistration>()
        createdPlayerIds.compactMap { id in
            fetchPlayers.first(where: { id == $0.license })
        }.forEach { player in
            let player = PlayerRegistration(importedPlayer: player)
            player.setComputedRank(in: tournament)
            currentSelection.insert(player)
        }
        
        createdPlayerIds.compactMap { id in
            createdPlayers.first(where: { id == $0.id })
        }.forEach {
            currentSelection.insert($0)
        }
        return currentSelection
    }

    private func _currentSelectionIds() -> [String?] {
        var currentSelection = [String?]()
        createdPlayerIds.compactMap { id in
            fetchPlayers.first(where: { id == $0.license })
        }.forEach { player in
            currentSelection.append(player.license)
        }
        
        createdPlayerIds.compactMap { id in
            createdPlayers.first(where: { id == $0.id })
        }.forEach {
            currentSelection.append($0.licenceId)
        }
        return currentSelection
    }
    
    private func _isDuplicate() -> Bool {
        let ids : [String?] = _currentSelectionIds()
        if tournament.selectedSortedTeams().anySatisfy({ $0.containsExactlyPlayerLicenses(ids) }) {
            return true
        }
        return false
    }
    
    private func _createTeam(checkDuplicates: Bool) {
        if checkDuplicates && _isDuplicate() {
            confirmDuplicate = true
            return
        }

        let players = _currentSelection()
        let team = tournament.addTeam(players)
        do {
            try self.tournamentStore.teamRegistrations.addOrUpdate(instance: team)
        } catch {
            Logger.error(error)
        }
        do {
            try self.tournamentStore.playerRegistrations.addOrUpdate(contentOfs: players)
        } catch {
            Logger.error(error)
        }

        createdPlayers.removeAll()
        createdPlayerIds.removeAll()
        pasteString = nil
        dismiss()
    }
    
    private func _updateTeam(checkDuplicates: Bool) {
        guard let editedTeam else { return }
        if checkDuplicates && _isDuplicate() {
            confirmDuplicate = true
            return
        }

        let players = _currentSelection()
        editedTeam.updatePlayers(players, inTournamentCategory: tournament.tournamentCategory)
        do {
            try self.tournamentStore.teamRegistrations.addOrUpdate(instance: editedTeam)
        } catch {
            Logger.error(error)
        }
        do {
            try self.tournamentStore.playerRegistrations.addOrUpdate(contentOfs: players)
        } catch {
            Logger.error(error)
        }
        createdPlayers.removeAll()
        createdPlayerIds.removeAll()
        pasteString = nil
        self.editedTeam = nil
        dismiss()
    }
    
    private func _buildingTeamView() -> some View {
        List(selection: $createdPlayerIds) {
            if let pasteString {
                
                Section {
                    Text(pasteString)
                } footer: {
                    HStack {
                        Text("contenu du presse-papier")
                        Spacer()
                        Button("effacer", role: .destructive) {
                            self.pasteString = nil
                            self.createdPlayers.removeAll()
                            self.createdPlayerIds.removeAll()
                        }
                        .buttonStyle(.borderless)
                    }
                }
            }
            
            Section {
                ForEach(createdPlayerIds.sorted(), id: \.self) { id in
                    if let p = createdPlayers.first(where: { $0.id == id }) {
                        VStack(alignment: .leading, spacing: 0) {
                            if let player = unsortedPlayers.first(where: { $0.licenceId == p.licenceId }), editedTeam?.includes(player: player) == false {
                                Text("Déjà inscrit !").foregroundStyle(.logoRed).bold()
                            }
                            PlayerView(player: p).tag(p.id)
                        }
                    }
                    if let p = fetchPlayers.first(where: { $0.license == id }) {
                        VStack(alignment: .leading, spacing: 0) {
                            if unsortedPlayers.first(where: { $0.licenceId == p.license }) != nil {
                                Text("Déjà inscrit !").foregroundStyle(.logoRed).bold()
                            }
                            ImportedPlayerView(player: p).tag(p.license!)
                        }
                    }
                }
            } header: {
                let _currentSelection = _currentSelection()
                let selectedSortedTeams = tournament.selectedSortedTeams()
                let rank = _currentSelection.map {
                    $0.computedRank
                }.reduce(0, +)
                let teamIndex = selectedSortedTeams.firstIndex(where: { $0.weight >= rank }) ?? selectedSortedTeams.count
                if _currentSelection.isEmpty == false, tournament.hideWeight() == false, rank > 0 {
                    HStack(spacing: 16.0) {
                        VStack(alignment: .leading, spacing: 0) {
                            Text("Rang").font(.caption)
                            Text("#" + (teamIndex + 1).formatted())
                        }

                        VStack(alignment: .leading, spacing: 0) {
                            Text("Poids").font(.caption)
                            Text(rank.formatted())
                        }
                        Spacer()
                        VStack(alignment: .trailing, spacing: 0) {
                            Text("").font(.caption)
                            Text(tournament.cutLabel(index: teamIndex, teamCount: selectedSortedTeams.count))
                        }
                    }
                }
            }

            
            Section {
                if editedTeam == nil {
                    if createdPlayerIds.isEmpty {
                        RowButtonView("Bloquer une place") {
                            _createTeam(checkDuplicates: false)
                        }
                    } else {
                        RowButtonView("Ajouter l'équipe") {
                            _createTeam(checkDuplicates: true)
                        }
                    }
                } else {
                    RowButtonView("Modifier l'équipe") {
                        _updateTeam(checkDuplicates: false)
                        editedTeam = nil
                    }
                }
            }

            if let pasteString {
                if fetchPlayers.isEmpty {
                    ContentUnavailableView {
                        Label("Aucun résultat", systemImage: "person.2.slash")
                    } description: {
                        Text("Aucun joueur classé n'a été trouvé dans ce message.")
                    } actions: {
                        RowButtonView("Créer un joueur non classé") {
                            presentPlayerCreation = true
                        }
                                                
                        RowButtonView("Effacer cette recherche") {
                            self.pasteString = nil
                        }
                    }

                } else {
                    Section {
                        ForEach(fetchPlayers.sorted(by: { $0.hitForSearch(pasteString) > $1.hitForSearch(pasteString) })) { player in
                            ImportedPlayerView(player: player).tag(player.license!)
                        }
                    } header: {
                        Text(fetchPlayers.count.formatted() + " résultat" + fetchPlayers.count.pluralSuffix)
                    }
                }
            } else {
                _managementView()
            }
        }
        .headerProminence(.increased)
        .onReceive(fetchPlayers.publisher.count()) { _ in  // <-- here
            if let pasteString, count == 2, autoSelect == true {
                fetchPlayers.filter { $0.hitForSearch(pasteString) >= hitTarget }.sorted(by: { $0.hitForSearch(pasteString) > $1.hitForSearch(pasteString) }).forEach { player in
                    createdPlayerIds.insert(player.license!)
                }
                autoSelect = false
            }
        }
        .environment(\.editMode, Binding.constant(EditMode.active))
    }
    
    private var count: Int {
        return fetchPlayers.filter { $0.hitForSearch(pasteString ?? "") >= hitTarget }.count
    }

    private var hitTarget: Int {
        if (pasteString?.matches(of: /[1-9][0-9]{5,7}/).count ?? 0) > 1 {
            if fetchPlayers.filter({ $0.hitForSearch(pasteString ?? "") == 100 }).count == 2 { return 100 }
        } else {
            return 2
        }
        return 1
    }
    
    private func _save() {
        do {
            try dataStore.tournaments.addOrUpdate(instance: tournament)
        } catch {
            Logger.error(error)
        }
    }
}
