import Foundation
import UIKit
import Display
import AsyncDisplayKit
import SwiftSignalKit
import Postbox
import TelegramCore
import SyncCore
import TelegramPresentationData
import AccountContext
import LocalAuth
import AppBundle
import PasscodeInputFieldNode
import MonotonicTime

private let titleFont = Font.regular(20.0)
private let subtitleFont = Font.regular(15.0)
private let buttonFont = Font.regular(17.0)

final class PasscodeEntryControllerNode: ASDisplayNode {
    private let accountManager: AccountManager
    private var theme: PresentationTheme
    private var strings: PresentationStrings
    private var wallpaper: TelegramWallpaper
    private let passcodeType: PasscodeEntryFieldType
    private let biometricsType: LocalAuthBiometricAuthentication?
    private let arguments: PasscodeEntryControllerPresentationArguments
    private var background: PasscodeBackground?
    
    private let statusBar: StatusBar
    
    private let modalPresentation: Bool
    
    private let backgroundNode: ASImageNode
    private let iconNode: PasscodeLockIconNode
    private let titleNode: PasscodeEntryLabelNode
    private let inputFieldNode: PasscodeInputFieldNode
    private let subtitleNode: PasscodeEntryLabelNode
    private let keyboardNode: PasscodeEntryKeyboardNode
    private let cancelButtonNode: HighlightableButtonNode
    private let deleteButtonNode: HighlightableButtonNode
    private let biometricButtonNode: HighlightableButtonNode
    private let effectView: UIVisualEffectView
    
    private var invalidAttempts: AccessChallengeAttempts?
    private var timer: SwiftSignalKit.Timer?
    
    private let hapticFeedback = HapticFeedback()
    
    private var validLayout: ContainerViewLayout?
    
    var checkPasscode: ((String) -> Void)?
    var requestBiometrics: (() -> Void)?
    
    init(accountManager: AccountManager, theme: PresentationTheme, strings: PresentationStrings, wallpaper: TelegramWallpaper, passcodeType: PasscodeEntryFieldType, biometricsType: LocalAuthBiometricAuthentication?, arguments: PasscodeEntryControllerPresentationArguments, statusBar: StatusBar, modalPresentation: Bool) {
        self.accountManager = accountManager
        self.theme = theme
        self.strings = strings
        self.wallpaper = wallpaper
        self.passcodeType = passcodeType
        self.biometricsType = biometricsType
        self.arguments = arguments
        self.statusBar = statusBar
        self.modalPresentation = modalPresentation
        
        self.backgroundNode = ASImageNode()
        self.backgroundNode.contentMode = .scaleToFill

        self.iconNode = PasscodeLockIconNode()
        self.titleNode = PasscodeEntryLabelNode()
        self.inputFieldNode = PasscodeInputFieldNode(color: .white, accentColor: .white, fieldType: passcodeType, keyboardAppearance: .dark, useCustomNumpad: true)
        self.subtitleNode = PasscodeEntryLabelNode()
        self.keyboardNode = PasscodeEntryKeyboardNode()
        self.cancelButtonNode = HighlightableButtonNode()
        self.deleteButtonNode = HighlightableButtonNode()
        self.biometricButtonNode = HighlightableButtonNode()
        self.effectView = UIVisualEffectView(effect: nil)
            
        super.init()
        
        self.setViewBlock({
            return UITracingLayerView()
        })
        
        self.backgroundColor = .clear
        self.iconNode.unlockedColor = theme.rootController.navigationBar.primaryTextColor
        
        self.keyboardNode.charactedEntered = { [weak self] character in
            self?.inputFieldNode.append(character)
        }
        self.inputFieldNode.complete = { [weak self] passcode in
            guard let strongSelf = self else {
                return
            }
            if strongSelf.shouldWaitBeforeNextAttempt() {
                strongSelf.animateError()
            } else {
                strongSelf.checkPasscode?(passcode)
            }
        }
        
        self.cancelButtonNode.setTitle(strings.Common_Cancel, with: buttonFont, with: .white, for: .normal)
        self.deleteButtonNode.setTitle(strings.Common_Delete, with: buttonFont, with: .white, for: .normal)
    
        if let biometricsType = self.biometricsType {
            switch biometricsType {
                case .touchId:
                    self.biometricButtonNode.setImage(generateTintedImage(image: UIImage(bundleImageName: "Settings/PasscodeTouchId"), color: .white), for: .normal)
                case .faceId:
                    self.biometricButtonNode.setImage(generateTintedImage(image: UIImage(bundleImageName: "Settings/PasscodeFaceId"), color: .white), for: .normal)
            }
        }
        
        self.addSubnode(self.backgroundNode)
        self.addSubnode(self.iconNode)
        self.addSubnode(self.titleNode)
        self.addSubnode(self.inputFieldNode)
        self.addSubnode(self.subtitleNode)
        self.addSubnode(self.keyboardNode)
        self.addSubnode(self.deleteButtonNode)
        self.addSubnode(self.biometricButtonNode)
        
        if self.arguments.cancel != nil {
            self.addSubnode(self.cancelButtonNode)
        }
    }
    
    override func didLoad() {
        super.didLoad()
        
        self.view.insertSubview(self.effectView, at: 0)
        
        if self.arguments.cancel != nil {
            self.cancelButtonNode.addTarget(self, action: #selector(self.cancelPressed), forControlEvents: .touchUpInside)
        }
        self.deleteButtonNode.addTarget(self, action: #selector(self.deletePressed), forControlEvents: .touchUpInside)
        self.biometricButtonNode.addTarget(self, action: #selector(self.biometricsPressed), forControlEvents: .touchUpInside)
    }
    
    @objc private func cancelPressed() {
        self.animateOut(down: true)
        self.arguments.cancel?()
    }
    
    @objc private func deletePressed() {
        self.hapticFeedback.tap()
        self.inputFieldNode.delete()
    }
    
    @objc private func biometricsPressed() {
        self.requestBiometrics?()
    }
    
    func activateInput() {
        self.inputFieldNode.activateInput()
    }
    
    func updatePresentationData(_ presentationData: PresentationData) {
        self.theme = presentationData.theme
        self.strings = presentationData.strings
        self.wallpaper = presentationData.chatWallpaper
        
        self.deleteButtonNode.setTitle(self.strings.Common_Delete, with: buttonFont, with: .white, for: .normal)
        if let validLayout = self.validLayout {
            self.containerLayoutUpdated(validLayout, navigationBarHeight: 0.0, transition: .immediate)
        }
    }
    
    func updateBackground() {
        guard let validLayout = self.validLayout else {
            return
        }
        
        var size = validLayout.size
        if let background = self.background, background.size == size {
            return
        }
        
        switch self.wallpaper {
            case .image, .file:
                if let image = chatControllerBackgroundImage(theme: self.theme, wallpaper: self.wallpaper, mediaBox: self.accountManager.mediaBox, composed: false, knockoutMode: false) {
                    self.background = ImageBasedPasscodeBackground(image: image, size: size)
                } else {
                    self.background = GradientPasscodeBackground(size: size, backgroundColors: self.theme.passcode.backgroundColors.colors, buttonColor: self.theme.passcode.buttonColor)
                }
            default:
                self.background = GradientPasscodeBackground(size: size, backgroundColors: self.theme.passcode.backgroundColors.colors, buttonColor: self.theme.passcode.buttonColor)
        }
        
        if let background = self.background {
            self.backgroundNode.image = background.backgroundImage
            self.keyboardNode.updateBackground(background)
            self.inputFieldNode.updateBackground(background.foregroundImage, size: background.size)
        }
    }
    
    private let waitInterval: Int32 = 60
    private func shouldWaitBeforeNextAttempt() -> Bool {
        if let attempts = self.invalidAttempts {
            if attempts.count >= 6 {
                var bootTimestamp: Int32 = 0
                let uptime = getDeviceUptimeSeconds(&bootTimestamp)
                
                if attempts.bootTimestamp != bootTimestamp {
                    return true
                }
                
                if uptime - attempts.uptime < waitInterval {
                    return true
                } else {
                    return false
                }
            } else {
                return false
            }
        } else {
            return false
        }
    }
    
    func updateInvalidAttempts(_ attempts: AccessChallengeAttempts?, animated: Bool = false) {
        self.invalidAttempts = attempts
        if let attempts = attempts {
            var text = NSAttributedString(string: "")
            if attempts.count >= 6 && self.shouldWaitBeforeNextAttempt() {
                text = NSAttributedString(string: self.strings.PasscodeSettings_TryAgainIn1Minute, font: subtitleFont, textColor: .white)
                
                self.timer?.invalidate()
                let timer = SwiftSignalKit.Timer(timeout: 1.0, repeat: true, completion: { [weak self] in
                    if let strongSelf = self {
                        if !strongSelf.shouldWaitBeforeNextAttempt() {
                            strongSelf.updateInvalidAttempts(strongSelf.invalidAttempts, animated: true)
                            strongSelf.timer?.invalidate()
                            strongSelf.timer = nil
                        }
                    }
                }, queue: Queue.mainQueue())
                self.timer = timer
                timer.start()
            }
            self.subtitleNode.setAttributedText(text, animation: animated ? .crossFade : .none, completion: {})
        } else {
            self.subtitleNode.setAttributedText(NSAttributedString(string: ""), animation: animated ? .crossFade : .none, completion: {})
        }
    }
    
    func hideBiometrics() {
        self.biometricButtonNode.layer.animateScale(from: 1.0, to: 0.00001, duration: 0.25, delay: 0.0, timingFunction: CAMediaTimingFunctionName.easeOut.rawValue, completion: { [weak self] _ in
            self?.biometricButtonNode.isHidden = true
        })
        self.animateError()
    }
    
    func initialAppearance(fadeIn: Bool = false) {
        if fadeIn {
            let effect = self.theme.overallDarkAppearance ? UIBlurEffect(style: .dark) : UIBlurEffect(style: .light)
            UIView.animate(withDuration: 0.3, animations: {
                if #available(iOS 9.0, *) {
                    self.effectView.effect = effect
                } else {
                    self.effectView.alpha = 1.0
                }
            })
            self.backgroundNode.layer.animateAlpha(from: 0.0, to: 1.0, duration: 0.3)
        }
        self.titleNode.setAttributedText(NSAttributedString(string: self.strings.EnterPasscode_EnterPasscode, font: titleFont, textColor: .white), animation: .none)
    }
    
    func animateIn(iconFrame: CGRect, completion: @escaping () -> Void = {}) {
        let effect = self.theme.overallDarkAppearance ? UIBlurEffect(style: .dark) : UIBlurEffect(style: .light)
        UIView.animate(withDuration: 0.3, animations: {
            if #available(iOS 9.0, *) {
                self.effectView.effect = effect
            } else {
                self.effectView.alpha = 1.0
            }
        })
        self.backgroundNode.layer.animateAlpha(from: 0.0, to: 1.0, duration: 0.3)
        if !iconFrame.isEmpty {
            self.iconNode.animateIn(fromScale: 0.416)
            self.iconNode.layer.animatePosition(from: iconFrame.center.offsetBy(dx: 6.0, dy: 6.0), to: self.iconNode.layer.position, duration: 0.45)
        }
        
        self.statusBar.layer.removeAnimation(forKey: "opacity")
        self.statusBar.layer.animateAlpha(from: 0.0, to: 1.0, duration: 0.3)
        
        self.subtitleNode.isHidden = true
        self.inputFieldNode.isHidden = true
        self.keyboardNode.isHidden = true
        self.cancelButtonNode.isHidden = true
        self.deleteButtonNode.isHidden = true
        self.biometricButtonNode.isHidden = true
        
        self.titleNode.setAttributedText(NSAttributedString(string: self.strings.Passcode_AppLockedAlert.replacingOccurrences(of: "\n", with: " "), font: titleFont, textColor: .white), animation: .slideIn, completion: {
            self.subtitleNode.isHidden = false
            self.inputFieldNode.isHidden = false
            self.keyboardNode.isHidden = false
            self.cancelButtonNode.isHidden = false
            self.deleteButtonNode.isHidden = false
            self.biometricButtonNode.isHidden = false
            
            self.subtitleNode.layer.animateAlpha(from: 0.0, to: 1.0, duration: 0.25)
            
            self.inputFieldNode.animateIn()
            self.keyboardNode.animateIn()
            var biometricDelay = 0.3
            if case .alphanumeric = self.passcodeType {
                biometricDelay = 0.0
            } else {
                self.cancelButtonNode.layer.animateScale(from: 0.0001, to: 1.0, duration: 0.25, delay: 0.15, timingFunction: CAMediaTimingFunctionName.easeOut.rawValue)
                self.deleteButtonNode.layer.animateScale(from: 0.0001, to: 1.0, duration: 0.25, delay: 0.15, timingFunction: CAMediaTimingFunctionName.easeOut.rawValue)
            }
            self.biometricButtonNode.layer.animateScale(from: 0.0001, to: 1.0, duration: 0.25, delay: biometricDelay, timingFunction: CAMediaTimingFunctionName.easeOut.rawValue)
            
            Queue.mainQueue().after(1.5, {
                self.titleNode.setAttributedText(NSAttributedString(string: self.strings.EnterPasscode_EnterPasscode, font: titleFont, textColor: .white), animation: .crossFade)
            })
            
            completion()
        })
    }
    
    func animateOut(down: Bool = false, completion: @escaping () -> Void = {}) {
        self.statusBar.layer.animateAlpha(from: 1.0, to: 0.0, duration: 0.3, removeOnCompletion: false)
        self.layer.animatePosition(from: CGPoint(), to: CGPoint(x: 0.0, y: down ? self.bounds.size.height : -self.bounds.size.height), duration: 0.2, removeOnCompletion: false, additive: true, completion: { _ in
            completion()
        })
    }
    
    func animateSuccess() {
        self.iconNode.animateUnlock()
        self.inputFieldNode.animateSuccess()
    }
    
    func animateError() {
        self.inputFieldNode.reset()
        self.inputFieldNode.layer.addShakeAnimation(amplitude: -30.0, duration: 0.5, count: 6, decay: true)
        self.iconNode.layer.addShakeAnimation(amplitude: -8.0, duration: 0.5, count: 6, decay: true)
        
        self.hapticFeedback.error()
    }
    
    func containerLayoutUpdated(_ layout: ContainerViewLayout, navigationBarHeight: CGFloat, transition: ContainedViewLayoutTransition) {
        self.validLayout = layout
        
        self.updateBackground()
            
        let bounds = CGRect(origin: CGPoint(), size: layout.size)
        transition.updateFrame(node: self.backgroundNode, frame: bounds)
        transition.updateFrame(view: self.effectView, frame: bounds)
        
        switch self.passcodeType {
            case .digits6, .digits4:
                self.keyboardNode.alpha = 1.0
                self.deleteButtonNode.alpha = 1.0
            case .alphanumeric:
                self.keyboardNode.alpha = 0.0
                self.deleteButtonNode.alpha = 0.0
        }
        
        let isLandscape = layout.orientation == .landscape && layout.deviceMetrics.type != .tablet
        let keyboardHidden = self.keyboardNode.alpha == 0.0
        
        let layoutSize: CGSize
        if isLandscape {
            if keyboardHidden {
                layoutSize = CGSize(width: layout.size.width - layout.safeInsets.left - layout.safeInsets.right, height: layout.size.height)
            } else {
                layoutSize = CGSize(width: layout.size.width / 2.0, height: layout.size.height)
            }
        } else {
            layoutSize = layout.size
        }
        
        if layout.size.width == 320.0 || (isLandscape && keyboardHidden) {
            self.iconNode.alpha = 0.0
        }
                
        let passcodeLayout = PasscodeLayout(layout: layout, modalPresentation: self.modalPresentation)
        let inputFieldOffset: CGFloat
        if isLandscape {
            let bottomInset = layout.inputHeight ?? 0.0
            if !keyboardHidden || bottomInset == 0.0 {
                inputFieldOffset = floor(layoutSize.height / 2.0 + 12.0)
            } else {
                inputFieldOffset = floor(layoutSize.height - bottomInset) / 2.0 - 40.0
            }
        } else {
            inputFieldOffset = passcodeLayout.inputFieldOffset
        }
        
        let inputFieldFrame = self.inputFieldNode.updateLayout(size: layoutSize, topOffset: inputFieldOffset, transition: transition)
        transition.updateFrame(node: self.inputFieldNode, frame: CGRect(origin: CGPoint(x: layout.safeInsets.left, y: 0.0), size: layoutSize))
                
        let titleFrame: CGRect
        if isLandscape {
            let titleSize = self.titleNode.updateLayout(size: CGSize(width: layoutSize.width, height: layout.size.height), transition: transition)
            titleFrame = CGRect(origin: CGPoint(x: layout.safeInsets.left, y: inputFieldFrame.minY - titleSize.height - 16.0), size: titleSize)
        } else {
            let titleSize = self.titleNode.updateLayout(size: layout.size, transition: transition)
            titleFrame = CGRect(origin: CGPoint(x: 0.0, y: passcodeLayout.titleOffset), size: titleSize)
        }
        transition.updateFrame(node: self.titleNode, frame: titleFrame)
        
        let iconSize = CGSize(width: 35.0, height: 37.0)
        let iconFrame: CGRect
        if isLandscape {
            iconFrame = CGRect(origin: CGPoint(x: layout.safeInsets.left + floor((layoutSize.width - iconSize.width) / 2.0) + 6.0, y: titleFrame.minY - iconSize.height - 14.0), size: iconSize)
        } else {
            iconFrame = CGRect(origin: CGPoint(x: floor((layoutSize.width - iconSize.width) / 2.0) + 6.0, y: layout.insets(options: .statusBar).top + 15.0), size: iconSize)
        }
        transition.updateFrame(node: self.iconNode, frame: iconFrame)
        
        var subtitleOffset = passcodeLayout.subtitleOffset
        if case .alphanumeric = self.passcodeType {
            subtitleOffset = 16.0
        }
        let subtitleSize = self.subtitleNode.updateLayout(size: layoutSize, transition: transition)
        transition.updateFrame(node: self.subtitleNode, frame: CGRect(origin: CGPoint(x: layout.safeInsets.left, y: inputFieldFrame.maxY + subtitleOffset), size: subtitleSize))
        
        let (keyboardFrame, keyboardButtonSize) = self.keyboardNode.updateLayout(layout: passcodeLayout, transition: transition)
        transition.updateFrame(node: self.keyboardNode, frame: CGRect(origin: CGPoint(x: 0.0, y: 0.0), size: layout.size))
                
        let bottomInset = layout.inputHeight ?? 0.0
        
        let cancelSize = self.cancelButtonNode.measure(layout.size)
        var bottomButtonY = layout.size.height - layout.intrinsicInsets.bottom - cancelSize.height - passcodeLayout.keyboard.deleteOffset
        var cancelX = floor(keyboardFrame.minX + keyboardButtonSize.width / 2.0 - cancelSize.width / 2.0)
        var cancelY = bottomButtonY
        if bottomInset > 0 && keyboardHidden {
            cancelX = floor((layout.size.width - cancelSize.width) / 2.0)
            cancelY = layout.size.height - bottomInset - cancelSize.height - 15.0 - layout.intrinsicInsets.bottom
        } else if isLandscape {
            bottomButtonY = keyboardFrame.maxY - keyboardButtonSize.height + floor((keyboardButtonSize.height - cancelSize.height) / 2.0)
            cancelY = bottomButtonY
        }
        
        transition.updateFrame(node: self.cancelButtonNode, frame: CGRect(origin: CGPoint(x: cancelX, y: cancelY), size: cancelSize))
        
        let deleteSize = self.deleteButtonNode.measure(layout.size)
        transition.updateFrame(node: self.deleteButtonNode, frame: CGRect(origin: CGPoint(x: floor(keyboardFrame.maxX - keyboardButtonSize.width / 2.0 - deleteSize.width / 2.0), y: bottomButtonY), size: deleteSize))
        
        if let biometricIcon = self.biometricButtonNode.image(for: .normal) {
            var biometricX = layout.safeInsets.left + floor((layoutSize.width - biometricIcon.size.width) / 2.0)
            var biometricY: CGFloat = 0.0
            if isLandscape {
                if bottomInset > 0 && keyboardHidden {
                    biometricX = cancelX + cancelSize.width + 64.0
                }
                biometricY = cancelY + floor((cancelSize.height - biometricIcon.size.height) / 2.0)
            } else {
                if bottomInset > 0 && keyboardHidden {
                    biometricY = inputFieldFrame.maxY + floor((layout.size.height - bottomInset - inputFieldFrame.maxY - biometricIcon.size.height) / 2.0)
                } else {
                    biometricY = keyboardFrame.maxY + passcodeLayout.keyboard.biometricsOffset
                }
            }
            transition.updateFrame(node: self.biometricButtonNode, frame: CGRect(origin: CGPoint(x: biometricX, y: biometricY), size: biometricIcon.size))
        }
    }
}