//
//  SendViewController.swift
//  ZcashLightClientSample
//
//  Created by Francisco Gindre on 12/3/19.
//  Copyright © 2019 Electric Coin Company. All rights reserved.
//

import UIKit
import ZcashLightClientKit
import KRProgressHUD

class SendViewController: UIViewController {
    @IBOutlet weak var addressLabel: UILabel!
    @IBOutlet weak var amountLabel: UILabel!
    @IBOutlet weak var addressTextField: UITextField!
    @IBOutlet weak var amountTextField: UITextField!
    @IBOutlet weak var balanceLabel: UILabel!
    @IBOutlet weak var verifiedBalanceLabel: UILabel!
    @IBOutlet weak var maxFunds: UISwitch!
    @IBOutlet weak var sendButton: UIButton!
    @IBOutlet weak var synchronizerStatusLabel: UILabel!
    @IBOutlet weak var memoField: UITextView!
    @IBOutlet weak var charactersLeftLabel: UILabel!
    
    let characterLimit: Int = 512
    
    var wallet = Initializer.shared

    // swiftlint:disable:next implicitly_unwrapped_optional
    var synchronizer: Synchronizer!
    
    override func viewDidLoad() {
        super.viewDidLoad()
        synchronizer = AppDelegate.shared.sharedSynchronizer
        // swiftlint:disable:next force_try
        _ = try! synchronizer.prepare(with: DemoAppConfig.seed)
        let tapRecognizer = UITapGestureRecognizer(target: self, action: #selector(viewTapped(_:)))
        self.view.addGestureRecognizer(tapRecognizer)
        setUp()
    }
    
    override func viewDidAppear(_ animated: Bool) {
        super.viewDidAppear(animated)
        do {
            try synchronizer.start(retry: false)
            self.synchronizerStatusLabel.text = SDKSynchronizer.textFor(state: synchronizer.status)
        } catch {
            self.synchronizerStatusLabel.text = SDKSynchronizer.textFor(state: synchronizer.status)
            fail(error)
        }
    }

    override func viewDidDisappear(_ animated: Bool) {
        super.viewDidDisappear(animated)

        // swiftlint:disable:next force_try
        try! synchronizer.stop() // Fail on purpose if this throws.
    }
    
    @objc func viewTapped(_ recognizer: UITapGestureRecognizer) {
        let point = recognizer.location(in: self.view)
        if addressTextField.isFirstResponder && !addressTextField.frame.contains(point) {
            addressTextField.resignFirstResponder()
        } else if amountTextField.isFirstResponder && !amountTextField.frame.contains(point) {
            amountTextField.resignFirstResponder()
        } else if memoField.isFirstResponder &&
            !memoField.frame.contains(point) {
            memoField.resignFirstResponder()
        }
    }
    
    func setUp() {
        balanceLabel.text = format(balance: wallet.getBalance())
        verifiedBalanceLabel.text = format(balance: wallet.getVerifiedBalance())
        toggleSendButton()
        memoField.text = ""
        memoField.layer.borderColor = UIColor.gray.cgColor
        memoField.layer.borderWidth = 1
        memoField.layer.cornerRadius = 5
        charactersLeftLabel.text = textForCharacterCount(0)
        let center = NotificationCenter.default
        
        center.addObserver(
            self,
            selector: #selector(synchronizerStarted(_:)),
            name: Notification.Name.synchronizerStarted,
            object: synchronizer
        )

        center.addObserver(
            self,
            selector: #selector(synchronizerSynced(_:)),
            name: Notification.Name.synchronizerSynced,
            object: synchronizer
        )

        center.addObserver(
            self,
            selector: #selector(synchronizerStopped(_:)),
            name: Notification.Name.synchronizerStopped,
            object: synchronizer
        )

        center.addObserver(
            self,
            selector: #selector(synchronizerUpdated(_:)),
            name: Notification.Name.synchronizerProgressUpdated,
            object: synchronizer
        )
    }
    
    func format(balance: Zatoshi = Zatoshi()) -> String {
        "Zec \(balance.formattedString ?? "0.0")"
    }
    
    func toggleSendButton() {
        sendButton.isEnabled = isFormValid()
    }
    
    func maxFundsOn() {
        let fee = Zatoshi(10000)
        let max: Zatoshi = wallet.getVerifiedBalance() - fee
        amountTextField.text = format(balance: max)
        amountTextField.isEnabled = false
    }
    
    func maxFundsOff() {
        amountTextField.isEnabled = true
    }
    
    func isFormValid() -> Bool {
        switch synchronizer.status {
        case .synced:
            return isBalanceValid() && isAmountValid() && isRecipientValid()
        default:
            return false
        }
    }
    
    func isBalanceValid() -> Bool {
        wallet.getVerifiedBalance() > .zero
    }
    
    func isAmountValid() -> Bool {
        guard
            let value = amountTextField.text,
            let amount = NumberFormatter.zcashNumberFormatter.number(from: value).flatMap({ Zatoshi($0.int64Value) }),
            amount <= wallet.getVerifiedBalance()
        else {
            return false
        }
        
        return true
    }
    
    func isRecipientValid() -> Bool {
        guard let addr = self.addressTextField.text else {
            return false
        }
        return wallet.isValidSaplingAddress(addr) || wallet.isValidTransparentAddress(addr)
    }
    
    @IBAction func maxFundsValueChanged(_ sender: Any) {
        if maxFunds.isOn {
            maxFundsOn()
        } else {
            maxFundsOff()
        }
    }
    
    @IBAction func send(_ sender: Any) {
        guard isFormValid() else {
            loggerProxy.warn("WARNING: Form is invalid")
            return
        }

        let alert = UIAlertController(
            title: "About To send funds!",
            // swiftlint:disable:next line_length
            message: "This is an ugly confirmation message. You should come up with something fancier that lets the user be sure about sending funds without disturbing the user experience with an annoying alert like this one",
            preferredStyle: UIAlertController.Style.alert
        )

        let sendAction = UIAlertAction(
            title: "Send!",
            style: UIAlertAction.Style.default,
            handler: { _ in
                self.send()
            }
        )

        let cancelAction = UIAlertAction(
            title: "Go back! I'm not sure about this.",
            style: UIAlertAction.Style.destructive,
            handler: { _ in
                self.cancel()
            }
        )

        alert.addAction(sendAction)
        alert.addAction(cancelAction)
        
        self.present(alert, animated: true, completion: nil)
    }
    
    func send() {
        guard
            isFormValid(),
            let amount = amountTextField.text,
            let zec = NumberFormatter.zcashNumberFormatter.number(from: amount).flatMap({ Zatoshi($0.int64Value) }),
            let recipient = addressTextField.text
        else {
            loggerProxy.warn("WARNING: Form is invalid")
            return
        }

        guard let spendingKey = try? DerivationTool(
            networkType: kZcashNetwork.networkType
        )
            .deriveUnifiedSpendingKey(
                seed: DemoAppConfig.seed,
                accountIndex: 0
            )
        else {
            loggerProxy.error("NO SPENDING KEY")
            return
        }
        
        KRProgressHUD.show()
        
        Task { @MainActor in
            do {
                let pendingTransaction = try await synchronizer.sendToAddress(
                    spendingKey: spendingKey,
                    zatoshi: zec,
                    // swiftlint:disable:next force_try
                    toAddress: try! Recipient(recipient, network: kZcashNetwork.networkType),
                    memo: try! self.memoField.text.asMemo()
                )
                KRProgressHUD.dismiss()
                loggerProxy.info("transaction created: \(pendingTransaction)")
            } catch {
                fail(error)
                loggerProxy.error("SEND FAILED: \(error)")
            }
        }
    }
    
    func fail(_ error: Error) {
        let alert = UIAlertController(
            title: "Send failed!",
            message: "\(error)",
            preferredStyle: UIAlertController.Style.alert
        )

        let action = UIAlertAction(title: "OK :(", style: UIAlertAction.Style.default, handler: nil)
        alert.addAction(action)
        self.present(alert, animated: true, completion: nil)
    }
    
    func cancel() {}
    
    // MARK: synchronizer notifications
    @objc func synchronizerUpdated(_ notification: Notification) {
        DispatchQueue.main.async { [weak self] in
            guard let self = self else {
                return
            }
            self.synchronizerStatusLabel.text = SDKSynchronizer.textFor(state: self.synchronizer.status)
        }
    }
    
    @objc func synchronizerStarted(_ notification: Notification) {
        DispatchQueue.main.async { [weak self] in
            guard let self = self else {
                return
            }
            self.synchronizerStatusLabel.text = SDKSynchronizer.textFor(state: self.synchronizer.status)
        }
    }
    
    @objc func synchronizerStopped(_ notification: Notification) {
        DispatchQueue.main.async { [weak self] in
            guard let self = self else {
                return
            }
            self.synchronizerStatusLabel.text = SDKSynchronizer.textFor(state: self.synchronizer.status)
        }
    }
    
    @objc func synchronizerSynced(_ notification: Notification) {
        DispatchQueue.main.async { [weak self] in
            guard let self = self else {
                return
            }
            self.synchronizerStatusLabel.text = SDKSynchronizer.textFor(state: self.synchronizer.status)
        }
    }
    
    func textForCharacterCount(_ count: Int) -> String {
        "\(count) of \(characterLimit) bytes left"
    }
}

extension SendViewController: UITextFieldDelegate {
    func textFieldShouldBeginEditing(_ textField: UITextField) -> Bool {
        if textField == amountTextField {
            return !maxFunds.isOn
        }
        return true
    }
    
    func textFieldDidEndEditing(_ textField: UITextField) {
        textField.resignFirstResponder()
        toggleSendButton()
    }
    
    func textFieldShouldReturn(_ textField: UITextField) -> Bool {
        if textField == amountTextField {
            addressTextField.becomeFirstResponder()
            return false
        }
        textField.resignFirstResponder()
        return true
    }
}

extension SendViewController: UITextViewDelegate {
    func textView(_ textView: UITextView, shouldChangeTextIn range: NSRange, replacementText text: String) -> Bool {
        let userPressedDelete = text.isEmpty && range.length > 0
        return textView.text.utf8.count < characterLimit || userPressedDelete
    }
    
    func textViewDidChange(_ textView: UITextView) {
        self.charactersLeftLabel.text = textForCharacterCount(textView.text.utf8.count)
    }
}

extension SDKSynchronizer {
    static func textFor(state: SyncStatus) -> String {
        switch state {
        case .downloading(let progress):
            return "Downloading \(progress.progressHeight)/\(progress.targetHeight)"

        case .enhancing(let enhanceProgress):
            return "Enhancing tx \(enhanceProgress.enhancedTransactions) of \(enhanceProgress.totalTransactions)"

        case .fetching:
            return "fetching UTXOs"

        case .scanning(let scanProgress):
            return "Scanning: \(scanProgress.progressHeight)/\(scanProgress.targetHeight)"

        case .disconnected:
            return "disconnected 💔"

        case .stopped:
            return "Stopped 🚫"

        case .synced:
            return "Synced 😎"

        case .unprepared:
            return "Unprepared 😅"

        case .validating:
            return "Validating"

        case .error(let e):
            return "Error: \(e.localizedDescription)"
        }
    }
}

extension Optional where Wrapped == String {
    func asMemo() throws -> Memo {
        switch self {
        case .some(let string):
            return try Memo(string: string)
        case .none:
            return .empty
        }
    }
}
