//
//  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: User = User.placeHolder() {
        didSet {
            let loggedUser = StoreCenter.main.userId != nil
            StoreCenter.main.collectionsCanSynchronize = loggedUser

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

    fileprivate var userStorage: StoredSingleton<User>
    
    fileprivate var _temporaryLocalUser: OptionalStorage<User> = 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
        let serverURL: String = URLs.api.rawValue
        StoreCenter.main.blackListUserName("apple-test")
        
        #if DEBUG
        if let server = PListReader.readString(plist: "local", key: "server") {
            StoreCenter.main.synchronizationApiURL = server
        } else {
            StoreCenter.main.synchronizationApiURL = serverURL
        }
        #else
        StoreCenter.main.synchronizationApiURL = serverURL
        #endif

        StoreCenter.main.logsFailedAPICalls()
        
        var synchronized: Bool = true
        
        _ = Guard.main // init
        
        #if DEBUG
        if let sync = PListReader.readBool(plist: "local", key: "synchronized") {
            synchronized = sync
        }
        #endif
        
        Logger.log("Sync URL: \(StoreCenter.main.synchronizationApiURL ?? "none"), sync: \(synchronized) ")
        
        let indexed: Bool = true
        self.clubs = store.registerCollection(synchronized: synchronized, indexed: indexed)
        self.courts = store.registerCollection(synchronized: synchronized, indexed: indexed)
        self.tournaments = store.registerCollection(synchronized: synchronized, indexed: indexed)
        self.events = store.registerCollection(synchronized: synchronized, indexed: indexed)
        self.monthData = store.registerCollection(synchronized: false, indexed: indexed)
        self.dateIntervals = store.registerCollection(synchronized: synchronized, indexed: indexed)

        self.userStorage = store.registerObject(synchronized: synchronized)
        
        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)
        
    }
    
    func saveUser() {
        do {
            if user.username.count > 0 {
                try self.userStorage.update()
            } else {
                self._temporaryLocalUser.item = self.user
            }
        } catch {
            Logger.error(error)
        }
    }
    
    @objc func collectionDidLoad(notification: Notification) {
        
        DispatchQueue.main.async {
            self.objectWillChange.send()
        }
        
        if let userSingleton: StoredSingleton<User> = notification.object as? StoredSingleton<User> {
            self.user = userSingleton.item() ?? self._temporaryLocalUser.item ?? User.placeHolder()
        } else if let clubsCollection: StoredCollection<Club> = notification.object as? StoredCollection<Club> {
            self._fixMissingClubCreatorIfNecessary(clubsCollection)
        } else if let eventsCollection: StoredCollection<Event> = notification.object as? StoredCollection<Event> {
            self._fixMissingEventCreatorIfNecessary(eventsCollection)
        }
        
        if Store.main.collectionsAllLoaded() {
            Patcher.applyAllWhenApplicable()
        }
        
    }
    
    fileprivate func _fixMissingClubCreatorIfNecessary(_ clubsCollection: StoredCollection<Club>) {
        do {
            for club in clubsCollection {
                if let userId = StoreCenter.main.userId, club.creator == nil {
                    club.creator = userId
                    self.userStorage.item()?.addClub(club)
                    try self.userStorage.update()
                    clubsCollection.writeChangeAndInsertOnServer(instance: club)
                }
            }
        } catch {
            Logger.error(error)
        }
    }
    
    fileprivate func _fixMissingEventCreatorIfNecessary(_ eventsCollection: StoredCollection<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()
    }
    
    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()
            }

        }

    }
    
    fileprivate func _localDisconnect() {
        
        StoreCenter.main.collectionsCanSynchronize = false

        self.tournaments.reset()
        self.clubs.reset()
        self.courts.reset()
        self.events.reset()
        self.dateIntervals.reset()
        self.userStorage.reset()
        
        for tournament in self.tournaments {
            StoreCenter.main.destroyStore(identifier: tournament.id)
        }
        
        Guard.main.disconnect()

        self.user = self._temporaryLocalUser.item ?? User.placeHolder()
        self.user.clubs.removeAll()
        
        StoreCenter.main.disconnect()
    }
    
    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 _: User = 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 }.sorted(by: \Tournament.startDate, order: .descending).prefix(10)
        
        var runningMatches: [Match] = []
        for tournament in lastTournaments {
            let matches = tournament.tournamentStore.matches.filter { match in
                match.confirmed && match.startDate != nil && match.endDate == nil }
            runningMatches.append(contentsOf: matches)
        }
        return runningMatches
    }
    
}
