//
//  ClubSearchView.swift
//  PadelClub
//
//  Created by Razmig Sarkissian on 21/03/2024.
//

import SwiftUI
import CoreLocation
import CoreLocationUI
import TipKit
import LeStorage

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

    @State private var searchedCity: String = ""
    @State private var radius: Double = 50
    @State private var clubMarkers : [ClubMarker] = []
    @State private var searching: Bool = false
    @State private var selectedClubs: [ClubMarker] = []
    @State private var searchAttempted: Bool = false
    @StateObject var locationManager = LocationManager()
    @StateObject private var debouncableViewModel: DebouncableViewModel = DebouncableViewModel()
    @State private var getForwardCityList: [CLPlacemark] = []
    @State private var searchPresented: Bool = false
    @State private var showingSettingsAlert = false
    @State private var newClub: Club?

    var presentClubCreationView: Binding<Bool> { Binding(
        get: { newClub != nil },
        set: { isPresented in
            if isPresented == false {
                newClub = nil
            }
        }
    )}

    var displayContext: DisplayContext = .edition
    var club: Club?
    var selection: ((Club) -> ())? = nil

    private var distanceLimit: Measurement<UnitLength> {
        Measurement(value: radius, unit: .kilometers)
    }
    
    private func _prompt() -> String {
        if clubMarkers.isEmpty {
            return "Chercher une ville ou un code postal"
        } else {
            return "Chercher un club parmi ceux listés"
        }
    }
    
    private func getClubs() async {
        do {
            defer {
                searching = false
                searchAttempted = true
            }
            
            clubMarkers = []
            guard let city = locationManager.city else { return }
            let response = try await NetworkFederalService.shared.federalClubs(city: city, radius: radius, location: locationManager.location)
            await MainActor.run {
                clubMarkers = response.clubMarkers.sorted(by: { a, b in
                    locationManager.location?.distance(from: a.location) ?? 0 < locationManager.location?.distance(from: b.location) ?? 0
                })
            }
        } catch {
            print("getclubs", error)
        }
    }
    
    /*
     Form {
     }
     .toolbarRole(.editor)
     .navigationTitle("Chercher un club")

     */
    
    var body: some View {
        List {
            if _filteredClubs().isEmpty == false {
                Section {
                    ForEach(_filteredClubs()) { clubMark in
                        Button {
                            let clubToEdit = club ?? Club.findOrCreate(name: clubMark.nom, code: clubMark.clubID)
                            _importClub(clubToEdit: clubToEdit, clubMarker: clubMark)
                        } label: {
                            clubView(clubMark)
                                .contentShape(Rectangle())
                        }
                        .frame(maxWidth: .infinity)
                        .buttonStyle(.plain)
                    }
                } header: {
                    HStack {
                        if let city = locationManager.city {
                            Text(_filteredClubs().count.formatted() + " clubs autour de \(city)")
                        } else {
                            Text(_filteredClubs().count.formatted() + " clubs trouvés")
                        }
                        Spacer()
                        Button {
                            _resetSearch()
                        } label: {
                            Text("effacer")
                        }
                        .buttonStyle(.borderless)
                        .textCase(nil)
                    }
                }
            }
        }
        .task {
            do {
                try await dataStore.clubs.loadDataFromServerIfAllowed()
            } catch {
                Logger.error(error)
            }
        }
        .listStyle(.grouped)
        .onChange(of: searchPresented) {
            locationManager.lastError = nil
        }
        .overlay {
            if locationManager.requestStarted == false {
                if locationManager.lastError != nil {
                    ContentUnavailableView {
                        Label("Erreur", systemImage: "exclamationmark.circle")
                    } description: {
                        Text("Une erreur est survenue lors de la récupération de votre localisation.")
                    } actions: {
                        RowButtonView("D'accord") {
                            locationManager.lastError = nil
                        }
                    }
                } else if clubMarkers.isEmpty == false && searching == false && _filteredClubs().isEmpty {
                    ContentUnavailableView.search(text: searchedCity)
                } else if clubMarkers.isEmpty && searching == false && searchPresented == false {
                    ContentUnavailableView {
                        if searchAttempted {
                            Label("Aucun club trouvé", systemImage: "mappin.slash")
                        } else {
                            Label("Recherche de club", systemImage: "location.circle")
                        }
                    } description: {
                        Text("Padel Club peut rechercher un club autour de vous, d'une ville ou d'un code postal, facilitant ainsi la saisie d'information.")
                    } actions: {
                        if locationManager.manager.authorizationStatus != .restricted {
                            RowButtonView("Chercher autour de moi") {
                                if locationManager.manager.authorizationStatus == .notDetermined {
                                    locationManager.manager.requestWhenInUseAuthorization()
                                } else if locationManager.manager.authorizationStatus == .denied {
                                    showingSettingsAlert = true
                                } else {
                                    locationManager.requestLocation()
                                }
                            }
                        }
                        RowButtonView("Chercher une ville ou un code postal") {
                            searchPresented = true
                        }
                        
                        if searchAttempted {
                            RowButtonView("Créer un club manuellement") {
                                newClub = club ?? Club.newEmptyInstance()
                            }
                        }
                    }
                }
            } else {
                ContentUnavailableView("recherche en cours", systemImage: "mappin.and.ellipse", description: Text("recherche des clubs autour de vous"))
            }
        }
        .sheet(isPresented: presentClubCreationView) {
            if let newClub {
                CreateClubView(club: newClub) { club in
                    selection?(club)
                    dismiss()
                }
                .tint(.master)
            }
        }
        .alert(isPresented: $showingSettingsAlert) {
            Alert(
                title: Text("Réglages"),
                message: Text("Pour trouver les clubs autour de vous, vous devez l'autorisation à Padel Club de récupérer votre position."),
                primaryButton: .default(Text("Ouvrir les réglages"), action: {
                    _openSettings()
                }),
                secondaryButton: .cancel()
            )
        }
        .onReceive(
            debouncableViewModel.$debouncableText
                .debounce(for: .seconds(debouncableViewModel.debounceTrigger), scheduler: DispatchQueue.main)
        ) {
            guard !$0.isEmpty else {
                if searchedCity.isEmpty == false {
                    searchedCity = ""
                }
                return
            }
            print(">> searching for: \($0)")
            
            if debouncableViewModel.debouncableText.trimmed.count > 1 {
                searchedCity = $0
            }
        }
        .onChange(of: searchedCity, {
            if searchedCity.isEmpty == false {
                locationManager.geocodeCity(cityOrZipcode: searchedCity) { cities, error in
                    getForwardCityList = cities ?? []
                }
            }
        })
        .onChange(of: locationManager.requestStarted, { oldValue, newValue in
            if oldValue == true && newValue == false {
                if locationManager.lastError == nil {
                    debouncableViewModel.debouncableText = ""
                    searchedCity = ""
                    searching = true
                    getForwardCityList = []
                    searchPresented = false
                    Task {
                        await getClubs()
                    }
                }
            }
        })
        .navigationTitle("Recherche de club")
        .searchable(text: $debouncableViewModel.debouncableText, isPresented: $searchPresented, prompt: _prompt())
        .autocorrectionDisabled(true)
        .keyboardType(.alphabet)
        .searchSuggestions {
            if clubMarkers.isEmpty {
                ForEach(getForwardCityList, id: \.self) { placemark in
                    Button {
                        locationManager.location = placemark.location
                        locationManager.city = placemark._userReadableCityAndZipCode()
                    } label: {
                        Text(placemark._userReadableCityAndZipCode())
                    }
                }
            }
        }
        .onSubmit(of: .search, {
            if clubMarkers.isEmpty {
                debouncableViewModel.debouncableText = ""
                searchedCity = ""
                searching = true
                if getForwardCityList.isEmpty {
                    locationManager.city = nil
                    locationManager.location = nil
                    locationManager.postalCode = nil
                } else {
                    locationManager.location = getForwardCityList.first?.location
                    locationManager.city = getForwardCityList.first?._userReadableCityAndZipCode()
                }
                searchPresented = false
                Task {
                    await getClubs()
                }
            }
        })
        .toolbarBackground(.visible, for: .navigationBar)
        .toolbar {
            ToolbarItem(placement: .topBarTrailing) {
                if locationManager.requestStarted == false && searching == false {
                    LocationButton(.currentLocation) {
                        clubMarkers = []
                        locationManager.requestLocation()
                    }
                    .labelStyle(.iconOnly)
                    .symbolVariant(.fill)
                    .foregroundColor (Color.white)
                    .cornerRadius (20)
                    .font(.system(size: 12))
                } else {
                    ProgressView()
                }
            }
//            if selectedClubs.isEmpty == false {
//                Button {
//                    
//                    selectedClubs.forEach { club in
////                        let federalClub = FederalClubData(context: viewContext)
////                        federalClub.updateWith(club)
////                        user.addToClubs(federalClub)
//                    }
//                    
////                    save()
//                    dismiss()
//                } label: {
//                    Text("Valider")
//                }
//            }
        }
    }

    private func _importClub(clubToEdit: Club, clubMarker: ClubMarker) {
        if clubToEdit.creator == dataStore.user.id {
            if clubToEdit.name.isEmpty {
                clubToEdit.name = clubMarker.nom.capitalized
                clubToEdit.acronym = clubToEdit.automaticShortName().capitalized
            }
            clubToEdit.code = clubMarker.clubID
            clubToEdit.latitude = clubMarker.lat
            clubToEdit.longitude = clubMarker.lng
            clubToEdit.city = clubMarker.ville
        }
        
        if displayContext == .addition {
            do {
                try dataStore.clubs.addOrUpdate(instance: clubToEdit)
                if dataStore.user.clubs.contains(where: { $0 == clubToEdit.id }) == false {
                    dataStore.user.clubs.append(clubToEdit.id)
                    self.dataStore.saveUser()
                }
            } catch {
                Logger.error(error)
            }
        }
        dismiss()
        selection?(clubToEdit)
    }
    
    private func _filteredClubs() -> [ClubMarker] {
        clubMarkers.filter({ _isClubValidForSearchedTerms(club: $0) })
    }
    
    private func _isClubValidForSearchedTerms(club: ClubMarker) -> Bool {
        searchedCity.isEmpty ||
        club.nom.localizedCaseInsensitiveContains(searchedCity) ||
        club.ville.localizedCaseInsensitiveContains(searchedCity)
    }
    
    private func _resetSearch() {
        searchAttempted = false
        debouncableViewModel.debouncableText = ""
        searchedCity = ""
        locationManager.city = nil
        locationManager.location = nil
        locationManager.postalCode = nil
        locationManager.lastError = nil
        clubMarkers = []
        getForwardCityList = []
    }

    private func _openSettings() {
        guard let settingsURL = URL(string: UIApplication.openSettingsURLString) else {
            return
        }
        UIApplication.shared.open(settingsURL)
    }

    @ViewBuilder
    private func clubView(_ club: ClubMarker) -> some View {
        LabeledContent {
            Text(club.distance(from: locationManager.location))
        } label: {
            Text(club.nom)
            Text(club.ville).font(.caption)
        }
    }
}

// MARK: - FederalClubResponse
struct FederalClubResponse: Codable {
    let typeRecherche: String
    let nombreResultat: Int
    let clubMarkers: [ClubMarker]

    enum CodingKeys: String, CodingKey {
        case typeRecherche, nombreResultat
        case clubMarkers = "club_markers"
    }
}

enum Pratique: String, Codable {
    case beach = "BEACH"
    case padel = "PADEL"
    case tennis = "TENNIS"
    case pickle = "PICKLE"
}

// MARK: - ClubMarker
struct ClubMarker: Codable, Hashable, Identifiable {
    let nom, clubID, ville, distance: String
    let terrainPratiqueLibelle: String
    let pratiques: [Pratique]
    let lat, lng: Double
    
    var location: CLLocation {
        CLLocation(latitude: lat, longitude: lng)
    }
    
    func distance(from location: CLLocation?) -> String {
        guard let location else { return "" }
        let measurement = Measurement(value: location.distance(from: self.location) / 1000, unit: UnitLength.kilometers)
        return measurement.formatted()
    }
    
    var id: String {
        clubID
    }

    enum CodingKeys: String, CodingKey {
        case nom
        case clubID = "clubId"
        case ville, distance, terrainPratiqueLibelle, pratiques, lat, lng
    }
}

fileprivate extension CLPlacemark {
    func _userReadableCityAndZipCode() -> String {
        [locality, postalCode].compactMap { $0 }.joined(separator: ", ")
    }
}


//#Preview {
//    ClubSearchView()
//}
