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

import LibSignalClient
import SignalServiceKit
import UIKit

class FingerprintScanViewController: OWSViewController, OWSNavigationChildController {

    private let recipientAci: Aci
    private let contactName: String
    private let fingerprint: CombinedFingerprints

    private lazy var qrCodeScanViewController = QRCodeScanViewController(appearance: .framed)

    init(
        recipientAci: Aci,
        recipientName: String,
        fingerprint: CombinedFingerprints,
    ) {
        self.recipientAci = recipientAci

        self.fingerprint = fingerprint
        self.contactName = recipientName

        super.init()

        title = CommonStrings.scanQRCodeTitle
    }

    override func viewDidLoad() {
        super.viewDidLoad()

        view.backgroundColor = .Signal.background

        qrCodeScanViewController.delegate = self
        view.addSubview(qrCodeScanViewController.view)
        qrCodeScanViewController.view.autoPinWidthToSuperview()
        qrCodeScanViewController.view.autoPin(toTopLayoutGuideOf: self, withInset: 0)
        addChild(qrCodeScanViewController)

        let footerView = UIView()
        footerView.backgroundColor = .Signal.secondaryBackground
        view.addSubview(footerView)
        footerView.autoPinWidthToSuperview()
        footerView.autoPinEdge(.top, to: .bottom, of: qrCodeScanViewController.view)
        footerView.autoPinEdge(toSuperviewEdge: .bottom)

        let cameraInstructionLabel = UILabel()
        cameraInstructionLabel.text = NSLocalizedString(
            "SCAN_CODE_INSTRUCTIONS",
            comment: "label presented once scanning (camera) view is visible.",
        )
        cameraInstructionLabel.font = .systemFont(ofSize: .scaleFromIPhone5To7Plus(14, 18))
        cameraInstructionLabel.textColor = .Signal.secondaryLabel
        cameraInstructionLabel.textAlignment = .center
        cameraInstructionLabel.numberOfLines = 0
        cameraInstructionLabel.lineBreakMode = .byWordWrapping
        footerView.addSubview(cameraInstructionLabel)
        cameraInstructionLabel.autoPinWidthToSuperview(withMargin: .scaleFromIPhone5To7Plus(16, 30))
        let instructionsVMargin = CGFloat.scaleFromIPhone5To7Plus(10, 20)
        cameraInstructionLabel.autoPin(toBottomLayoutGuideOf: self, withInset: instructionsVMargin)
        cameraInstructionLabel.autoPinEdge(toSuperviewEdge: .top, withInset: instructionsVMargin)
    }

    override var supportedInterfaceOrientations: UIInterfaceOrientationMask {
        UIDevice.current.isIPad ? .all : .portrait
    }

    override func dismiss(animated flag: Bool, completion: (() -> Void)? = nil) {
        qrCodeScanViewController.view.isHidden = true
        super.dismiss(animated: flag, completion: completion)
    }

    // MARK: -

    private func verifyCombinedFingerprintData(_ combinedFingerprintData: Data) {
        AssertIsOnMainThread()

        func showSuccess() {
            FingerprintScanViewController.showVerificationSucceeded(
                from: self,
                identityKey: fingerprint.remote.identityKey,
                recipientAci: recipientAci,
                contactName: contactName,
                tag: "[\(type(of: self))]",
            )
        }

        func showFailure(localizedErrorDescription: String) {
            FingerprintScanViewController.showVerificationFailed(
                from: self,
                isUserError: false,
                localizedErrorDescription: localizedErrorDescription,
                retry: { self.qrCodeScanViewController.tryToStartScanning() },
                cancel: { self.navigationController?.popViewController(animated: true) },
                tag: "[\(type(of: self))]",
            )
        }

        let combinedFingerprints: Textsecure_CombinedFingerprints
        do {
            combinedFingerprints = try Textsecure_CombinedFingerprints(serializedBytes: combinedFingerprintData)
        } catch {
            Logger.warn("fingerprint failure: \(error)")
            showFailure(localizedErrorDescription: OWSLocalizedString("PRIVACY_VERIFICATION_FAILURE_INVALID_QRCODE", comment: "alert body"))
            return
        }
        do throws(CombinedFingerprints.MatchError) {
            try fingerprint.checkAgainst(combinedFingerprints: combinedFingerprints)
            showSuccess()
        } catch {
            Logger.warn("verification failure: \(error)")
            let message: String
            switch error {
            case .theyHaveOldVersion:
                message = OWSLocalizedString("PRIVACY_VERIFICATION_FAILED_WITH_OLD_REMOTE_VERSION", comment: "alert body")
            case .weHaveOldVersion:
                message = OWSLocalizedString("PRIVACY_VERIFICATION_FAILED_WITH_OLD_LOCAL_VERSION", comment: "alert body")
            case .theyHaveWrongKeyForUs:
                let descriptionFormat = OWSLocalizedString(
                    "PRIVACY_VERIFICATION_FAILED_THEY_HAVE_WRONG_KEY_FOR_ME",
                    comment: "Alert body when verifying with {{contact name}}",
                )
                message = String.nonPluralLocalizedStringWithFormat(descriptionFormat, self.contactName)
            case .weHaveWrongKeyForThem:
                let descriptionFormat = OWSLocalizedString(
                    "PRIVACY_VERIFICATION_FAILED_I_HAVE_WRONG_KEY_FOR_THEM",
                    comment: "Alert body when verifying with {{contact name}}",
                )
                message = String.nonPluralLocalizedStringWithFormat(descriptionFormat, self.contactName)
            }
            showFailure(localizedErrorDescription: message)
        }
    }

    static func showVerificationSucceeded(
        from viewController: UIViewController,
        identityKey: IdentityKey,
        recipientAci: Aci,
        contactName: String,
        tag: String,
    ) {
        AssertIsOnMainThread()

        Logger.info("\(tag) Successfully verified safety numbers.")

        let successTitle = NSLocalizedString("SUCCESSFUL_VERIFICATION_TITLE", comment: "")
        let descriptionFormat = NSLocalizedString(
            "SUCCESSFUL_VERIFICATION_DESCRIPTION",
            comment: "Alert body after verifying privacy with {{other user's name}}",
        )
        let successDescription = String.nonPluralLocalizedStringWithFormat(descriptionFormat, contactName)
        let actionSheet = ActionSheetController(title: successTitle, message: successDescription)
        actionSheet.addAction(ActionSheetAction(
            title: NSLocalizedString(
                "FINGERPRINT_SCAN_VERIFY_BUTTON",
                comment: "Button that marks user as verified after a successful fingerprint scan.",
            ),
            style: .default,
            handler: { _ in
                DependenciesBridge.shared.db.write { tx in
                    let identityManager = DependenciesBridge.shared.identityManager
                    identityManager.saveIdentityKey(
                        identityKey,
                        for: recipientAci,
                        shouldUpdateStorageService: true,
                        tx: tx,
                    )
                    _ = identityManager.setVerificationState(
                        .verified,
                        of: identityKey.publicKey.keyBytes,
                        for: SignalServiceAddress(recipientAci),
                        isUserInitiatedChange: true,
                        tx: tx,
                    )
                }
                if let navigationController = viewController.navigationController {
                    navigationController.popViewController(animated: true)
                } else {
                    viewController.dismiss(animated: true)
                }
            },
        ))
        actionSheet.addAction(ActionSheetAction(
            title: CommonStrings.dismissButton,
            style: .cancel,
            handler: { _ in
                if let navigationController = viewController.navigationController {
                    navigationController.popViewController(animated: true)
                } else {
                    viewController.dismiss(animated: true)
                }
            },
        ))

        viewController.presentActionSheet(actionSheet)
    }

    static func showVerificationFailed(
        from viewController: UIViewController,
        isUserError: Bool,
        localizedErrorDescription: String,
        retry: (() -> Void)? = nil,
        cancel: (() -> Void)? = nil,
        tag: String,
    ) {
        Logger.info("\(tag) Failed to verify safety numbers.")

        // We don't want to show a big scary "VERIFICATION FAILED" when it's just user error.
        let actionSheet = ActionSheetController(
            title: isUserError ? nil : NSLocalizedString("FAILED_VERIFICATION_TITLE", comment: "alert title"),
            message: localizedErrorDescription,
        )

        if let retry {
            actionSheet.addAction(ActionSheetAction(
                title: CommonStrings.retryButton,
                style: .default,
                handler: { _ in
                    retry()
                },
            ))
        }

        actionSheet.addAction(ActionSheetAction(
            title: CommonStrings.cancelButton,
            style: .cancel,
            handler: { _ in
                cancel?()
            },
        ))

        viewController.presentActionSheet(actionSheet)

        Logger.warn("\(tag) Identity verification failed")
    }
}

extension FingerprintScanViewController: QRCodeScanDelegate {
    func qrCodeScanViewScanned(
        qrCodeData: Data?,
        qrCodeString: String?,
    ) -> QRCodeScanOutcome {

        guard let qrCodeData else {
            // Only accept QR codes with a valid data (not string) payload.
            return .continueScanning
        }

        verifyCombinedFingerprintData(qrCodeData)

        // Stop scanning even if verification failed.
        return .stopScanning
    }

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

        navigationController?.popViewController(animated: true)
    }
}
