//
//  UserAccountsManager.swift
//  UserAccounts
//
//  Created by Shadowfacts on 3/5/23.
//

import Foundation
import Combine

public class UserAccountsManager: ObservableObject {
    
    public static let shared = UserAccountsManager()
    
    let defaults: UserDefaults
    
    private init() {
        if ProcessInfo.processInfo.environment.keys.contains("UI_TESTING") {
            defaults = UserDefaults(suiteName: "\(Bundle.main.bundleIdentifier!).uitesting")!
            defaults.removePersistentDomain(forName: "\(Bundle.main.bundleIdentifier!).uitesting")
            if ProcessInfo.processInfo.environment.keys.contains("UI_TESTING_LOGIN") {
                accounts = [
                    UserAccountInfo(
                        instanceURL: URL(string: "http://localhost:8080")!,
                        clientID: "client_id",
                        clientSecret: "client_secret",
                        username: "admin",
                        accessToken: "access_token")
                ]
            }
        } else {
            defaults = UserDefaults(suiteName: "group.space.vaccor.Tusker")!
        }
        
        migrateAccountIDsIfNecessary()
    }
    
    private let accountsKey = "accounts"
    public private(set) var accounts: [UserAccountInfo] {
        get {
            if let array = defaults.array(forKey: accountsKey) as? [[String: String]] {
                return array.compactMap(UserAccountInfo.init(userDefaultsDict:))
            } else {
                return []
            }
        }
        set {
            objectWillChange.send()
            let array = newValue.map { (info) -> [String: String] in
                var res = [
                    "id": info.id,
                    "instanceURL": info.instanceURL.absoluteString,
                    "clientID": info.clientID,
                    "clientSecret": info.clientSecret,
                    "accessToken": info.accessToken
                ]
                if let username = info.username {
                    res["username"] = username
                }
                return res
            }
            defaults.set(array, forKey: accountsKey)
        }
    }
    
    private let mostRecentAccountKey = "mostRecentAccount"
    public private(set) var mostRecentAccountID: String? {
        get {
            return defaults.string(forKey: mostRecentAccountKey)
        }
        set {
            objectWillChange.send()
            defaults.set(newValue, forKey: mostRecentAccountKey)
        }
    }
    
    private let usesAccountIDHashesKey = "usesAccountIDHashes"
    private var usesAccountIDHashes: Bool {
        get {
            return defaults.bool(forKey: usesAccountIDHashesKey)
        }
        set {
            return defaults.set(newValue, forKey: usesAccountIDHashesKey)
        }
    }
    
    private func migrateAccountIDsIfNecessary() {
        if usesAccountIDHashes {
            return
        }
        if let mostRecentAccount = getMostRecentAccount() {
            let hashedMostRecentID = UserAccountInfo.id(instanceURL: mostRecentAccount.instanceURL, username: mostRecentAccount.username)
            mostRecentAccountID = hashedMostRecentID
        }
        if let array = defaults.array(forKey: accountsKey) as? [[String: String]] {
            accounts = array.compactMap {
                guard let urlString = $0["instanceURL"],
                      let url = URL(string: urlString),
                      let username = $0["username"] else {
                    return nil
                }
                var dict = $0
                dict["id"] = UserAccountInfo.id(instanceURL: url, username: username)
                return UserAccountInfo(userDefaultsDict: dict)
            }
        }
        usesAccountIDHashes = true
    }
    
    // MARK: Account Management
    
    public var onboardingComplete: Bool {
        return !accounts.isEmpty
    }
    
    public func addAccount(instanceURL url: URL, clientID: String, clientSecret secret: String, username: String?, accessToken: String) -> UserAccountInfo {
        var accounts = self.accounts
        if let index = accounts.firstIndex(where: { $0.instanceURL == url && $0.username == username }) {
            accounts.remove(at: index)
        }
        let info = UserAccountInfo(instanceURL: url, clientID: clientID, clientSecret: secret, username: username, accessToken: accessToken)
        accounts.append(info)
        self.accounts = accounts
        return info
    }
    
    public func removeAccount(_ info: UserAccountInfo) {
        accounts.removeAll(where: { $0.id == info.id })
    }
    
    public func getAccount(id: String) -> UserAccountInfo? {
        return accounts.first(where: { $0.id == id })
    }
    
    public func getMostRecentAccount() -> UserAccountInfo? {
        guard onboardingComplete else { return nil }
        let mostRecent: UserAccountInfo?
        if let id = mostRecentAccountID {
            mostRecent = accounts.first { $0.id == id }
        } else {
            mostRecent = nil
        }
        return mostRecent ?? accounts.first!
    }
    
    public func setMostRecentAccount(_ account: UserAccountInfo?) {
        mostRecentAccountID = account?.id
    }

}

public extension Notification.Name {
    static let userLoggedOut = Notification.Name("Tusker.userLoggedOut")
    static let addAccount = Notification.Name("Tusker.addAccount")
    static let activateAccount = Notification.Name("Tusker.activateAccount")
}
