import Foundation
import UIKit
import WebKit
import React
import FiuuXDKSwift

@MainActor
@objc(FiuuPaymentManager)
final class FiuuPaymentManager: NSObject {
  private var paymentController: FiuuXDKController?
  private var navController: UINavigationController?
  private var successCallback: RCTResponseSenderBlock?
  private var errorCallback: RCTResponseSenderBlock?
  private var isTaskCompleted = false
  private var isPaymentInstructionPresent = false
  private var isApplePayFlow = false
  private var didDeliverResult = false
  private var applePayWatchdogTask: Task<Void, Never>?

  private static let applePayWatchdogNanoseconds: UInt64 = 180_000_000_000

  @objc
  nonisolated static func requiresMainQueueSetup() -> Bool {
    true
  }

  @objc
  func setPaymentDetails(
    _ paymentDetails: NSDictionary,
    successCallback: @escaping RCTResponseSenderBlock,
    errorCallback: @escaping RCTResponseSenderBlock
  ) {
    Task { @MainActor [weak self] in
      guard let self else {
        errorCallback(["Unable to start payment: payment manager was released."])
        return
      }

      if self.paymentController != nil {
        errorCallback(["A payment is already in progress."])
        return
      }

      self.startPayment(
        with: paymentDetails,
        successCallback: successCallback,
        errorCallback: errorCallback
      )
    }
  }

  @MainActor
  private func startPayment(
    with paymentDetails: NSDictionary,
    successCallback: @escaping RCTResponseSenderBlock,
    errorCallback: @escaping RCTResponseSenderBlock
  ) {
    isTaskCompleted = false
    isPaymentInstructionPresent = false
    didDeliverResult = false
    cancelApplePayWatchdog()

    if let validationError = validateRequiredFields(paymentDetails) {
      reportError(validationError, errorCallback: errorCallback)
      return
    }

    var details = dictionary(from: paymentDetails)
    details["is_submodule"] = true
    details["module_id"] = IntegrationMetadata.moduleId
    details["wrapper_version"] = IntegrationMetadata.wrapperVersion
    isApplePayFlow = Self.isApplePay(details)

    guard let presentingController = topMostViewController() else {
      reportError(
        "Unable to start payment: no active window was found.",
        errorCallback: errorCallback
      )
      return
    }

    self.successCallback = successCallback
    self.errorCallback = errorCallback

    let controller = FiuuXDKController(with: details)
    paymentController = controller

    controller.startXDK(
      completion: { [weak self] result in
        Task { @MainActor in
          self?.handleXDKResult(result)
        }
      },
      onFinishDeepLink: { [weak self] in
        Task { @MainActor in
          self?.handleDeepLink()
        }
      }
    )

    let navigationController = UINavigationController(rootViewController: controller)
    let showCloseButton = !isApplePayFlow && Self.boolValue(details["mp_closebutton_display"])
    navigationController.setNavigationBarHidden(!showCloseButton, animated: false)
    if showCloseButton {
      let appearance = UINavigationBarAppearance()
      appearance.configureWithOpaqueBackground()
      appearance.backgroundColor = .white
      appearance.titleTextAttributes = [.foregroundColor: UIColor.black]
      navigationController.navigationBar.standardAppearance = appearance
      navigationController.navigationBar.scrollEdgeAppearance = appearance
      navigationController.navigationBar.compactAppearance = appearance
      navigationController.navigationBar.tintColor = .systemBlue
    }
    navController = navigationController

    if isApplePayFlow {
      // Keep the controller in the hierarchy so Apple Pay can present, but do
      // not show the standard XDK webview behind the sheet.
      navigationController.modalPresentationStyle = .overFullScreen
      navigationController.view.isOpaque = false
      navigationController.view.backgroundColor = .clear
      navigationController.view.isHidden = true
      controller.view.isHidden = true
      presentingController.present(navigationController, animated: false)
      startApplePayWatchdog()
      return
    }

    navigationController.modalPresentationStyle = .fullScreen
    presentingController.present(navigationController, animated: false)
  }

  @MainActor
  private func handleXDKResult(_ result: Result<String, Error>) {
    switch result {
    case .success(let data):
      finish(with: data)
    case .failure(let error):
      if isApplePayFlow, isExpectedApplePayWebViewFailure(error) {
        // Hidden webview often fails while PassKit is still presenting.
        // Ignore only that false failure; keep the controller for the real result.
        return
      }
      if isTaskCompleted {
        return
      }
      isTaskCompleted = true
      reportError(error.localizedDescription)
      dismissPaymentFlow()
    }
  }

  @MainActor
  private func handleDeepLink() {
    paymentController?.closePayment()

    if isPaymentInstructionPresent {
      isPaymentInstructionPresent = false
      dismissPaymentFlow()
      return
    }

    if isTaskCompleted {
      return
    }
    isTaskCompleted = true
    reportError("Transaction canceled or failed. Please try again.")
    dismissPaymentFlow()
  }

  @MainActor
  private func finish(with payload: String) {
    let normalized = normalizedResult(from: payload)

    if isApplePayFlow, isTransientApplePayWebViewResult(normalized, raw: payload) {
      // Hidden webview often posts a Communication Error JSON while PassKit is
      // still presenting. Ignore it so the sheet can finish with a real result.
      return
    }

    if shouldKeepPaymentInstructionVisible(normalized) {
      isPaymentInstructionPresent = true
      isTaskCompleted = true
      cancelApplePayWatchdog()
      deliver(result: normalized ?? payload)
      return
    }

    if isTaskCompleted {
      return
    }
    isTaskCompleted = true
    deliver(result: normalized ?? payload)
    dismissPaymentFlow()
  }

  private func deliver(result: Any) {
    guard !didDeliverResult else {
      return
    }
    didDeliverResult = true

    if let dictionary = result as? [String: Any], isSuccessfulTransaction(dictionary) {
      successCallback?([dictionary])
      return
    }

    if let dictionary = result as? [String: Any] {
      if let errorCallback {
        errorCallback([dictionary])
      } else {
        reportError(errorMessage(from: dictionary))
      }
      return
    }

    if let errorCallback {
      errorCallback([result])
    } else {
      reportError(String(describing: result))
    }
  }

  private func validateRequiredFields(_ paymentDetails: NSDictionary) -> String? {
    let requiredFields = [
      "mp_merchant_ID",
      "mp_verification_key",
      "mp_amount",
      "mp_order_ID",
    ]
    let placeholders: Set<String> = ["merchant_id", "verfication_key", "verification_key"]
    var missing: [String] = []

    for field in requiredFields {
      let raw = paymentDetails[field]
      let stringValue = (raw as? String) ?? (raw.map { String(describing: $0) })
      let trimmed = stringValue?.trimmingCharacters(in: .whitespacesAndNewlines) ?? ""
      if trimmed.isEmpty || placeholders.contains(trimmed.lowercased()) {
        missing.append(field)
      }
    }

    guard !missing.isEmpty else {
      return nil
    }
    return "Missing or invalid required payment field(s): \(missing.joined(separator: ", ")). Provide valid merchant credentials and a unique mp_order_ID for each payment attempt."
  }

  private static let booleanFields: Set<String> = [
    "mp_express_mode",
    "mp_extended_vcode",
    "mp_closebutton_display",
    "mp_sandbox_mode",
    "mp_channel_editing",
    "mp_editing_enabled",
    "mp_dev_mode",
  ]

  private static func isApplePay(_ details: [String: Any]) -> Bool {
    guard boolValue(details["mp_express_mode"]) else {
      return false
    }
    return stringValue(details["mp_channel"])?.caseInsensitiveCompare("ApplePay") == .orderedSame
  }

  private func isExpectedApplePayWebViewFailure(_ error: Error) -> Bool {
    if error is WKError {
      return true
    }

    let nsError = error as NSError
    if nsError.domain == WKErrorDomain {
      return true
    }

    return isApplePayWebViewFailureText(nsError.localizedDescription)
  }

  /// FiuuXDKSwift sometimes surfaces the hidden-webview glitch as a success JSON
  /// string instead of a `WKError`. Ignore only that transient payload; keep
  /// real StatCode results and Close/cancel JSON such as M00008.
  private func isTransientApplePayWebViewResult(_ result: [String: Any]?, raw: String) -> Bool {
    if let result {
      if !statCode(from: result).isEmpty {
        return false
      }
      let errorText = [
        result["Error"],
        result["error"],
        result["ErrorDesc"],
        result["error_message"],
      ]
      .compactMap { $0 as? String }
      .joined(separator: " ")
      return isApplePayWebViewFailureText(errorText)
    }

    return isApplePayWebViewFailureText(raw)
  }

  private func isApplePayWebViewFailureText(_ message: String) -> Bool {
    let lowered = message.lowercased()
    return lowered.contains("communication") || lowered.contains("webview")
  }

  private func dictionary(from object: NSDictionary) -> [String: Any] {
    var result: [String: Any] = [:]
    object.enumerateKeysAndObjects { key, value, _ in
      guard let stringKey = key as? String else {
        return
      }
      result[stringKey] = Self.normalizedValue(stringKey, value)
    }
    return result
  }

  private static func normalizedValue(_ key: String, _ value: Any) -> Any {
    if booleanFields.contains(key) {
      return boolValue(value)
    }
    if key == "mp_allowed_channels" || key == "mp_gpay_channel" {
      return stringArray(value) ?? value
    }
    return value
  }

  private static func boolValue(_ value: Any?) -> Bool {
    if let bool = value as? Bool {
      return bool
    }
    if let number = value as? NSNumber {
      return number.boolValue
    }
    if let string = stringValue(value) {
      return ["true", "1", "yes"].contains(string.lowercased())
    }
    return false
  }

  private static func stringValue(_ value: Any?) -> String? {
    if let string = value as? String {
      let trimmed = string.trimmingCharacters(in: .whitespacesAndNewlines)
      return trimmed.isEmpty ? nil : trimmed
    }
    if value is NSNull || value == nil {
      return nil
    }
    if let number = value as? NSNumber {
      return number.stringValue
    }
    return nil
  }

  private static func stringArray(_ value: Any?) -> [String]? {
    if let strings = value as? [String] {
      return strings
    }
    if let items = value as? [Any] {
      return items.map { String(describing: $0) }
    }
    return nil
  }

  private func normalizedResult(from payload: String) -> [String: Any]? {
    let trimmed = payload.trimmingCharacters(in: .whitespacesAndNewlines)
    guard trimmed.hasPrefix("{") || trimmed.hasPrefix("[") else {
      return nil
    }
    guard let data = trimmed.data(using: .utf8),
          let json = try? JSONSerialization.jsonObject(with: data) as? [String: Any] else {
      return nil
    }
    return json
  }

  private func statCode(from result: [String: Any]) -> String {
    if let value = result["StatCode"] ?? result["status_code"] {
      return String(describing: value)
    }
    return ""
  }

  private func isSuccessfulTransaction(_ result: [String: Any]) -> Bool {
    let code = statCode(from: result)
    return code == "00"
  }

  private func errorMessage(from result: [String: Any]) -> String {
    if let errorDesc = result["ErrorDesc"] as? String {
      let trimmed = errorDesc.trimmingCharacters(in: .whitespacesAndNewlines)
      if !trimmed.isEmpty {
        return trimmed
      }
    }
    if let errorCode = result["ErrorCode"] {
      return "Payment failed (\(errorCode))."
    }
    return "Transaction canceled or failed. Please try again."
  }

  private func shouldKeepPaymentInstructionVisible(_ result: [String: Any]?) -> Bool {
    guard let result, let instruction = result["pInstruction"] else {
      return false
    }

    let flag: Int
    if let number = instruction as? NSNumber {
      flag = number.intValue
    } else if let string = instruction as? String {
      flag = Int(string) ?? 0
    } else {
      flag = 0
    }

    return flag == 1 && !isPaymentInstructionPresent
  }

  private func reportError(
    _ message: String,
    errorCallback: RCTResponseSenderBlock? = nil
  ) {
    guard !didDeliverResult else {
      return
    }
    didDeliverResult = true

    if let errorCallback {
      errorCallback([message])
      return
    }

    if let errorCallback = self.errorCallback {
      errorCallback([message])
    } else {
      NSLog("[FiuuPayment] %@ (no errorCallback was registered)", message)
    }
  }

  @MainActor
  private func startApplePayWatchdog() {
    cancelApplePayWatchdog()
    applePayWatchdogTask = Task { @MainActor [weak self] in
      do {
        try await Task.sleep(nanoseconds: Self.applePayWatchdogNanoseconds)
      } catch {
        return
      }
      guard let self, !Task.isCancelled, self.isApplePayFlow, !self.isTaskCompleted else {
        return
      }
      self.isTaskCompleted = true
      self.reportError("Apple Pay timed out. Please try again.")
      self.dismissPaymentFlow()
    }
  }

  @MainActor
  private func cancelApplePayWatchdog() {
    applePayWatchdogTask?.cancel()
    applePayWatchdogTask = nil
  }

  @MainActor
  private func dismissPaymentFlow() {
    cancelApplePayWatchdog()
    navController?.dismiss(animated: true)
    navController = nil
    paymentController = nil
    successCallback = nil
    errorCallback = nil
    isApplePayFlow = false
  }

  private func activeKeyWindow() -> UIWindow? {
    let scenes = UIApplication.shared.connectedScenes
      .compactMap { $0 as? UIWindowScene }

    for scene in scenes where scene.activationState == .foregroundActive {
      if let keyWindow = scene.windows.first(where: \.isKeyWindow) {
        return keyWindow
      }
      if let first = scene.windows.first {
        return first
      }
    }

    return scenes.flatMap(\.windows).first
  }

  private func topMostViewController() -> UIViewController? {
    guard let root = activeKeyWindow()?.rootViewController else {
      return nil
    }
    return topMostViewController(from: root)
  }

  private func topMostViewController(from controller: UIViewController) -> UIViewController {
    if let presented = controller.presentedViewController {
      return topMostViewController(from: presented)
    }
    if let navigation = controller as? UINavigationController,
       let visible = navigation.visibleViewController {
      return topMostViewController(from: visible)
    }
    if let tabBar = controller as? UITabBarController,
       let selected = tabBar.selectedViewController {
      return topMostViewController(from: selected)
    }
    return controller
  }
}

private enum IntegrationMetadata {
  static let moduleId = "fiuu-mobile-xdk-reactnative"

  static var wrapperVersion: String {
    wrapperVersion(fromPackageVersion: packageVersion() ?? "0.0.0")
  }

  private static func packageVersion() -> String? {
    guard
      let url = Bundle(for: FiuuPaymentManager.self).url(forResource: "package", withExtension: "json"),
      let data = try? Data(contentsOf: url),
      let json = try? JSONSerialization.jsonObject(with: data) as? [String: Any],
      let version = json["version"] as? String
    else {
      return nil
    }
    let trimmed = version.trimmingCharacters(in: .whitespacesAndNewlines)
    return trimmed.isEmpty ? nil : trimmed
  }

  private static func wrapperVersion(fromPackageVersion version: String) -> String {
    let patch = version.split(separator: ".").last.map(String.init) ?? version
    return "\(patch)rn"
  }
}
