import Retry
import Models
import Combine
import XXModels
import Foundation

extension Session {
    public func waitForNodes(timeout: Int) -> AnyPublisher<Void, Error> {
        Deferred {
            Future { promise in
                retry(max: timeout, retryStrategy: .delay(seconds: 1)) { [weak self] in
                    guard let self = self else { return }
                    try self.client.bindings.nodeRegistrationStatus()
                    promise(.success(()))
                }.finalCatch {
                    promise(.failure($0))
                }
            }
        }.eraseToAnyPublisher()
    }

    public func search(fact: String) -> AnyPublisher<Contact, Error> {
        Deferred {
            Future { promise in
                guard let ud = self.client.userDiscovery else {
                    let error = NSError(domain: "", code: 0)
                    promise(.failure(error))
                    return
                }

                do {
                    try self.client.bindings.nodeRegistrationStatus()
                    try ud.search(fact: fact) {
                        switch $0 {
                        case .success(let contact):
                            promise(.success(contact))
                        case .failure(let error):
                            promise(.failure(error))
                        }
                    }
                } catch {
                    promise(.failure(error))
                }
            }
        }.eraseToAnyPublisher()
    }

    public func search(fact: String, _ completion: @escaping (Result<Contact, Error>) -> Void) throws {
        guard let ud = client.userDiscovery else { return }
        try client.bindings.nodeRegistrationStatus()
        try ud.search(fact: fact, completion)
    }

    public func extract(fact: FactType, from marshalled: Data) throws -> String? {
        guard let ud = client.userDiscovery else { return nil }
        return try ud.retrieve(from: marshalled, fact: fact)
    }

    public func unregister(fact: FactType) throws {
        guard let ud = client.userDiscovery else { return }

        switch fact {
        case .phone:
            try ud.remove("P" + phone!)
            isSharingPhone = false
            phone = nil
        case .email:
            try ud.remove("E" + email!)
            isSharingEmail = false
            email = nil
        default:
            break
        }
    }

    public func register(_ fact: FactType, value: String, _ completion: @escaping (Result<String?, Error>) -> Void) {
        guard let ud = client.userDiscovery else { return }

        switch fact {
        case .username:
            ud.register(.username, value: value) { [weak self] in
                guard let self = self else { return }

                switch $0 {
                case .success(_):
                    self.username = value

                    if var me = try? self.myContact() {
                        me.username = value
                        _ = try? self.dbManager.saveContact(me)
                    }

                    completion(.success(nil))
                case .failure(let error):
                    completion(.failure(error))
                }
            }
        default:
            ud.register(fact, value: value, completion)
        }
    }

    public func confirm(code: String, confirmation: AttributeConfirmation) throws {
        guard let ud = client.userDiscovery else { return }

        try ud.confirm(code: code, id: confirmation.confirmationId!)

        if confirmation.isEmail {
            email = confirmation.content
        } else {
            phone = confirmation.content
        }

        if let _ = client.backupManager {
            updateFactsOnBackup()
        }
    }
}