/*
 Copyright 2019 New Vector Ltd

 Licensed under the Apache License, Version 2.0 (the "License");
 you may not use this file except in compliance with the License.
 You may obtain a copy of the License at

 http://www.apache.org/licenses/LICENSE-2.0

 Unless required by applicable law or agreed to in writing, software
 distributed under the License is distributed on an "AS IS" BASIS,
 WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
 See the License for the specific language governing permissions and
 limitations under the License.
 */

import UIKit

@objc protocol SettingsKeyBackupTableViewSectionDelegate: class {
    func settingsKeyBackupTableViewSectionDidUpdate(_ settingsKeyBackupTableViewSection: SettingsKeyBackupTableViewSection)

    func settingsKeyBackupTableViewSection(_ settingsKeyBackupTableViewSection: SettingsKeyBackupTableViewSection, textCellForRow: Int) -> MXKTableViewCellWithTextView
    func settingsKeyBackupTableViewSection(_ settingsKeyBackupTableViewSection: SettingsKeyBackupTableViewSection, buttonCellForRow: Int) -> MXKTableViewCellWithButton


    func settingsKeyBackupTableViewSectionShowKeyBackupSetup(_ settingsKeyBackupTableViewSection: SettingsKeyBackupTableViewSection)
    func settingsKeyBackup(_ settingsKeyBackupTableViewSection: SettingsKeyBackupTableViewSection, showKeyBackupRecover keyBackupVersion: MXKeyBackupVersion)
    func settingsKeyBackup(_ settingsKeyBackupTableViewSection: SettingsKeyBackupTableViewSection, showKeyBackupDeleteConfirm keyBackupVersion: MXKeyBackupVersion)

    func settingsKeyBackup(_ settingsKeyBackupTableViewSection: SettingsKeyBackupTableViewSection, showActivityIndicator show: Bool)
    func settingsKeyBackup(_ settingsKeyBackupTableViewSection: SettingsKeyBackupTableViewSection, showError error: Error)
}

private enum BackupRows {
    case info(text: String)
    case createAction
    case restoreAction(keyBackupVersion: MXKeyBackupVersion, title: String)
    case deleteAction(keyBackupVersion: MXKeyBackupVersion)
}

@objc final class SettingsKeyBackupTableViewSection: NSObject {

    // MARK: - Properties

    @objc weak var delegate: SettingsKeyBackupTableViewSectionDelegate?

    // MARK: Private

    // This view class holds the model because the model is in pure Swift
    // whereas this class can be used from objC
    private var viewModel: SettingsKeyBackupViewModelType!

    // Need to know the state to make `cellForRow` deliver cells accordingly
    private var viewState: SettingsKeyBackupViewState = .checkingBackup {
        didSet {
            self.updateBackupRows()
        }
    }

    private var userDevice: MXDeviceInfo
    
    private var backupRows: [BackupRows] = []

    // MARK: - Public

    @objc init(withKeyBackup keyBackup: MXKeyBackup, userDevice: MXDeviceInfo) {
        self.viewModel = SettingsKeyBackupViewModel(keyBackup: keyBackup)
        self.userDevice = userDevice
        super.init()
        self.viewModel.viewDelegate = self

        self.viewModel.process(viewAction: .load)
    }
    
    @objc func numberOfRows() -> Int {
        return self.backupRows.count
    }
    
    @objc func cellForRow(atRow row: Int) -> UITableViewCell {
        guard let delegate = self.delegate else {
            return UITableViewCell()
        }
        
        let backupRow = self.backupRows[row]
        
        var cell: UITableViewCell
        switch backupRow {
        case .info(let infoText):
            let infoCell: MXKTableViewCellWithTextView = delegate.settingsKeyBackupTableViewSection(self, textCellForRow: row)
            infoCell.mxkTextView.text = infoText
            cell = infoCell
        case .createAction:
            cell = self.buttonCellForCreate(atRow: row)
        case .restoreAction(keyBackupVersion: let keyBackupVersion, let title):
            cell = self.buttonCellForRestore(keyBackupVersion: keyBackupVersion, title: title, atRow: row)
        case .deleteAction(keyBackupVersion: let keyBackupVersion):
            cell = self.buttonCellForDelete(keyBackupVersion: keyBackupVersion, atRow: row)
        }
        cell.backgroundColor = themeService.attrs.primaryBgColor
        return cell
    }

    @objc func reload() {
        self.viewModel.process(viewAction: .load)
    }

    @objc func delete(keyBackupVersion: MXKeyBackupVersion) {
        self.viewModel.process(viewAction: .delete(keyBackupVersion))
    }

    // MARK: - Data Computing
    
    private func updateBackupRows() {
        
        let backupRows: [BackupRows]
        
        switch self.viewState {
        case .checkingBackup:
            
            let info = CKLocalization.string(byKey: "settings_key_backup_info")
            let checking = CKLocalization.string(byKey: "settings_key_backup_info_checking")
            let strings = [info, "", checking]
            let text = strings.joined(separator: "\n")
            
            backupRows = [
                .info(text: text)
            ]
            
        case .noBackup:
            
            let noBackup = CKLocalization.string(byKey: "settings_key_backup_info_none")
            let info = CKLocalization.string(byKey: "settings_key_backup_info")
            let signoutWarning = CKLocalization.string(byKey: "settings_key_backup_info_signout_warning")
            let strings = [noBackup, "", info, "", signoutWarning]
            let backupInfoText = strings.joined(separator: "\n")
            
            backupRows = [
                .info(text: backupInfoText),
                .createAction
            ]
            
        case .backup(let keyBackupVersion, let keyBackupVersionTrust):
            
            let info = CKLocalization.string(byKey: "settings_key_backup_info")
            let backupStatus = CKLocalization.string(byKey: "settings_key_backup_info_valid")
            let backupStrings = [info, "", backupStatus]
            let backupInfoText = backupStrings.joined(separator: "\n")
            
            let version = String(format: CKLocalization.string(byKey: "settings_key_backup_info_version"), keyBackupVersion.version ?? "")
            let algorithm = String(format: CKLocalization.string(byKey: "settings_key_backup_info_algorithm"), keyBackupVersion.algorithm)
            let uploadStatus = CKLocalization.string(byKey: "settings_key_backup_info_progress_done")
            let additionalStrings = [version, algorithm, uploadStatus]
            let additionalInfoText = additionalStrings.joined(separator: "\n")
            
            let backupTrust = self.stringForKeyBackupTrust(keyBackupVersionTrust)
            let backupTrustInfoText = backupTrust.joined(separator: "\n")
            
            var backupViewStateRows: [BackupRows] = [
                .info(text: backupInfoText),
                .info(text: additionalInfoText),
                .info(text: backupTrustInfoText)
            ]
            
            // TODO: Do not display restore button if all keys are stored on the device
            if true {
                backupViewStateRows.append(.restoreAction(keyBackupVersion: keyBackupVersion, title: CKLocalization.string(byKey: "settings_key_backup_button_restore")))
            }
            
            backupViewStateRows.append(.deleteAction(keyBackupVersion: keyBackupVersion))
            
            backupRows = backupViewStateRows
            
        case .backupAndRunning(let keyBackupVersion, let keyBackupVersionTrust, let backupProgress):
            
            let info = CKLocalization.string(byKey: "settings_key_backup_info")
            let backupStatus = CKLocalization.string(byKey: "settings_key_backup_info_valid")
            let backupStrings = [info, "", backupStatus]
            let backupInfoText = backupStrings.joined(separator: "\n")
            
            let remaining = backupProgress.totalUnitCount - backupProgress.completedUnitCount
            let version = String(format: CKLocalization.string(byKey: "settings_key_backup_info_version"), keyBackupVersion.version ?? "")
            let algorithm = String(format: CKLocalization.string(byKey: "settings_key_backup_info_algorithm"), keyBackupVersion.algorithm)
            
            let uploadStatus = String(format: CKLocalization.string(byKey: "settings_key_backup_info_progress"), String(remaining))
            let additionalStrings = [version, algorithm, uploadStatus]
            let additionalInfoText = additionalStrings.joined(separator: "\n")
            
            let backupTrust = self.stringForKeyBackupTrust(keyBackupVersionTrust)
            let backupTrustInfoText = backupTrust.joined(separator: "\n")
            
            var backupAndRunningViewStateRows: [BackupRows] = [
                .info(text: backupInfoText),
                .info(text: additionalInfoText),
                .info(text: backupTrustInfoText)
            ]
            
            // TODO: Do not display restore button if all keys are stored on the device
            if true {
                backupAndRunningViewStateRows.append(.restoreAction(keyBackupVersion: keyBackupVersion, title: CKLocalization.string(byKey: "settings_key_backup_button_restore")))
            }
            
            backupAndRunningViewStateRows.append(.deleteAction(keyBackupVersion: keyBackupVersion))
            
            backupRows = backupAndRunningViewStateRows
            
        case .backupNotTrusted(let keyBackupVersion, let keyBackupVersionTrust):
            
            let info = CKLocalization.string(byKey: "settings_key_backup_info")
            let backupStatus = CKLocalization.string(byKey: "settings_key_backup_info_not_valid")
            let signoutWarning = CKLocalization.string(byKey: "settings_key_backup_info_signout_warning")
            let backupStrings = [info, "", backupStatus, "", signoutWarning]
            let backupInfoText = backupStrings.joined(separator: "\n")
            
            let version = String(format: CKLocalization.string(byKey: "settings_key_backup_info_version"), keyBackupVersion.version ?? "")
            let algorithm = String(format: CKLocalization.string(byKey: "settings_key_backup_info_algorithm"), keyBackupVersion.algorithm)
            let additionalStrings = [version, algorithm]
            let additionalInfoText = additionalStrings.joined(separator: "\n")
            
            let backupTrust = self.stringForKeyBackupTrust(keyBackupVersionTrust)
            let backupTrustInfoText = backupTrust.joined(separator: "\n")
            
            var backupNotTrustedViewStateRows: [BackupRows] = [
                .info(text: backupInfoText),
                .info(text: additionalInfoText),
                .info(text: backupTrustInfoText)
            ]
            
            // TODO: Do not display restore button if all keys are stored on the device
            if true {
                backupNotTrustedViewStateRows.append(.restoreAction(keyBackupVersion: keyBackupVersion, title: CKLocalization.string(byKey: "settings_key_backup_button_connect")))
            }
            
            backupNotTrustedViewStateRows.append(.deleteAction(keyBackupVersion: keyBackupVersion))
            
            backupRows = backupNotTrustedViewStateRows
        }
        
        self.backupRows = backupRows
    }

    private func stringForKeyBackupTrust(_ keyBackupVersionTrust: MXKeyBackupVersionTrust) -> [String] {

        return keyBackupVersionTrust.signatures.map { (signature) -> String in
            guard let device = signature.device else {
                return String(format: CKLocalization.string(byKey: "settings_key_backup_info_trust_signature_unknown"), signature.deviceId)
            }

            let displayName = device.displayName ?? device.deviceId ?? ""

            if device.fingerprint == self.userDevice.fingerprint {
                return CKLocalization.string(byKey: "settings_key_backup_info_trust_signature_valid")
            } else if signature.valid && (device.verified == MXDeviceVerified) {
                return String(format: CKLocalization.string(byKey: "settings_key_backup_info_trust_signature_valid_device_verified"), displayName)
            } else if signature.valid && (device.verified != MXDeviceVerified) {
                return String(format: CKLocalization.string(byKey: "settings_key_backup_info_trust_signature_valid_device_unverified"), displayName)
            } else if !signature.valid && (device.verified == MXDeviceVerified) {
                return String(format: CKLocalization.string(byKey: "settings_key_backup_info_trust_signature_invalid_device_verified"), displayName)
            } else if !signature.valid && (device.verified != MXDeviceVerified) {
                return String(format: CKLocalization.string(byKey: "settings_key_backup_info_trust_signature_invalid_device_unverified"), displayName)
            }

            return ""
        }
    }

    // MARK: - Button cells

    private func buttonCellForCreate(atRow row: Int) -> UITableViewCell {

        guard let delegate = self.delegate else {
            return UITableViewCell()
        }

        let cell: MXKTableViewCellWithButton = delegate.settingsKeyBackupTableViewSection(self, buttonCellForRow: row)

        let btnTitle = CKLocalization.string(byKey: "settings_key_backup_button_create")
        cell.mxkButton.setTitle(btnTitle, for: .normal)
        cell.mxkButton.setTitle(btnTitle, for: .highlighted)

        cell.mxkButton.vc_addAction {
            self.viewModel.process(viewAction: .create)
        }
        
        cell.mxkButton.tintColor = CKColor.Text.tint
    
        return cell
    }

    private func buttonCellForRestore(keyBackupVersion: MXKeyBackupVersion, title: String, atRow row: Int) -> UITableViewCell {
        guard let delegate = self.delegate else {
            return UITableViewCell()
        }

        let cell: MXKTableViewCellWithButton = delegate.settingsKeyBackupTableViewSection(self, buttonCellForRow: row)
        cell.mxkButton.setTitle(title, for: .normal)
        cell.mxkButton.setTitle(title, for: .highlighted)
        cell.mxkButton.vc_addAction {
            self.viewModel.process(viewAction: .restore(keyBackupVersion))
        }
        
        cell.mxkButton.tintColor = CKColor.Text.tint
        
        return cell
    }

    private func buttonCellForDelete(keyBackupVersion: MXKeyBackupVersion, atRow row: Int) -> UITableViewCell {
        guard let delegate = self.delegate else {
            return UITableViewCell()
        }

        let cell: MXKTableViewCellWithButton = delegate.settingsKeyBackupTableViewSection(self, buttonCellForRow: row)
        let btnTitle = CKLocalization.string(byKey: "settings_key_backup_button_delete") 
        cell.mxkButton.setTitle(btnTitle, for: .normal)
        cell.mxkButton.setTitle(btnTitle, for: .highlighted)
        cell.mxkButton.tintColor = CKColor.Text.warning
        cell.mxkButton.vc_addAction {
            self.viewModel.process(viewAction: .confirmDelete(keyBackupVersion))
        }
        
        cell.mxkButton.tintColor = CKColor.Text.warning
        
        return cell
    }
}


// MARK: - KeyBackupSetupRecoveryKeyViewModelViewDelegate
extension SettingsKeyBackupTableViewSection: SettingsKeyBackupViewModelViewDelegate {
    func settingsKeyBackupViewModel(_ viewModel: SettingsKeyBackupViewModelType, didUpdateViewState viewState: SettingsKeyBackupViewState) {
        self.viewState = viewState

        // The tableview datasource will call `self.cellForRow()`
        self.delegate?.settingsKeyBackupTableViewSectionDidUpdate(self)
    }

    func settingsKeyBackupViewModel(_ viewModel: SettingsKeyBackupViewModelType, didUpdateNetworkRequestViewState networkRequestViewSate: SettingsKeyBackupNetworkRequestViewState) {
        switch networkRequestViewSate {
        case .loading:
            self.delegate?.settingsKeyBackup(self, showActivityIndicator: true)
        case .loaded:
            self.delegate?.settingsKeyBackup(self, showActivityIndicator: false)
        case .error(let error):
            self.delegate?.settingsKeyBackup(self, showError: error)
        }
    }

    func settingsKeyBackupViewModelShowKeyBackupSetup(_ viewModel: SettingsKeyBackupViewModelType) {
        self.delegate?.settingsKeyBackupTableViewSectionShowKeyBackupSetup(self)
    }

    func settingsKeyBackup(_ viewModel: SettingsKeyBackupViewModelType, showKeyBackupRecover keyBackupVersion: MXKeyBackupVersion) {
        self.delegate?.settingsKeyBackup(self, showKeyBackupRecover: keyBackupVersion)
    }
    
    func settingsKeyBackup(_ viewModel: SettingsKeyBackupViewModelType, showKeyBackupDeleteConfirm keyBackupVersion: MXKeyBackupVersion) {
        self.delegate?.settingsKeyBackup(self, showKeyBackupDeleteConfirm: keyBackupVersion)
    }
}
