//
//  DataStore.swift
//  PadelClub
//
//  Created by Laurent Morvillier on 02/02/2024.
//

import Foundation
import LeStorage
import SwiftUI

class DataStore: ObservableObject {
    
    static let shared = DataStore()
    
    @Published var user: CustomUser = CustomUser.placeHolder() {
        didSet {
            let loggedUser = StoreCenter.main.isAuthenticated

            if loggedUser {
                if self.user.id != self.userStorage.item()?.id {
                    self.userStorage.setItemNoSync(self.user)
                    StoreCenter.main.initialSynchronization(clear: false)
                    self._fixMissingClubCreatorIfNecessary(self.clubs)
                    self._fixMissingEventCreatorIfNecessary(self.events)
                }
            } else {
                self._temporaryLocalUser.item = self.user
            }
        }
    }
    
    fileprivate(set) var tournaments: SyncedCollection<Tournament>
    fileprivate(set) var clubs: SyncedCollection<Club>
    fileprivate(set) var courts: SyncedCollection<Court>
    fileprivate(set) var events: SyncedCollection<Event>
    fileprivate(set) var monthData: StoredCollection<MonthData>
    fileprivate(set) var dateIntervals: SyncedCollection<DateInterval>
    fileprivate(set) var purchases: SyncedCollection<Purchase>

    fileprivate var userStorage: StoredSingleton<CustomUser>
    
    fileprivate var _temporaryLocalUser: OptionalStorage<CustomUser> = OptionalStorage(fileName: "tmp_local_user.json")
    fileprivate(set) var appSettingsStorage: MicroStorage<AppSettings> = MicroStorage(fileName: "appsettings.json")

    var appSettings: AppSettings {
        appSettingsStorage.item
    }
    
    init() {
        let store = Store.main
        StoreCenter.main.blackListUserName("apple-test")
        
//        let secureScheme = true
        let domain: String = URLs.activationHost.rawValue
        
        #if DEBUG
        if let secure = PListReader.readBool(plist: "local", key: "secure_server"),
           let domain = PListReader.readString(plist: "local", key: "server_domain") {
            StoreCenter.main.configureURLs(secureScheme: secure, domain: domain)
        } else {
            StoreCenter.main.configureURLs(secureScheme: true, domain: domain)
        }
        #else
        StoreCenter.main.configureURLs(secureScheme: true, domain: domain)
        #endif

        StoreCenter.main.logsFailedAPICalls()
        
        var synchronized: Bool = true
                
        #if DEBUG
        if let sync = PListReader.readBool(plist: "local", key: "synchronized") {
            synchronized = sync
        }
        #endif
        
        StoreCenter.main.forceNoSynchronization = !synchronized
        
        
        let indexed: Bool = true
        self.clubs = store.registerSynchronizedCollection(indexed: indexed)
        self.courts = store.registerSynchronizedCollection(indexed: indexed)
        self.tournaments = store.registerSynchronizedCollection(indexed: indexed)
        self.events = store.registerSynchronizedCollection(indexed: indexed)
        self.dateIntervals = store.registerSynchronizedCollection(indexed: indexed)
        self.userStorage = store.registerObject(synchronized: synchronized)
        self.purchases = Store.main.registerSynchronizedCollection(inMemory: true)

        self.monthData = store.registerCollection(indexed: indexed)

        // Load ApiCallCollection, making them restart at launch and deletable on disconnect
        StoreCenter.main.loadApiCallCollection(type: GroupStage.self)
        StoreCenter.main.loadApiCallCollection(type: Round.self)
        StoreCenter.main.loadApiCallCollection(type: PlayerRegistration.self)
        StoreCenter.main.loadApiCallCollection(type: TeamRegistration.self)
        StoreCenter.main.loadApiCallCollection(type: Match.self)
        StoreCenter.main.loadApiCallCollection(type: TeamScore.self)
        StoreCenter.main.loadApiCallCollection(type: DrawLog.self)
        
        NotificationCenter.default.addObserver(self, selector: #selector(collectionDidLoad), name: NSNotification.Name.CollectionDidLoad, object: nil)
        NotificationCenter.default.addObserver(self, selector: #selector(collectionDidUpdate), name: NSNotification.Name.CollectionDidChange, object: nil)
        NotificationCenter.default.addObserver(
            self,
            selector: #selector(_willEnterForegroundNotification),
            name: UIScene.willEnterForegroundNotification,
            object: nil)

    }
    
    deinit {
        NotificationCenter.default.removeObserver(self)
    }
    
    func saveUser() {
        if user.username.count > 0 {
            self.userStorage.update()
        } else {
            self._temporaryLocalUser.item = self.user
        }
    }
    
    @objc func collectionDidLoad(notification: Notification) {

        if let userSingleton: StoredSingleton<CustomUser> = notification.object as? StoredSingleton<CustomUser> {
            self.user = userSingleton.item() ?? self._temporaryLocalUser.item ?? CustomUser.placeHolder()
        } else if let clubsCollection: SyncedCollection<Club> = notification.object as? SyncedCollection<Club> {
            self._fixMissingClubCreatorIfNecessary(clubsCollection)
        } else if let eventsCollection: SyncedCollection<Event> = notification.object as? SyncedCollection<Event> {
            self._fixMissingEventCreatorIfNecessary(eventsCollection)
        }
        
        if Store.main.fileCollectionsAllLoaded() {
            AutomaticPatcher.applyAllWhenApplicable()
        }
        
    }
    
    fileprivate func _fixMissingClubCreatorIfNecessary(_ clubsCollection: SyncedCollection<Club>) {
        for club in clubsCollection {
            if let userId = StoreCenter.main.userId, club.creator == nil {
                club.creator = userId
                self.userStorage.item()?.addClub(club)
                self.userStorage.update()
                clubsCollection.writeChangeAndInsertOnServer(instance: club)
            }
        }
    }
    
    fileprivate func _fixMissingEventCreatorIfNecessary(_ eventsCollection: SyncedCollection<Event>) {
        for event in eventsCollection {
            if let userId = StoreCenter.main.userId, event.creator == nil {
                event.creator = userId
                do {
                    try event.insertOnServer()
                } catch {
                    Logger.error(error)
                }
            }
        }
    }
    
    @objc func collectionDidUpdate(notification: Notification) {
        self.objectWillChange.send()
    }
    
    @objc func _willEnterForegroundNotification() {
        Task {
            try await self.purchases.loadDataFromServerIfAllowed(clear: true)
        }
    }
    
    func disconnect() {
        
        Task {
            if await StoreCenter.main.hasPendingAPICalls() {
                // todo qu'est ce qu'on fait des API Call ?
            }
            
            do {
                let services = try StoreCenter.main.service()
                try await services.logout()
            } catch {
                Logger.error(error)
            }
            
            DispatchQueue.main.async {
                self._localDisconnect()
            }
        }
        
    }
    
    func deleteAccount() {
        
        Task {
            do {
                let services = try StoreCenter.main.service()
                try await services.deleteAccount()
            } catch {
                Logger.error(error)
            }
            
            DispatchQueue.main.async {
                self._localDisconnect()
            }

        }

    }
    
    func deleteTournament(_ tournament: Tournament) {
        let event = tournament.eventObject()
        let isLastTournament = event?.tournaments.count == 1
        self.tournaments.delete(instance: tournament)
        if let event, isLastTournament {
            self.events.delete(instance: event)
        }
        StoreCenter.main.destroyStore(identifier: tournament.id)
    }
    
    fileprivate func _localDisconnect() {
        
        let tournamendIds: [String] = self.tournaments.map { $0.id }
        
        TournamentLibrary.shared.reset()
        
        self.tournaments.reset()
        self.clubs.reset()
        self.courts.reset()
        self.events.reset()
        self.dateIntervals.reset()
        self.userStorage.reset()
        self.purchases.reset()
        
        Guard.main.disconnect()

        StoreCenter.main.disconnect()
        
        for tournament in tournamendIds {
            StoreCenter.main.destroyStore(identifier: tournament.id)
        }

        self.user = self._temporaryLocalUser.item ?? CustomUser.placeHolder()
        self.user.clubs.removeAll()

    }
    
//    func copyToLocalServer(tournament: Tournament) {
//        
//        Task {
//            do {
//                
//                if let url = PListReader.readString(plist: "local", key: "local_server"),
//                   let login = PListReader.readString(plist: "local", key: "username"),
//                   let pass = PListReader.readString(plist: "local", key: "password") {
//                    let service = Services(url: url)
//                    let _: CustomUser = try await service.login(username: login, password: pass)
//                    
//                    tournament.event = nil
//                    _ = try await service.post(tournament)
//                    
//                    for groupStage in tournament.groupStages() {
//                        _ = try await service.post(groupStage)
//                    }
//                    for round in tournament.rounds() {
//                        try await self._insertRoundAndChildren(round: round, service: service)
//                    }
//                    for teamRegistration in tournament.unsortedTeams() {
//                        _ = try await service.post(teamRegistration)
//                        for playerRegistration in teamRegistration.unsortedPlayers() {
//                            _ = try await service.post(playerRegistration)
//                        }
//                    }
//                    for groupStage in tournament.groupStages() {
//                        for match in groupStage._matches() {
//                            try await self._insertMatch(match: match, service: service)
//                        }
//                    }
//                    for round in tournament.allRounds() {
//                        for match in round._matches() {
//                            try await self._insertMatch(match: match, service: service)
//                        }
//                    }
//
//                }
//            } catch {
//                Logger.error(error)
//            }
//        }
//        
//    }
//    
//    fileprivate func _insertRoundAndChildren(round: Round, service: Services) async throws {
//        _ = try await service.post(round)
//        for loserRound in round.loserRounds() {
//            try await self._insertRoundAndChildren(round: loserRound, service: service)
//        }
//    }
//    
//    fileprivate func _insertMatch(match: Match, service: Services) async throws {
//        _ = try await service.post(match)
//        for teamScore in match.teamScores {
//            _ = try await service.post(teamScore)
//        }
//
//    }
    
    // MARK: - Convenience
    
    func runningMatches() -> [Match] {
        let dateNow : Date = Date()
        let lastTournaments = self.tournaments.filter { $0.isDeleted == false && $0.startDate <= dateNow && $0.hasEnded() == false }.sorted(by: \Tournament.startDate, order: .descending).prefix(10)
        
        var runningMatches: [Match] = []
        for tournament in lastTournaments {
            if let store = tournament.tournamentStore {
                let matches = store.matches.filter { match in
                    match.disabled == false && match.isRunning()
                }
                runningMatches.append(contentsOf: matches)
            }
        }
        return runningMatches
    }
 
    func runningAndNextMatches() -> [Match] {
        let dateNow : Date = Date()
        let lastTournaments = self.tournaments.filter { $0.isDeleted == false && $0.startDate <= dateNow && $0.hasEnded() == false }.sorted(by: \Tournament.startDate, order: .descending).prefix(10)
        
        var runningMatches: [Match] = []
        for tournament in lastTournaments {
            if let store = tournament.tournamentStore {
                let matches = store.matches.filter { match in
                    match.disabled == false && match.startDate != nil && match.endDate == nil }
                runningMatches.append(contentsOf: matches)
            }
        }
        return runningMatches
    }

    func endMatches() -> [Match] {
        let dateNow : Date = Date()
        let lastTournaments = self.tournaments.filter { $0.isDeleted == false && $0.startDate <= dateNow && $0.hasEnded() == false }.sorted(by: \Tournament.startDate, order: .descending).prefix(10)
        
        var runningMatches: [Match] = []
        for tournament in lastTournaments {
            if let store = tournament.tournamentStore {
                let matches = store.matches.filter { match in
                    match.disabled == false && match.hasEnded() }
                runningMatches.append(contentsOf: matches)
            }
        }
        return runningMatches.sorted(by: \.endDate!, order: .descending)
    }

}
