import UIKit
import Shout
import Socket
import Models
import Combine
import Keychain
import Foundation
import Presentation
import DependencyInjection

public typealias SFTPDownloadResult = (Result<Data, Error>) -> Void
public typealias SFTPUploadResult = (Result<Backup, Error>) -> Void
public typealias SFTPFetchResult = (Result<RestoreSettings?, Error>) -> Void
public typealias SFTPAuthorizationParams = (UIViewController, () -> Void)

public struct SFTPService {
    public var isAuthorized: () -> Bool
    public var fetchMetadata: (@escaping SFTPFetchResult) -> Void
    public var uploadBackup: SFTPServiceBackupUploader
    public var authorizeFlow: (SFTPAuthorizationParams) -> Void
    public var authenticate: (String, String, String) throws -> Void
    public var downloadBackup: SFTPServiceBackupDownloader
}

public extension SFTPService {
    static var mock = SFTPService(
        isAuthorized: {
            print("^^^ Requested auth status on sftp service")
            return true
        },
        fetchMetadata: { completion in
            print("^^^ Requested backup metadata on sftp service.")
            completion(.success(nil))
        },
        uploadBackup: .mock,
        authorizeFlow: { (_, completion) in
            print("^^^ Requested authorizing flow on sftp service.")
            completion()
        },
        authenticate: { host, username, password in
            print("^^^ Requested authentication on sftp service.")
            print("^^^ Host: \(host)")
            print("^^^ Username: \(username)")
            print("^^^ Password: \(password)")
        },
        downloadBackup: .mock
    )

    static var live = SFTPService(
        isAuthorized: {
            if let keychain = try? DependencyInjection.Container.shared.resolve() as KeychainHandling,
               let pwd = try? keychain.get(key: .pwd),
               let host = try? keychain.get(key: .host),
               let username = try? keychain.get(key: .username) {
                return true
            }

            return false
        },
        fetchMetadata: { completion in
            DispatchQueue.global().async {
                do {
                    let keychain = try DependencyInjection.Container.shared.resolve() as KeychainHandling
                    let host = try keychain.get(key: .host)
                    let password = try keychain.get(key: .pwd)
                    let username = try keychain.get(key: .username)

                    let ssh = try SSH(host: host!, port: 22)
                    try ssh.authenticate(username: username!, password: password!)
                    let sftp = try ssh.openSftp()

                    if let files = try? sftp.listFiles(in: "backup"),
                       let backup = files.filter({ file in file.0 == "backup.xxm" }).first {
                        completion(.success(.init(
                            backup: .init(
                                id: "backup/backup.xxm",
                                date: backup.value.lastModified,
                                size: Float(backup.value.size)
                            ),
                            cloudService: .sftp
                        )))

                        return
                    }

                    completion(.success(nil))
                } catch {
                    if let error = error as? SSHError {
                        print(error.kind)
                        print(error.message)
                        print(error.description)
                    } else if let error = error as? Socket.Error {
                        print(error.errorCode)
                        print(error.description)
                        print(error.errorReason)
                        print(error.localizedDescription)
                    } else {
                        print(error.localizedDescription)
                    }

                    completion(.failure(error))
                }
            }
        },
        uploadBackup: .live ,
        authorizeFlow: { controller, completion in
            var pushPresenter: Presenting = PushPresenter()
            pushPresenter.present(SFTPController(completion), from: controller)
        },
        authenticate: { host, username, password in
            do {
                try SSH.connect(
                    host: host,
                    port: 22,
                    username: username,
                    authMethod: SSHPassword(password)) { ssh in
                        _ = try ssh.openSftp()

                        let keychain = try DependencyInjection.Container.shared.resolve() as KeychainHandling
                        try keychain.store(key: .host, value: host)
                        try keychain.store(key: .pwd, value: password)
                        try keychain.store(key: .username, value: username)
                    }
            } catch {
                if let error = error as? SSHError {
                    print(error.kind)
                    print(error.message)
                    print(error.description)
                } else if let error = error as? Socket.Error {
                    print(error.errorCode)
                    print(error.description)
                    print(error.errorReason)
                    print(error.localizedDescription)
                } else {
                    print(error.localizedDescription)
                }

                throw error
            }
        },
        downloadBackup: .live
    )
}