//
// Copyright 2020 Signal Messenger, LLC
// SPDX-License-Identifier: AGPL-3.0-only
//

import MultipeerConnectivity
import SignalServiceKit
import SignalUI

class OutgoingDeviceTransferQRScanningViewController: DeviceTransferBaseViewController {

    private let qrCodeScanViewController = QRCodeScanViewController(appearance: .unadorned)

    lazy var activityIndicator: UIActivityIndicatorView = {
        let activityIndicator = UIActivityIndicatorView(style: .medium)
        activityIndicator.color = Theme.primaryIconColor
        return activityIndicator
    }()
    lazy var label: UILabel = {
        let label = UILabel()
        label.font = .dynamicTypeBody2
        label.textColor = Theme.primaryTextColor
        label.numberOfLines = 0
        label.lineBreakMode = .byWordWrapping
        label.textAlignment = .center
        return label
    }()
    lazy var hStack: UIStackView = {
        let stackView = UIStackView()
        stackView.axis = .horizontal
        stackView.spacing = 6
        stackView.setContentHuggingVerticalLow()
        stackView.setCompressionResistanceVerticalHigh()

        let leadingSpacer = UIView.hStretchingSpacer()
        stackView.addArrangedSubview(leadingSpacer)

        stackView.addArrangedSubview(activityIndicator)

        stackView.addArrangedSubview(label)
        label.setCompressionResistanceHorizontalHigh()
        label.setCompressionResistanceVerticalHigh()

        let trailingSpacer = UIView.hStretchingSpacer()
        stackView.addArrangedSubview(trailingSpacer)

        leadingSpacer.autoMatch(.width, to: .width, of: trailingSpacer)

        return stackView
    }()
    lazy var captureContainerView: UIView = {
        let view = UIView()

        let qrView = qrCodeScanViewController.view!
        view.addSubview(qrView)

        view.addSubview(maskingView)
        maskingView.autoPinHeightToSuperview()
        maskingView.autoHCenterInSuperview()

        qrView.autoPinEdges(toEdgesOf: maskingView)

        view.addSubview(cameraToggleButton)
        cameraToggleButton.autoSetDimensions(to: .square(32))
        cameraToggleButton.autoPinEdge(.trailing, to: .trailing, of: maskingView)
        cameraToggleButton.autoPinEdge(.bottom, to: .bottom, of: view)

        return view
    }()
    lazy var maskingView: UIView = {
        let maskingView = BezierPathView { layer, bounds in
            let path = UIBezierPath(rect: bounds)

            let circlePath = UIBezierPath(roundedRect: bounds, cornerRadius: bounds.size.height * 0.5)
            path.append(circlePath)
            path.usesEvenOddFillRule = true

            layer.path = path.cgPath
            layer.fillRule = .evenOdd
            layer.fillColor = Theme.actionSheetBackgroundColor.cgColor
        }
        maskingView.autoPinToSquareAspectRatio()
        maskingView.autoSetDimension(.height, toSize: 256)
        return maskingView
    }()

    private lazy var cameraToggleButton: UIButton = {
        let button = UIButton()
        button.setImage(UIImage(named: "switch-camera-28"), animated: false)
        button.tintColor = Theme.primaryIconColor
        button.addTarget(
            self,
            action: #selector(didTapCameraToggle),
            for: .touchUpInside
        )
        return button
    }()

    override func viewDidLoad() {
        super.viewDidLoad()

        let titleLabel = self.titleLabel(
            text: OWSLocalizedString("DEVICE_TRANSFER_SCANNING_TITLE",
                                    comment: "The title for the action sheet asking the user to scan the QR code to transfer")
        )
        contentView.addArrangedSubview(titleLabel)

        contentView.addArrangedSubview(.spacer(withHeight: 12))

        let explanationLabel = self.explanationLabel(
            explanationText: OWSLocalizedString("DEVICE_TRANSFER_SCANNING_EXPLANATION",
                                               comment: "The explanation for the action sheet asking the user to scan the QR code to transfer")
        )
        contentView.addArrangedSubview(explanationLabel)

        let topSpacer = UIView.vStretchingSpacer()
        contentView.addArrangedSubview(topSpacer)

        contentView.addArrangedSubview(captureContainerView)
        contentView.addArrangedSubview(hStack)
        hStack.isHidden = true

        let bottomSpacer = UIView.vStretchingSpacer()
        contentView.addArrangedSubview(bottomSpacer)
        topSpacer.autoMatch(.height, to: .height, of: bottomSpacer)
        topSpacer.autoSetDimension(.height, toSize: 25, relation: .greaterThanOrEqual)

        qrCodeScanViewController.delegate = self
        addChild(qrCodeScanViewController)
    }

    @objc
    private func didTapCameraToggle() {
        qrCodeScanViewController.prefersFrontFacingCamera =
            !qrCodeScanViewController.prefersFrontFacingCamera
    }
}

// MARK: -

extension OutgoingDeviceTransferQRScanningViewController: QRCodeScanDelegate {

    func qrCodeScanViewDismiss(_ qrCodeScanViewController: QRCodeScanViewController) {
        AssertIsOnMainThread()

        navigationController?.popViewController(animated: true)
    }

    func qrCodeScanViewScanned(_ qrCodeScanViewController: QRCodeScanViewController,
                               qrCodeData: Data?,
                               qrCodeString: String?) -> QRCodeScanOutcome {
        AssertIsOnMainThread()

        guard let qrCodeString = qrCodeString else {
            // TODO: The user has probably scanned the wrong QR code.
            // We could show an error alert to help them resolve the issue.
            // For now, ignore the QR code and continue scanning.
            owsFailDebug("QR code does not have a valid string payload.")
            return .continueScanning
        }

        guard let scannedURL = URL(string: qrCodeString) else {
            // TODO: The user has probably scanned the wrong QR code.
            // We could show an error alert to help them resolve the issue.
            // For now, ignore the QR code and continue scanning.
            owsFailDebug("QR code does not have a valid URL payload: \(qrCodeString).")
            return .continueScanning
        }

        // Ignore if a non-signal url was scanned
        guard scannedURL.scheme == UrlOpener.Constants.sgnlPrefix else {
            // TODO: The user has probably scanned the wrong QR code.
            // We could show an error alert to help them resolve the issue.
            // For now, ignore the QR code and continue scanning.
            owsFailDebug("QR code does not have a transfer URL payload: \(qrCodeString).")
            return .continueScanning
        }

        showConnecting()

        DispatchQueue.global().async {
            do {
                let (peerId, certificateHash) = try self.deviceTransferService.parseTransferURL(scannedURL)
                self.deviceTransferService.addObserver(self)
                try self.deviceTransferService.transferAccountToNewDevice(with: peerId, certificateHash: certificateHash)
            } catch {
                owsFailDebug("Something went wrong \(error)")

                if let error = error as? DeviceTransferService.Error {
                    switch error {
                    case .unsupportedVersion:
                        self.showError(
                            text: OWSLocalizedString("DEVICE_TRANSFER_ERROR_UNSUPPORTED_VERSION",
                                                    comment: "An error indicating the user must update their device before trying to transfer.")
                        )
                        return
                    case .modeMismatch:
                        let desiredMode: DeviceTransferService.TransferMode =
                            DependenciesBridge.shared.tsAccountManager.registrationStateWithMaybeSneakyTransaction.isPrimaryDevice ?? true
                            ? .linked : .primary
                        switch desiredMode {
                        case .linked:
                            self.showError(
                                text: OWSLocalizedString("DEVICE_TRANSFER_ERROR_MODE_MISMATCH_LINKED",
                                                        comment: "An error indicating the user must scan this code with a linked device to transfer.")
                            )
                        case .primary:
                            self.showError(
                                text: OWSLocalizedString("DEVICE_TRANSFER_ERROR_MODE_MISMATCH_PRIMARY",
                                                        comment: "An error indicating the user must scan this code with a primary device to transfer.")
                            )
                        }
                        return
                    default:
                        break
                    }
                }

                self.showError(
                    text: OWSLocalizedString("DEVICE_TRANSFER_ERROR_GENERIC",
                                            comment: "An error indicating that something went wrong with the transfer and it could not complete")
                )
            }
        }

        return .stopScanning
    }

    func showConnecting() {
        captureContainerView.isHidden = true
        hStack.isHidden = false
        label.text = OWSLocalizedString("DEVICE_TRANSFER_SCANNING_CONNECTING",
                                       comment: "Text indicating that we are connecting to the scanned device")
        label.textColor = Theme.primaryTextColor
        activityIndicator.isHidden = false
        activityIndicator.startAnimating()
    }

    func showError(text: String) {
        DispatchMainThreadSafe {
            self.captureContainerView.isHidden = true
            self.hStack.isHidden = false
            self.label.text = text
            self.label.textColor = .ows_accentRed
            self.activityIndicator.stopAnimating()
            self.activityIndicator.isHidden = true
        }
    }
}

// MARK: -

extension OutgoingDeviceTransferQRScanningViewController: DeviceTransferServiceObserver {
    func deviceTransferServiceDiscoveredNewDevice(peerId: MCPeerID, discoveryInfo: [String: String]?) {}

    func deviceTransferServiceDidStartTransfer(progress: Progress) {
        deviceTransferService.removeObserver(self)
        let vc = OutgoingDeviceTransferProgressViewController(progress: progress)
        navigationController?.pushViewController(vc, animated: true)
    }

    func deviceTransferServiceDidEndTransfer(error: DeviceTransferService.Error?) {
        if error != nil {
            showError(
                text: OWSLocalizedString("DEVICE_TRANSFER_ERROR_GENERIC",
                                        comment: "An error indicating that something went wrong with the transfer and it could not complete")
            )
        }
    }

    func deviceTransferServiceDidRequestAppRelaunch() {
        owsFail("Relaunch not supported for outgoing transfer; only on the receiving device during transfer")
    }
}
