//
//  PlanningSettingsView.swift
//  PadelClub
//
//  Created by Razmig Sarkissian on 07/04/2024.
//

import SwiftUI
import LeStorage

struct PlanningSettingsView: View {

    @EnvironmentObject var dataStore: DataStore

    @Bindable var tournament: Tournament
    @Bindable var matchScheduler: MatchScheduler

    @State private var groupStageChunkCount: Int
    @State private var isScheduling: Bool = false
    @State private var schedulingDone: Bool = false
    @State private var showOptions: Bool = false
    @State private var issueFound: Bool = false
    @State private var parallelType: Bool = false
    @State private var deletingDateMatchesDone: Bool = false
    @State private var deletingDone: Bool = false
    @State private var presentFormatHelperView: Bool = false

    var tournamentStore: TournamentStore? {
        return self.tournament.tournamentStore
    }

    init(tournament: Tournament) {
        self.tournament = tournament
        if let matchScheduler = tournament.matchScheduler() {
            self.matchScheduler = matchScheduler
            if matchScheduler.groupStageChunkCount != nil {
                _parallelType = .init(wrappedValue: true)
                _groupStageChunkCount = State(wrappedValue: matchScheduler.groupStageChunkCount!)
            } else {
                _groupStageChunkCount = State(wrappedValue: tournament.getGroupStageChunkValue())
            }
        } else {
            self.matchScheduler = MatchScheduler(tournament: tournament.id, courtsAvailable: Set(tournament.courtsAvailable()))
            self._groupStageChunkCount = State(wrappedValue: tournament.getGroupStageChunkValue())
        }
    }

    var body: some View {
        List {
            if tournament.payment == nil {
                SubscriptionInfoView()
            }

            Section {
                DatePicker(selection: $tournament.startDate) {
                    Text(tournament.startDate.formatted(.dateTime.weekday(.wide)).capitalized).lineLimit(1)
                }
                LabeledContent {
                    StepperView(count: $tournament.dayDuration, minimum: 1)
                } label: {
                    Text("Durée")
                    Text("\(tournament.dayDuration) jour" + tournament.dayDuration.pluralSuffix)
                }

                TournamentFieldsManagerView(localizedStringKey: "Terrains maximum", count: $tournament.courtCount)

                if let event = tournament.eventObject() {
                    NavigationLink {
                        CourtAvailabilitySettingsView(event: event)
                            .environment(tournament)
                    } label: {
                        LabeledContent {
                            Text(event.courtsUnavailability.count.formatted())
                        } label: {
                            Text("Créneaux d'indisponibilités")
                        }
                    }
                }

                NavigationLink {
                    MultiCourtPickerView(matchScheduler: matchScheduler)
                        .environment(tournament)
                } label: {
                    LabeledContent {
                        Text(matchScheduler.courtsAvailable.count.formatted() + "/" + tournament.courtCount.formatted())
                    } label: {
                        Text("Sélection des terrains")
                        if matchScheduler.courtsAvailable.count >  tournament.courtCount {
                            Text("Attention !")
                                .tint(.red)
                        }
                    }
                }
            } footer: {
                if let club = tournament.club() {
                    if tournament.courtCount < club.courtCount {
                        let plural = tournament.courtCount.pluralSuffix
                        let verb = tournament.courtCount > 1 ? "seront" : "sera"
                        Text("En réduisant les terrains maximum, seul\(plural) le\(plural) \(tournament.courtCount) premier\(plural) terrain\(plural) \(verb) utilisé\(plural)") + Text(", par contre, si vous gardez le nombre de terrains du club, vous pourrez plutôt préciser quel terrain n'est pas disponible.")
                    } else if tournament.courtCount > club.courtCount {
                        let isCreatedByUser = club.hasBeenCreated(by: StoreCenter.main.userId)
                        Button {
                            do {
                                club.courtCount = tournament.courtCount
                                try dataStore.clubs.addOrUpdate(instance: club)
                            } catch {
                                Logger.error(error)
                            }
                        } label: {
                            if isCreatedByUser {
                                Text("Vous avez indiqué plus de terrains dans ce tournoi que dans le club. ")
                                + Text("Mettre à jour le club ?").underline().foregroundStyle(.master)
                            } else {
                                Label("Vous avez indiqué plus de terrains dans ce tournoi que dans le club.", systemImage: "exclamationmark.triangle.fill").foregroundStyle(.logoRed)
                            }
                        }
                        .buttonStyle(.plain)
                        .disabled(isCreatedByUser == false)
                    }
                }
            }

            if issueFound {
                Text("Padel Club n'a pas réussi à définir un horaire pour tous les matchs de ce tournoi, à cause de la programmation d'autres tournois ou de l'indisponibilité des terrains.")
                    .foregroundStyle(.logoRed)
            }

            let event = tournament.eventObject()
            Section {
                NavigationLink {
                    _optionsView()
                } label: {
                    Text("Voir plus d'options intelligentes")
                }

                if let event, event.tournaments.count > 1 {
                    Toggle(isOn: $matchScheduler.overrideCourtsUnavailability) {
                        Text("Ne pas tenir compte des autres tournois")
                    }
                }
            } footer: {
                if let event, event.tournaments.count > 1 {
                    Text("Cette option fait en sorte qu'un terrain pris par un match d'un autre tournoi de cet événement soit toujours considéré comme libre.")
                }
            }

            _smartView()
        }
        .navigationTitle("Réglages")
        .toolbar {
            ToolbarItem(placement: .topBarTrailing) {
                Button {
                    presentFormatHelperView = true
                } label: {
                    Text("Aide-mémoire")
                }
            }
        }
        .sheet(isPresented: $presentFormatHelperView) {
            NavigationStack {
                MatchFormatGuideView()
                    .toolbar {
                        ToolbarItem(placement: .topBarLeading) {
                            Button("Retour", role: .cancel) {
                                presentFormatHelperView = false
                            }
                        }
                    }
            }
        }
        .headerProminence(.increased)
        .onAppear {
            do {
                try self.tournamentStore?.matchSchedulers.addOrUpdate(instance: matchScheduler)
            } catch {
                Logger.error(error)
            }
        }
        .overlay(alignment: .bottom) {
            if schedulingDone {
                if issueFound {
                    Label("Horaires mis à jour", systemImage: "xmark.circle.fill")
                        .toastFormatted()
                        .deferredRendering(for: .seconds(2))
                } else {
                    Label("Horaires mis à jour", systemImage: "checkmark.circle.fill")
                        .toastFormatted()
                        .deferredRendering(for: .seconds(2))
                }
            }

            if deletingDone {
                Label("Tous les horaires ont été supprimés", systemImage: "clock.badge.xmark")
                    .toastFormatted()
                    .deferredRendering(for: .seconds(2))
            }

            if deletingDateMatchesDone {
                Label("Horaires des matchs supprimés", systemImage: "clock.badge.xmark")
                    .toastFormatted()
                    .deferredRendering(for: .seconds(2))
            }
        }
        .onChange(of: tournament.startDate) {
            _save()
        }
        .onChange(of: tournament.courtCount) {
            matchScheduler.courtsAvailable = Set(tournament.courtsAvailable())
            _save()
        }
        .onChange(of: tournament.dayDuration) {
            _save()
        }
    }

    private func _localizedFooterMessage(groupStagesWithDateIsEmpty: Bool, roundsWithDateIsEmpty: Bool) -> String {
        let base = "Supprime les horaires des matchs restants non démarrés."
        let extend = " Garde les horaires définis pour les "
        if groupStagesWithDateIsEmpty && roundsWithDateIsEmpty {
            return base
        } else if groupStagesWithDateIsEmpty, roundsWithDateIsEmpty == false {
            return base + extend + "manches du tableau."
        } else if roundsWithDateIsEmpty, groupStagesWithDateIsEmpty == false {
            return base + extend + "poules."
        } else {
            return base + extend + "poules et les manches du tableau."
        }
    }

    @ViewBuilder
    private func _smartView() -> some View {
        let allMatches = tournament.allMatches().filter({ $0.hasEnded() == false && $0.hasStarted() == false })
        let allGroupStages = tournament.allGroupStages()
        let allRounds = tournament.allRounds()
        let matchesWithDate = allMatches.filter({ $0.startDate != nil })

        let groupMatchesByDay = _groupMatchesByDay(matches: matchesWithDate)

        let countedSet = _matchCountPerDay(matchesByDay: groupMatchesByDay, tournament: tournament)

        _formatPerDayView(matchCountPerDay: countedSet)

        let groupStagesWithDate = allGroupStages.filter({ $0.startDate != nil })
        let roundsWithDate = allRounds.filter({ $0.startDate != nil })
        if matchesWithDate.isEmpty == false {
            Section {
                RowButtonView("Supprimer les horaires des matches", role: .destructive) {
                    do {
                        deletingDateMatchesDone = false
                        allMatches.forEach({
                            $0.startDate = nil
                            $0.confirmed = false
                        })
                        try self.tournamentStore?.matches.addOrUpdate(contentOfs: allMatches)
                        deletingDateMatchesDone = true
                    } catch {
                        Logger.error(error)
                    }
                }
            } footer: {
                Text(_localizedFooterMessage(groupStagesWithDateIsEmpty: groupStagesWithDate.isEmpty, roundsWithDateIsEmpty: roundsWithDate.isEmpty))
            }
        }

        if groupStagesWithDate.isEmpty == false {
            Section {
                RowButtonView("Supprimer les horaires des poules", role: .destructive) {
                    do {
                        deletingDone = false
                        allGroupStages.forEach({ $0.startDate = nil })
                        try self.tournamentStore?.groupStages.addOrUpdate(contentOfs: allGroupStages)

                        deletingDone = true
                    } catch {
                        Logger.error(error)
                    }
                }
            }
        }

        if roundsWithDate.isEmpty == false {
            Section {
                RowButtonView("Supprimer les horaires du tableau", role: .destructive) {
                    do {
                        deletingDone = false
                        allRounds.forEach({ $0.startDate = nil })
                        try self.tournamentStore?.rounds.addOrUpdate(contentOfs: allRounds)
                        deletingDone = true
                    } catch {
                        Logger.error(error)
                    }
                }
            } footer: {
                Text("Supprime les horaires définis pour les manches du tableau.")
            }
        }

        if matchesWithDate.isEmpty == false && groupStagesWithDate.isEmpty == false && roundsWithDate.isEmpty == false {
            Section {
                RowButtonView("Supprimer tous les horaires", role: .destructive) {
                    do {
                        deletingDone = false
                        allMatches.forEach({
                            $0.startDate = nil
                            $0.confirmed = false
                        })
                        try self.tournamentStore?.matches.addOrUpdate(contentOfs: allMatches)

                        allGroupStages.forEach({ $0.startDate = nil })
                        try self.tournamentStore?.groupStages.addOrUpdate(contentOfs: allGroupStages)

                        allRounds.forEach({ $0.startDate = nil })
                        try self.tournamentStore?.rounds.addOrUpdate(contentOfs: allRounds)
                        deletingDone = true
                    } catch {
                        Logger.error(error)
                    }
                }
            } footer: {
                Text("Supprime les horaires des matchs restants non démarrés, les horaires définis pour les poules et les manches du tableau.")
            }
        }

        #if DEBUG
        Section {
            RowButtonView("Debug delete all dates", role: .destructive) {
                do {
                    deletingDone = false
                    tournament.allMatches().forEach({
                        $0.startDate = nil
                        $0.endDate = nil
                        $0.confirmed = false
                    })
                    try self.tournamentStore?.matches.addOrUpdate(contentOfs: tournament.allMatches())

                    allGroupStages.forEach({ $0.startDate = nil })
                    try self.tournamentStore?.groupStages.addOrUpdate(contentOfs: allGroupStages)

                    allRounds.forEach({ $0.startDate = nil })
                    try self.tournamentStore?.rounds.addOrUpdate(contentOfs: allRounds)
                    deletingDone = true
                } catch {
                    Logger.error(error)
                }
            }
        } footer: {
            Text("Supprime les horaires des matchs, les horaires définis pour les poules et les manches du tableau.")
        }
        #endif


        Section {
            if groupStagesWithDate.isEmpty == false {
                Text("Des dates de démarrages ont été indiqué pour les poules et seront prises en compte.")
            }
            if roundsWithDate.isEmpty == false {
                Text("Des dates de démarrages ont été indiqué pour les manches et seront prises en compte.")
            }
            RowButtonView("Horaire intelligent", role: .destructive) {
                await MainActor.run {
                    issueFound = false
                    schedulingDone = false
                }
                self.issueFound = await _setupSchedule()
                await MainActor.run {
                    _save()
                    schedulingDone = true
                }
            }
        } footer: {
            Text("Padel Club programmera tous les matchs de votre tournoi en fonction de différents paramètres, ") + Text("tout en tenant compte des horaires que vous avez fixé.").underline()
        }
    }

    @ViewBuilder
    private func _optionsView() -> some View {
        List {
            if tournament.groupStageCount > 0 {
                Section {
                    Picker(selection: $parallelType) {
                        Text("Auto.").tag(false)
                        Text("Manuel").tag(true)
                    } label: {
                        Text("Poules en parallèle")
                        let value = tournament.getGroupStageChunkValue()
                        if parallelType == false {
                            if value > 1 {
                                Text("\(value.formatted()) poules en parallèle")
                            } else {
                                Text("une poule sera jouée à la fois")
                            }
                        }
                    }
                    .onChange(of: parallelType) {
                        if parallelType {
                            groupStageChunkCount = tournament.getGroupStageChunkValue()
                        } else {
                            matchScheduler.groupStageChunkCount = nil
                        }
                    }

                    if parallelType {
                        TournamentFieldsManagerView(localizedStringKey: "Poule en parallèle", count: $groupStageChunkCount, max: tournament.groupStageCount)
                            .onChange(of: groupStageChunkCount) {
                                matchScheduler.groupStageChunkCount = groupStageChunkCount
                            }
                    }
                } footer: {
                    Text("Vous pouvez indiquer le nombre de poule démarrant en même temps.")
                }

                Section {
                    Toggle(isOn: $matchScheduler.simultaneousStart) {
                        Text("Démarrage simultané")
                    }
                } footer: {
                    Text("En simultané, un match de chaque poule d'un groupe de poule sera joué avant de passer à la suite de la programmation. Si l'option est désactivée, un maximum de matchs simultanés d'une poule sera programmé avant de passer à la poule suivante.")
                }
            }

            Section {
                Toggle(isOn: $matchScheduler.randomizeCourts) {
                    Text("Distribuer les terrains au hasard")
                }
            }

            Section {
                Toggle(isOn: $matchScheduler.shouldTryToFillUpCourtsAvailable) {
                    Text("Remplir au maximum les terrains d'une rotation")
                }
            } footer: {
                Text("Tout en tenant compte de l'option ci-dessous, Padel Club essaiera de remplir les créneaux à chaque rotation.")
            }

            Section {
                Toggle(isOn: $matchScheduler.shouldHandleUpperRoundSlice) {
                    Text("Équilibrer les matchs d'une manche")
                }
            } footer: {
                Text("Cette option permet de programmer une manche sur plusieurs rotation de manière équilibrée dans le cas où il y a plus de matchs à jouer dans cette manche que de terrains.")
            }

            Section {
                Toggle(isOn: $matchScheduler.shouldEndRoundBeforeStartingNext) {
                    Text("Finir une manche, classement inclus avant de continuer")
                }
            }

            Section {
                Toggle(isOn: $matchScheduler.accountUpperBracketBreakTime) {
                    Text("Tenir compte des temps de pause réglementaires")
                }
            } header: {
                Text("Tableau")
            }

            Section {
                Toggle(isOn: $matchScheduler.accountLoserBracketBreakTime) {
                    Text("Tenir compte des temps de pause réglementaires")
                }
            } header: {
                Text("Classement")
            }

            Section {
                Toggle(isOn: $matchScheduler.rotationDifferenceIsImportant) {
                    Text("Forcer une rotation d'attente supplémentaire entre 2 phases")
                }

                LabeledContent {
                    StepperView(count: $matchScheduler.upperBracketRotationDifference, minimum: 0, maximum: 2)
                } label: {
                    Text("Tableau")
                }
                .disabled(matchScheduler.rotationDifferenceIsImportant == false)

                LabeledContent {
                    StepperView(count: $matchScheduler.loserBracketRotationDifference, minimum: 0, maximum: 2)
                } label: {
                    Text("Classement")
                }
                .disabled(matchScheduler.rotationDifferenceIsImportant == false)
            } footer: {
                Text("Cette option ajoute du temps entre 2 rotations, permettant ainsi de mieux configurer plusieurs tournois se déroulant en même temps.")
            }

            Section {
                LabeledContent {
                    StepperView(count: $matchScheduler.timeDifferenceLimit, step: 5)
                } label: {
                    Text("Optimisation des créneaux")
                    Text("Si libre plus de \(matchScheduler.timeDifferenceLimit) minutes")
                }
            } footer: {
                Text("Cette option essaie d'optimiser les créneaux disponibles à partir du moment où ils sont à priori libre plus de \(matchScheduler.timeDifferenceLimit) minutes.")

            }

        }
        .navigationTitle("Options avancées")
        .navigationBarTitleDisplayMode(.inline)
        .toolbarBackground(.visible, for: .navigationBar)
    }

    private func _setupSchedule() async -> Bool {
        return matchScheduler.updateSchedule(tournament: tournament)
    }

    private func _save() {
        do {
            try self.tournamentStore?.matchSchedulers.addOrUpdate(instance: matchScheduler)
            try dataStore.tournaments.addOrUpdate(instance: tournament)
        } catch {
            Logger.error(error)
        }
    }

    private func _groupMatchesByDay(matches: [Match]) -> [Date: [Match]] {
        var matchesByDay = [Date: [Match]]()
        let calendar = Calendar.current

        for match in matches {
            // Extract day/month/year and create a date with only these components
            let components = calendar.dateComponents([.year, .month, .day], from: match.computedStartDateForSorting)
            let strippedDate = calendar.date(from: components)!

            // Group matches by the strippedDate (only day/month/year)
            if matchesByDay[strippedDate] == nil {
                matchesByDay[strippedDate] = []
            }

            let shouldIncludeMatch: Bool
            switch match.matchType {
            case .groupStage:
                shouldIncludeMatch = !matchesByDay[strippedDate]!.filter { $0.groupStage != nil }.compactMap { $0.groupStage }.contains(match.groupStage!)
            case .bracket:
                shouldIncludeMatch = !matchesByDay[strippedDate]!.filter { $0.round != nil }.compactMap { $0.round }.contains(match.round!)
            case .loserBracket:
                shouldIncludeMatch = true
            }

            if shouldIncludeMatch {
                matchesByDay[strippedDate]!.append(match)
            }
        }

        return matchesByDay
    }

    private func _matchCountPerDay(matchesByDay: [Date: [Match]], tournament: Tournament) -> [Date: NSCountedSet] {
        let days = matchesByDay.keys
        var matchCountPerDay = [Date: NSCountedSet]()

        for day in days {
            if let matches = matchesByDay[day] {
                var groupStageCount = 0
                let countedSet = NSCountedSet()

                for match in matches {
                    switch match.matchType {
                    case .groupStage:
                        if let groupStage = match.groupStageObject {
                            if groupStageCount < groupStage.size - 1 {
                                groupStageCount = groupStage.size - 1
                            }
                        }
                    case .bracket:
                        countedSet.add(match.matchFormat)
                    case .loserBracket:
                        break
                    }
                }

                if groupStageCount > 0 {
                    for _ in 0..<groupStageCount {
                        countedSet.add(tournament.groupStageMatchFormat)
                    }
                }

                if let loserRounds = matches.filter({ $0.round != nil }).filter({ $0.roundObject?.parent == nil }).sorted(by: \.computedStartDateForSorting).last?.roundObject?.loserRounds() {

                    let ids = matches.map { $0.id }
                    for loserRound in loserRounds {
                        if let first = loserRound.playedMatches().first {
                            if ids.contains(first.id) {
                                countedSet.add(first.matchFormat)
                            }
                        }
                    }
                }
                matchCountPerDay[day] = countedSet
            }
        }

        return matchCountPerDay
    }

    private func _formatPerDayView(matchCountPerDay: [Date: NSCountedSet]) -> some View {
        ForEach(Array(matchCountPerDay.keys).sorted(), id: \.self) { date in
            if let countedSet = matchCountPerDay[date] {
                Section {
                    let totalMatches = countedSet.totalCount()
                    ForEach(Array(countedSet).compactMap { $0 as? MatchFormat }, id: \.self) { matchFormat in

                        let count = countedSet.count(for: matchFormat)
                        let totalForThisFormat = matchFormat.maximumMatchPerDay(for: totalMatches)
                        let error = count > totalForThisFormat
                        // Presenting LabeledContent for each match format and its count
                        LabeledContent {
                            Image(systemName: error ? "exclamationmark.triangle" : "checkmark")
                                .font(.title3)
                                .foregroundStyle(error ? .red : .green)
                        } label: {
                            let label : String = "\(count) match\(count.pluralSuffix) en \(matchFormat.format)"
                            let optionA : String = "aucun match possible à ce format"
                            let optionB : String = "pas plus de " + totalForThisFormat.formatted() + " match\(totalForThisFormat.pluralSuffix) à ce format"
                            let subtitle : String = (totalForThisFormat == 0) ? optionA : optionB
                            Text(label)
                            Text(subtitle)
                        }
                    }
                } header: {
                    Text(date.formatted(.dateTime.weekday(.abbreviated).day(.twoDigits).month(.abbreviated)))
                } footer: {
                    let totalMatches = countedSet.totalCount()
                    Text("Une équipe jouera potentiellement jusqu'à \(totalMatches) match\(totalMatches.pluralSuffix) ce jour.")
                }
            }
        }
    }

    // Helper function to format date to string (you can customize the format)
    private func _formattedDate(_ date: Date) -> String {
        let formatter = DateFormatter()
        formatter.dateStyle = .medium  // Set the date style (e.g., Oct 12, 2024)
        return formatter.string(from: date)
    }


}

// Extension to compute the total count in an NSCountedSet
extension NSCountedSet {
    func totalCount() -> Int {
        var total = 0
        for element in self {
            total += self.count(for: element)
        }
        return total
    }
}

//#Preview {
//    PlanningSettingsView(tournament: Tournament.mock())
//}
