//
//  GitRepositorySettingsTableViewController.swift
//  pass
//
//  Created by Mingshen Sun on 21/1/2017.
//  Copyright © 2017 Bob Sun. All rights reserved.
//

import passKit
import SVProgressHUD
import UIKit

class GitRepositorySettingsTableViewController: UITableViewController, PasswordAlertPresenter {
    // MARK: - View Outlet

    @IBOutlet var gitURLTextField: UITextField!
    @IBOutlet var usernameTextField: UITextField!
    @IBOutlet var branchNameTextField: UITextField!
    @IBOutlet var authSSHKeyCell: UITableViewCell!
    @IBOutlet var authPasswordCell: UITableViewCell!
    @IBOutlet var gitURLCell: UITableViewCell!

    // MARK: - Properties

    private var sshLabel: UILabel?
    private let passwordStore = PasswordStore.shared
    private let keychain = AppKeychain.shared
    private var gitCredential: GitCredential {
        GitCredential.from(
            authenticationMethod: Defaults.gitAuthenticationMethod,
            userName: Defaults.gitUsername,
            keyStore: keychain
        )
    }

    private var gitAuthenticationMethod: GitAuthenticationMethod {
        get { Defaults.gitAuthenticationMethod }
        set {
            Defaults.gitAuthenticationMethod = newValue
            updateAuthenticationMethodCheckView(for: newValue)
        }
    }

    private var gitURL: URL {
        get { Defaults.gitURL }
        set { Defaults.gitURL = newValue }
    }

    private var gitBranchName: String {
        get { Defaults.gitBranchName }
        set { Defaults.gitBranchName = newValue }
    }

    private var gitUsername: String {
        get { Defaults.gitUsername }
        set { Defaults.gitUsername = newValue }
    }

    // MARK: - View Controller Lifecycle

    override func viewDidLoad() {
        super.viewDidLoad()
        gitURLTextField.text = gitURL.absoluteString
        usernameTextField.text = gitUsername
        branchNameTextField.text = gitBranchName
        sshLabel = authSSHKeyCell.subviews[0].subviews[0] as? UILabel
        authSSHKeyCell.accessoryType = .detailButton
    }

    override func viewWillAppear(_ animated: Bool) {
        super.viewWillAppear(animated)
        // Grey out ssh option if ssh_key is not present.
        sshLabel?.isEnabled = keychain.contains(key: SSHKey.PRIVATE.getKeychainKey())
        updateAuthenticationMethodCheckView(for: gitAuthenticationMethod)
    }

    override func viewWillDisappear(_ animated: Bool) {
        super.viewWillDisappear(animated)
        view.endEditing(true)
    }

    // MARK: - UITableViewController Override

    override func tableView(_ tableView: UITableView, accessoryButtonTappedForRowWith indexPath: IndexPath) {
        let cell = tableView.cellForRow(at: indexPath)
        if cell == authSSHKeyCell {
            showSSHKeyActionSheet()
        } else if cell == gitURLCell {
            showGitURLFormatHelp()
        }
    }

    override func tableView(_ tableView: UITableView, didSelectRowAt indexPath: IndexPath) {
        let cell = tableView.cellForRow(at: indexPath)
        if cell == authPasswordCell {
            gitAuthenticationMethod = .password
        } else if cell == authSSHKeyCell {
            if !keychain.contains(key: SSHKey.PRIVATE.getKeychainKey()) {
                Utils.alert(title: "CannotSelectSshKey".localize(), message: "PleaseSetupSshKeyFirst.".localize(), controller: self)
                gitAuthenticationMethod = .password
            } else {
                gitAuthenticationMethod = .key
            }
        }
        tableView.deselectRow(at: indexPath, animated: true)
    }

    override func tableView(_: UITableView, heightForRowAt _: IndexPath) -> CGFloat {
        UITableView.automaticDimension
    }

    override func tableView(_: UITableView, estimatedHeightForRowAt _: IndexPath) -> CGFloat {
        UITableView.automaticDimension
    }

    // MARK: - Segue Handlers

    @IBAction
    private func save(_: Any) {
        guard let gitURLTextFieldText = gitURLTextField.text, let gitURL = URL(string: gitURLTextFieldText.trimmed) else {
            Utils.alert(title: "CannotSave".localize(), message: "SetGitRepositoryUrl".localize(), controller: self)
            return
        }

        guard let branchName = branchNameTextField.text?.trimmed, !branchName.isEmpty else {
            Utils.alert(title: "CannotSave".localize(), message: "SpecifyBranchName.".localize(), controller: self)
            return
        }

        if let scheme = gitURL.scheme {
            switch scheme {
            case "http", "https", "ssh":
                if gitURL.user == nil, usernameTextField.text == nil {
                    Utils.alert(title: "CannotSave".localize(), message: "CannotFindUsername.".localize(), controller: self)
                    return
                }
                if let urlUsername = gitURL.user, let textFieldUsername = usernameTextField.text, urlUsername != textFieldUsername.trimmed {
                    Utils.alert(title: "CannotSave".localize(), message: "CheckEnteredUsername.".localize(), controller: self)
                    return
                }
            case "file":
                break
            default:
                Utils.alert(title: "CannotSave".localize(), message: "Protocol is not supported", controller: self)
                return
            }
        }

        let username = (gitURL.user ?? usernameTextField.text ?? "git").trimmed

        if passwordStore.repositoryExists() {
            let overwriteAlert: UIAlertController = {
                let alert = UIAlertController(title: "Overwrite?".localize(), message: "OperationWillOverwriteData.".localize(), preferredStyle: .alert)
                alert.addAction(
                    UIAlertAction(title: "Overwrite".localize(), style: .destructive) { _ in
                        self.cloneAndSegueIfSuccess(url: gitURL, branch: branchName, username: username)
                    }
                )
                alert.addAction(UIAlertAction.cancel())
                return alert
            }()
            present(overwriteAlert, animated: true)
        } else {
            cloneAndSegueIfSuccess(url: gitURL, branch: branchName, username: username)
        }
    }

    private func cloneAndSegueIfSuccess(url: URL, branch: String, username: String) {
        gitURL = url
        gitBranchName = branch
        gitUsername = username

        // Remember git credential password/passphrase temporarily, ask whether users want this after a successful clone.
        Defaults.isRememberGitCredentialPassphraseOn = true
        // swiftlint:disable:next closure_body_length
        DispatchQueue.global(qos: .userInitiated).async {
            do {
                let transferProgressBlock: (UnsafePointer<git_transfer_progress>, UnsafeMutablePointer<ObjCBool>) -> Void = { git_transfer_progress, _ in
                    let gitTransferProgress = git_transfer_progress.pointee
                    let progress = Float(gitTransferProgress.received_objects) / Float(gitTransferProgress.total_objects)
                    SVProgressHUD.showProgress(progress, status: "Cloning Remote Repository")
                }

                let checkoutProgressBlock: (String, UInt, UInt) -> Void = { _, completedSteps, totalSteps in
                    let progress = Float(completedSteps) / Float(totalSteps)
                    SVProgressHUD.showProgress(progress, status: "CheckingOutBranch".localize(self.gitBranchName))
                }

                let options = self.gitCredential.getCredentialOptions(passwordProvider: self.present)

                try self.passwordStore.cloneRepository(
                    remoteRepoURL: self.gitURL,
                    branchName: self.gitBranchName,
                    options: options,
                    transferProgressBlock: transferProgressBlock,
                    checkoutProgressBlock: checkoutProgressBlock
                )

                let gpgIDFile = self.passwordStore.storeURL.appendingPathComponent(".gpg-id").path
                guard FileManager.default.fileExists(atPath: gpgIDFile) else {
                    self.passwordStore.eraseStoreData()
                    SVProgressHUD.dismiss {
                        DispatchQueue.main.async {
                            Utils.alert(title: "Error".localize(), message: "NoProperPassRepo.".localize(), controller: self)
                        }
                    }
                    return
                }

                SVProgressHUD.dismiss {
                    self.savePassphraseAndSegue()
                }
            } catch {
                SVProgressHUD.dismiss {
                    self.passwordStore.eraseStoreData()
                    let error = error as NSError
                    var message = error.localizedDescription
                    if let underlyingError = error.userInfo[NSUnderlyingErrorKey] as? NSError {
                        message = "\(message)\n\("UnderlyingError".localize(underlyingError.localizedDescription))"
                    }
                    DispatchQueue.main.async {
                        Utils.alert(title: "Error".localize(), message: message, controller: self)
                    }
                }
            }
        }
    }

    private func savePassphraseAndSegue() {
        let savePassphraseAlert = UIAlertController(
            title: "Done".localize(),
            message: "WantToSaveGitCredential?".localize(),
            preferredStyle: .alert
        )
        savePassphraseAlert.addAction(
            UIAlertAction(title: "No".localize(), style: .default) { _ in
                Defaults.isRememberGitCredentialPassphraseOn = false
                self.passwordStore.gitPassword = nil
                self.passwordStore.gitSSHPrivateKeyPassphrase = nil
                self.performSegue(withIdentifier: "saveGitServerSettingSegue", sender: self)
            }
        )
        savePassphraseAlert.addAction(
            UIAlertAction(title: "Yes".localize(), style: .destructive) { _ in
                Defaults.isRememberGitCredentialPassphraseOn = true
                self.performSegue(withIdentifier: "saveGitServerSettingSegue", sender: self)
            }
        )
        DispatchQueue.main.async {
            self.present(savePassphraseAlert, animated: true)
        }
    }

    @IBAction
    private func importSSHKey(segue: UIStoryboardSegue) {
        guard let sourceController = segue.source as? KeyImporter, sourceController.isReadyToUse() else {
            return
        }
        importSSHKey(using: sourceController)
    }

    private func importSSHKey(using keyImporter: KeyImporter) {
        DispatchQueue.global(qos: .userInitiated).async { [unowned self] in
            do {
                try keyImporter.importKeys()
                DispatchQueue.main.async {
                    SVProgressHUD.showSuccess(withStatus: "Imported".localize())
                    SVProgressHUD.dismiss(withDelay: 1)
                    Defaults.gitSSHKeySource = type(of: keyImporter).keySource
                    self.gitAuthenticationMethod = .key
                    self.sshLabel?.isEnabled = true
                }
            } catch {
                DispatchQueue.main.async {
                    Utils.alert(title: "Error".localize(), message: error.localizedDescription, controller: self)
                }
            }
        }
    }

    // MARK: - Helper Functions

    private func showSSHKeyActionSheet() {
        let optionMenu = UIAlertController(title: nil, message: nil, preferredStyle: .actionSheet)
        optionMenu.addAction(
            UIAlertAction(title: SSHKeyURLImportTableViewController.menuLabel, style: .default) { _ in
                self.performSegue(withIdentifier: "setGitSSHKeyByURLSegue", sender: self)
            }
        )
        optionMenu.addAction(
            UIAlertAction(title: SSHKeyArmorImportTableViewController.menuLabel, style: .default) { _ in
                self.performSegue(withIdentifier: "setGitSSHKeyByArmorSegue", sender: self)
            }
        )
        optionMenu.addAction(
            UIAlertAction(title: SSHKeyFileImportTableViewController.menuLabel, style: .default) { _ in
                self.performSegue(withIdentifier: "setGitSSHKeyByFileSegue", sender: self)
            }
        )

        if isReadyToUse() {
            optionMenu.addAction(
                UIAlertAction(title: "\(Self.menuLabel) (\("Import".localize()))", style: .default) { _ in
                    self.importSSHKey(using: self)
                }
            )
        } else {
            optionMenu.addAction(
                UIAlertAction(title: "\(Self.menuLabel) (\("Tips".localize()))", style: .default) { _ in
                    let title = "Tips".localize()
                    let message = "SshCopyPrivateKeyToPass.".localize()
                    Utils.alert(title: title, message: message, controller: self)
                }
            )
        }

        if Defaults.gitSSHKeySource != nil {
            optionMenu.addAction(
                UIAlertAction(title: "RemoveSShKeys".localize(), style: .destructive) { _ in
                    let alert = UIAlertController.removeConfirmationAlert(title: "RemoveSShKeys".localize(), message: "") { _ in
                        self.passwordStore.removeGitSSHKeys()
                        Defaults.gitSSHKeySource = nil
                        self.sshLabel?.isEnabled = false
                        self.gitAuthenticationMethod = .password
                    }
                    self.present(alert, animated: true, completion: nil)
                }
            )
        }
        optionMenu.addAction(UIAlertAction.cancel())
        optionMenu.popoverPresentationController?.sourceView = authSSHKeyCell
        optionMenu.popoverPresentationController?.sourceRect = authSSHKeyCell.bounds
        present(optionMenu, animated: true)
    }

    private func updateAuthenticationMethodCheckView(for method: GitAuthenticationMethod) {
        let passwordCheckView = authPasswordCell.viewWithTag(1001)
        let sshKeyCheckView = authSSHKeyCell.viewWithTag(1001)

        switch method {
        case .password:
            passwordCheckView?.isHidden = false
            sshKeyCheckView?.isHidden = true
        case .key:
            passwordCheckView?.isHidden = true
            sshKeyCheckView?.isHidden = false
        }
    }

    private func showGitURLFormatHelp() {
        let message = """
        https://example.com[:port]/project.git
        ssh://[user@]server[:port]/project.git
        [user@]server:project.git (no scheme)
        """
        Utils.alert(title: "Git URL Format", message: message, controller: self)
    }
}

extension GitRepositorySettingsTableViewController: KeyImporter {
    static let keySource = KeySource.itunes
    static let label = "ITunesFileSharing".localize()

    func isReadyToUse() -> Bool {
        KeyFileManager.PrivateSSH.doesKeyFileExist()
    }

    func importKeys() throws {
        try KeyFileManager.PrivateSSH.importKeyFromFileSharing()
    }
}
