import AVFoundation
import UIKit
import WebKit

public struct UmbrellaXVerifyEvidence {
    public let id: String
    public let status: String
    public let value: String
}

public struct UmbrellaXVerifyResult {
    public let status: String
    public let verificationId: String?
    public let coreVerificationId: String?
    public let coreStatus: String?
    public let coreDecision: String?
    public let riskScore: Double?
    public let reasons: [String]
    public let faceSimilarity: Double?
    public let evidence: [UmbrellaXVerifyEvidence]
    public let raw: [String: Any]
}

public struct SNSInitConfig {
    public let email: String?
    public let phone: String?

    public init(email: String? = nil, phone: String? = nil) {
        self.email = email
        self.phone = phone
    }
}

public final class UmbrellaXVerifySDK: NSObject {
    public typealias TokenExpirationHandler = (@escaping (String) -> Void) -> Void

    public var onEvent: (([String: Any]) -> Void)?
    public var onStatusDidChange: ((String) -> Void)?
    public var onComplete: ((UmbrellaXVerifyResult) -> Void)?
    public var onError: ((String) -> Void)?

    private var accessToken: String
    private let baseURL: URL
    private var locale: Locale?
    private var initConfig: SNSInitConfig?
    private var autoCloseOnApproveSeconds: TimeInterval?
    private var tokenExpirationHandler: TokenExpirationHandler?
    private weak var presentingViewController: UIViewController?
    private var navigationController: UINavigationController?
    private var webView: WKWebView?

    public var isReady: Bool {
        !accessToken.trimmingCharacters(in: .whitespacesAndNewlines).isEmpty || tokenExpirationHandler != nil
    }

    public var verboseStatus: String {
        isReady ? "ready" : "accessToken_required"
    }

    public var mainVC: UIViewController {
        let navigationController = makeNavigationController()
        self.navigationController = navigationController
        DispatchQueue.main.async { [weak self] in
            self?.loadFlow()
        }
        return navigationController
    }

    public init(
        accessToken: String,
        baseURL: URL = URL(string: "https://verify.umbrellax.io")!,
        locale: Locale? = nil,
        conf: SNSInitConfig? = nil
    ) {
        self.accessToken = accessToken
        self.baseURL = baseURL
        self.locale = locale
        self.initConfig = conf
        super.init()
    }

    @discardableResult
    public func withAccessToken(_ accessToken: String) -> Self {
        self.accessToken = accessToken
        return self
    }

    @discardableResult
    public func withLocale(_ locale: Locale) -> Self {
        self.locale = locale
        return self
    }

    @discardableResult
    public func withConf(_ conf: SNSInitConfig) -> Self {
        self.initConfig = conf
        return self
    }

    @discardableResult
    public func withAutoCloseOnApprove(_ seconds: TimeInterval) -> Self {
        self.autoCloseOnApproveSeconds = max(0, seconds)
        return self
    }

    @discardableResult
    public func tokenExpirationHandler(_ handler: @escaping TokenExpirationHandler) -> Self {
        self.tokenExpirationHandler = handler
        return self
    }

    public func launch(from viewController: UIViewController) {
        present(from: viewController)
    }

    public func present(from viewController: UIViewController, animated: Bool = true) {
        presentingViewController = viewController
        requestCameraAndMicrophone { [weak self] in
            self?.presentWebFlow(animated: animated)
        }
    }

    public func dismiss(animated: Bool = true) {
        webView?.configuration.userContentController.removeScriptMessageHandler(forName: "faces")
        navigationController?.dismiss(animated: animated)
        navigationController = nil
        webView = nil
    }

    private func requestCameraAndMicrophone(_ completion: @escaping () -> Void) {
        let group = DispatchGroup()
        group.enter()
        AVCaptureDevice.requestAccess(for: .video) { _ in group.leave() }
        group.enter()
        AVCaptureDevice.requestAccess(for: .audio) { _ in group.leave() }
        group.notify(queue: .main, execute: completion)
    }

    private func presentWebFlow(animated: Bool) {
        guard let presentingViewController else { return }

        let navigationController = makeNavigationController()
        self.navigationController = navigationController

        presentingViewController.present(navigationController, animated: animated) { [weak self] in
            self?.loadFlow()
        }
    }

    private func makeNavigationController() -> UINavigationController {
        let configuration = WKWebViewConfiguration()
        configuration.allowsInlineMediaPlayback = true
        configuration.mediaTypesRequiringUserActionForPlayback = []
        configuration.userContentController.add(self, name: "faces")

        let webView = WKWebView(frame: .zero, configuration: configuration)
        webView.navigationDelegate = self
        webView.translatesAutoresizingMaskIntoConstraints = false
        self.webView = webView

        let controller = UIViewController()
        controller.view.backgroundColor = .systemBackground
        controller.view.addSubview(webView)
        NSLayoutConstraint.activate([
            webView.leadingAnchor.constraint(equalTo: controller.view.leadingAnchor),
            webView.trailingAnchor.constraint(equalTo: controller.view.trailingAnchor),
            webView.topAnchor.constraint(equalTo: controller.view.topAnchor),
            webView.bottomAnchor.constraint(equalTo: controller.view.bottomAnchor)
        ])
        controller.navigationItem.title = "Verify"
        controller.navigationItem.leftBarButtonItem = UIBarButtonItem(
            barButtonSystemItem: .close,
            target: self,
            action: #selector(closeTapped)
        )

        let navigationController = UINavigationController(rootViewController: controller)
        navigationController.modalPresentationStyle = .fullScreen
        return navigationController
    }

    private func loadFlow() {
        guard var components = URLComponents(url: baseURL, resolvingAgainstBaseURL: false) else {
            onError?("invalid_base_url")
            return
        }
        components.path = "/sdk/sumsub"
        components.queryItems = [
            URLQueryItem(name: "token", value: accessToken),
            URLQueryItem(name: "locale", value: locale?.identifier),
            URLQueryItem(name: "email", value: initConfig?.email),
            URLQueryItem(name: "phone", value: initConfig?.phone)
        ].compactMap { $0.value == nil ? nil : $0 }

        guard let url = components.url else {
            onError?("invalid_flow_url")
            return
        }
        webView?.load(URLRequest(url: url))
    }

    @objc private func closeTapped() {
        dismiss()
    }

    private func parseDouble(_ value: Any?) -> Double? {
        if let double = value as? Double {
            return double
        }
        if let number = value as? NSNumber {
            return number.doubleValue
        }
        return nil
    }

    private func parseStringArray(_ value: Any?) -> [String] {
        if let strings = value as? [String] {
            return strings
        }
        if let values = value as? [Any] {
            return values.compactMap { $0 as? String }
        }
        return []
    }

    private func parseEvidenceArray(_ value: Any?) -> [UmbrellaXVerifyEvidence] {
        guard let values = value as? [Any] else { return [] }
        return values.compactMap { item in
            guard let object = item as? [String: Any] else { return nil }
            return UmbrellaXVerifyEvidence(
                id: object["id"] as? String ?? "",
                status: object["status"] as? String ?? "review",
                value: object["value"] as? String ?? ""
            )
        }
    }

    private func handle(_ payload: [String: Any]) {
        onEvent?(payload)

        guard let type = payload["type"] as? String else { return }
        switch type {
        case "status":
            if let status = payload["status"] as? String {
                onStatusDidChange?(status)
            }
        case "complete":
            let result = UmbrellaXVerifyResult(
                status: payload["status"] as? String ?? "manual_review",
                verificationId: payload["verificationId"] as? String,
                coreVerificationId: payload["coreVerificationId"] as? String,
                coreStatus: payload["coreStatus"] as? String,
                coreDecision: payload["coreDecision"] as? String,
                riskScore: parseDouble(payload["riskScore"]),
                reasons: parseStringArray(payload["reasons"]),
                faceSimilarity: parseDouble(payload["faceSimilarity"]),
                evidence: parseEvidenceArray(payload["evidence"]),
                raw: payload
            )
            onComplete?(result)
            if result.status == "accepted", let delay = autoCloseOnApproveSeconds {
                DispatchQueue.main.asyncAfter(deadline: .now() + delay) { [weak self] in
                    self?.dismiss()
                }
            }
        case "error":
            onError?(payload["error"] as? String ?? "verify_error")
        case "tokenExpired":
            tokenExpirationHandler? { [weak self] freshToken in
                self?.accessToken = freshToken
                self?.loadFlow()
            }
        default:
            break
        }
    }
}

extension UmbrellaXVerifySDK: WKScriptMessageHandler {
    public func userContentController(
        _ userContentController: WKUserContentController,
        didReceive message: WKScriptMessage
    ) {
        if let payload = message.body as? [String: Any] {
            handle(payload)
        }
    }
}

extension UmbrellaXVerifySDK: WKNavigationDelegate {
    public func webView(
        _ webView: WKWebView,
        decidePolicyFor navigationAction: WKNavigationAction,
        decisionHandler: @escaping (WKNavigationActionPolicy) -> Void
    ) {
        decisionHandler(.allow)
    }
}

public typealias SNSMobileSDK = UmbrellaXVerifySDK
