//
//  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 = (user.username.count > 0)
            Store.main.collectionsCanSynchronize = loggedUser

            if loggedUser {
                if self.user.id != self.userStorage.item()?.id {
                    self.userStorage.setItemNoSync(self.user)
                    if Store.main.collectionsCanSynchronize {
                        Store.main.loadCollectionFromServer()
                        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 groupStages: StoredCollection<GroupStage>
    fileprivate(set) var matches: StoredCollection<Match>
    fileprivate(set) var teamRegistrations: StoredCollection<TeamRegistration>
    fileprivate(set) var playerRegistrations: StoredCollection<PlayerRegistration>
    fileprivate(set) var rounds: StoredCollection<Round>
    fileprivate(set) var teamScores: StoredCollection<TeamScore>
    fileprivate(set) var monthData: StoredCollection<MonthData>
    fileprivate(set) var dateIntervals: StoredCollection<DateInterval>
    fileprivate(set) var matchSchedulers: StoredCollection<MatchScheduler>

    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
        store.blackListUserName("apple-test")
        
        #if DEBUG
        if let server = PListReader.readString(plist: "local", key: "server") {
            store.synchronizationApiURL = server
        } else {
            store.synchronizationApiURL = serverURL
        }
        #else
        store.synchronizationApiURL = serverURL
        #endif

        store.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: \(store.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.groupStages = store.registerCollection(synchronized: synchronized, indexed: indexed)
        self.teamScores = store.registerCollection(synchronized: synchronized, indexed: indexed)
        self.teamRegistrations = store.registerCollection(synchronized: synchronized, indexed: indexed)
        self.playerRegistrations = store.registerCollection(synchronized: synchronized, indexed: indexed)
        self.rounds = store.registerCollection(synchronized: synchronized, indexed: indexed)
        self.matches = store.registerCollection(synchronized: synchronized, indexed: indexed)
        self.monthData = store.registerCollection(synchronized: false, indexed: indexed)
        self.dateIntervals = store.registerCollection(synchronized: synchronized, indexed: indexed)
        self.matchSchedulers = store.registerCollection(synchronized: false, 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)
        } 
//        else if let purchaseCollection: StoredCollection<Purchase> = notification.object as? StoredCollection<Purchase> {
//            try? purchaseCollection.deleteAll()
//        }
    }
    
    fileprivate func _fixMissingClubCreatorIfNecessary(_ clubsCollection: StoredCollection<Club>) {
        for club in clubsCollection {
            if let userId = Store.main.userId, club.creator == nil {
                club.creator = userId
                clubsCollection.writeChangeAndInsertOnServer(instance: club)
            }
        }
    }
    
    fileprivate func _fixMissingEventCreatorIfNecessary(_ eventsCollection: StoredCollection<Event>) {
        for event in eventsCollection {
            if let userId = Store.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 Store.main.hasPendingAPICalls() {
                // todo qu'est ce qu'on fait des API Call ?
            }
        }
        
        self.user = self._temporaryLocalUser.item ?? User.placeHolder()
        
        Store.main.disconnect()
        Store.main.collectionsCanSynchronize = false
        
        self.tournaments.reset()
        self.clubs.reset()
        self.courts.reset()
        self.events.reset()
        self.groupStages.reset()
        self.matches.reset()
        self.teamRegistrations.reset()
        self.playerRegistrations.reset()
        self.rounds.reset()
        self.teamScores.reset()
        self.dateIntervals.reset()
        self.matchSchedulers.reset()
        self.userStorage.reset()
        
        Guard.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)
        }

    }
    
}
