import UIKit
import Shared
import Combine

public final class InputField: UIView {
    public enum Style {
        case phone
        case regular
    }

    public enum LeftView {
        case image(UIImage)
    }

    public enum RightView {
        case image(UIImage)
        case toggleSecureEntry
    }

    public enum ValidationStatus: Equatable {
        case valid(String?)
        case invalid(String)
        case unknown(String?)
    }

    let title = UILabel()
    let hide = UIButton()
    let clear = UIButton()
    let subtitle = UILabel()

    let outerStack = UIStackView()
    let codeContainer = UIView()
    let code = PhoneCodeField()

    let container = UIView()
    let innerStack = UIStackView()
    let left = UIImageView()
    let field = UITextField()

    let toolbar = UIToolbar()
    let toolbarButton = UIButton()

    var isPhone: Bool = false

    // MARK: Properties

    private var rightView: RightView? = .none {
        didSet { set(rightView: rightView) }
    }

    private var clearable: Bool = false
    private var allowsEmptySpace: Bool = true
    private var cancellables = Set<AnyCancellable>()

    private let codeSubject = PassthroughSubject<Void, Never>()
    private let returnSubject = PassthroughSubject<Void, Never>()
    private let textSubject = PassthroughSubject<String, Never>()

    public var codePublisher: AnyPublisher<Void, Never> { codeSubject.eraseToAnyPublisher() }
    public var textPublisher: AnyPublisher<String, Never> { textSubject.eraseToAnyPublisher() }
    public var returnPublisher: AnyPublisher<Void, Never> { returnSubject.eraseToAnyPublisher() }

    public init() {
        super.init(frame: .zero)
        setup()
    }

    required init?(coder: NSCoder) { nil }

    // MARK: Public

    public func makeFirstResponder() {
        field.becomeFirstResponder()
    }

    public func setup(
        style: Style = .regular,
        title: String? = nil,
        placeholder: String? = nil,
        leftView: LeftView? = nil,
        rightView: RightView? = nil,
        accessibility: String? = nil,
        subtitleAccessibility: String? = nil,
        subtitleColor: UIColor = Asset.neutralWhite.color,
        allowsEmptySpace: Bool = true,
        keyboardType: UIKeyboardType = .default,
        autocapitalization: UITextAutocapitalizationType = .sentences,
        autoCorrect: UITextAutocorrectionType = .no,
        contentType: UITextContentType? = nil,
        returnKeyType: UIReturnKeyType = .done,
        toolbarButtonTitle: String = Localized.Shared.done,
        codeAccessibility: String? = nil,
        clearable: Bool = false
    ) {
        self.title.text = title
        self.set(leftView: leftView)

        self.rightView = rightView
        self.field.attributedPlaceholder = NSAttributedString(
            string: placeholder ?? "",
            attributes: [
                .font: Fonts.Mulish.semiBold.font(size: 14.0),
                .foregroundColor: Asset.neutralDisabled.color
            ])

        if contentType == .telephoneNumber {
            isPhone = true
        } else {
            self.field.textContentType = contentType
        }

        self.field.returnKeyType = returnKeyType
        self.field.keyboardType = keyboardType
        self.subtitle.textColor = subtitleColor
        self.allowsEmptySpace = allowsEmptySpace
        self.field.autocorrectionType = autoCorrect
        self.field.accessibilityIdentifier = accessibility
        self.field.autocapitalizationType = autocapitalization
        self.subtitle.accessibilityIdentifier = subtitleAccessibility
        self.clearable = clearable

        if style == .phone {
            codeContainer.addSubview(code)
            code.accessibilityIdentifier = codeAccessibility
            code.snp.makeConstraints { $0.edges.equalToSuperview() }
            outerStack.insertArrangedSubview(codeContainer, at: 0)

            code.publisher(for: .touchUpInside)
                .sink { [weak codeSubject] in codeSubject?.send() }
                .store(in: &cancellables)

            self.field.keyboardType = .numberPad
            self.allowsEmptySpace = false

            toolbar.barTintColor = Asset.neutralWhite.color
            toolbarButton.setTitle(toolbarButtonTitle, for: .normal)
            toolbarButton.setTitleColor(Asset.brandPrimary.color, for: .normal)
            toolbarButton.titleLabel?.font = Fonts.Mulish.bold.font(size: 17.0)
            toolbar.setShadowImage(.color(Asset.neutralLine.color), forToolbarPosition: .any)
            toolbarButton.addTarget(self, action: #selector(didTapDone), for: .touchUpInside)
            toolbar.items = [UIBarButtonItem(customView: toolbarButton.pinning(at: .right(0)))]

            toolbar.sizeToFit()
            self.field.inputAccessoryView = toolbar
        }
    }

    public func set(prefix: String) {
        code.content.text = prefix
    }

    public func update(content: String) {
        field.text = content
    }

    public func update(placeholder: String) {
        field.placeholder = placeholder
    }

    public func update(status: ValidationStatus) {
        switch status {
        case .unknown(let text):
            set(rightView: nil)
            subtitle.text = text ?? " "
        case .invalid(let text):
            set(rightView: .image(Asset.sharedError.image))
            subtitle.text = text
        case .valid(let text):
            set(rightView: .image(Asset.sharedSuccess.image))
            subtitle.text = text ?? " "
        }
    }

    // MARK: Private

    private func set(leftView: LeftView?) {
        switch leftView {
        case .image(let image):
            left.image = image
            left.tintColor = Asset.neutralDisabled.color
        case .none:
            innerStack.removeArrangedSubview(left)
        }
    }

    public func set(rightView: RightView?) {
        switch rightView {
        case.image(let image):
            field.rightView = UIImageView(image: image)
        case .toggleSecureEntry:
            field.rightView = hide
            field.isSecureTextEntry = true
            hide.setImage(hideButtonImage(isSecureEntry: field.isSecureTextEntry), for: .normal)
        case .none:
            field.rightView = nil
        }
    }

    private func hideButtonImage(isSecureEntry: Bool) -> UIImage? {
        let openImage = Asset.eyeOpen.image.withTintColor(Asset.neutralWeak.color)
        let closedImage = Asset.eyeClosed.image.withTintColor(Asset.neutralWeak.color)
        return isSecureEntry ? closedImage : openImage
    }

    private func setup() {
        subtitle.textAlignment = .right
        subtitle.numberOfLines = 0
        container.layer.cornerRadius = 4
        container.backgroundColor = Asset.neutralSecondary.color

        codeContainer.layer.cornerRadius = 4
        codeContainer.backgroundColor = Asset.neutralSecondary.color

        title.textColor = Asset.neutralWeak.color
        field.textColor = Asset.neutralActive.color
        subtitle.textColor = Asset.neutralWhite.color

        title.font = Fonts.Mulish.regular.font(size: 12.0)
        field.font = Fonts.Mulish.semiBold.font(size: 14.0)
        subtitle.font = Fonts.Mulish.regular.font(size: 12.0)

        clear.setImage(Asset.sharedCross.image, for: .normal)

        field.textPublisher
            .sink { [weak textSubject] in textSubject?.send($0) }
            .store(in: &cancellables)

        hide.publisher(for: .touchUpInside)
            .sink { [unowned self] _ in
                field.isSecureTextEntry.toggle()
                hide.setImage(hideButtonImage(isSecureEntry: field.isSecureTextEntry), for: .normal)
            }.store(in: &cancellables)

        clear.publisher(for: .touchUpInside)
            .sink { [unowned self] in
                field.text = ""
                textSubject.send("")
                field.resignFirstResponder()
            }.store(in: &cancellables)

        field.delegate = self
        field.rightViewMode = .always

        left.contentMode = .center
        left.setContentHuggingPriority(.required, for: .horizontal)

        innerStack.spacing = 12
        innerStack.addArrangedSubview(left)
        innerStack.addArrangedSubview(field)

        outerStack.spacing = 8
        container.addSubview(innerStack)
        outerStack.addArrangedSubview(container)

        addSubview(title)
        addSubview(outerStack)
        addSubview(subtitle)

        setupConstraints()
    }

    private func setupConstraints() {
        title.snp.makeConstraints { make in
            make.top.equalToSuperview()
            make.left.equalToSuperview().offset(8)
        }

        outerStack.snp.makeConstraints { make in
            make.top.equalTo(title.snp.bottom).offset(10)
            make.left.equalToSuperview()
            make.right.equalToSuperview()
            make.height.equalTo(36)
        }

        innerStack.snp.makeConstraints { make in
            make.top.equalToSuperview()
            make.left.equalToSuperview().offset(11)
            make.right.equalToSuperview().offset(-11)
            make.bottom.equalToSuperview()
        }

        subtitle.snp.makeConstraints { make in
            make.top.equalTo(outerStack.snp.bottom).offset(8)
            make.right.equalToSuperview()
            make.bottom.equalToSuperview()
            make.left.greaterThanOrEqualToSuperview()
        }
    }

    @objc private func didTapDone() {
        returnSubject.send()
    }

    public func textFieldDidBeginEditing(_ textField: UITextField) {
        if clearable {
            field.rightView = clear
        }
    }

    public func textFieldDidEndEditing(_ textField: UITextField) {
        if clearable {
            set(rightView: rightView)
        }
    }

    public func textFieldShouldReturn(_ textField: UITextField) -> Bool {
        returnSubject.send()
        return true
    }

    public func textField(
        _ textField: UITextField,
        shouldChangeCharactersIn range: NSRange,
        replacementString string: String
    ) -> Bool {
        if isPhone {
            if string.count > 1 {
                textField.text = string.replaceCharactersFromSet(characterSet: .decimalDigits.inverted)
                textSubject.send(textField.text ?? "")
                return false
            } else {
                return string.rangeOfCharacter(from: .decimalDigits) != nil || string == ""
            }
        }

        if !allowsEmptySpace {
            if string.count > 1 {
                if textField.textContentType == .emailAddress && [".us", ".net", ".edu", ".org", ".com"].contains(string) {
                    textSubject.send(textField.text ?? "")
                    return true
                }

                textField.text = string.replaceCharactersFromSet(characterSet: .whitespacesAndNewlines)
                textSubject.send(textField.text ?? "")
                return false
            } else {
                return string != " "
            }
        }

        return true
    }
}

extension InputField: UITextFieldDelegate {}

private extension String {
    func replaceCharactersFromSet(characterSet: CharacterSet, replacementString: String = "") -> String {
        return components(separatedBy: characterSet).joined(separator: replacementString)
    }
}