import UIKit
import Toast_Swift

class PAMToastManager {
  private static let toastViewTag = 9997

  // Instance-level state
  private var toastQueue: [UIView] = []
  private var toastDurationTimers: [UIView: Timer] = [:]
  private weak var toastContainer: UIView?
  private weak var toastContainerWindow: UIWindow?
  private var toastContainerBottomConstraint: NSLayoutConstraint?

  // Keyboard state
  private var lastKeyboardHeight: CGFloat = 0
  private var isKeyboardVisibleState: Bool = false
  private var keyboardObserverSetup = false

  private let getKeyWindow: () -> UIWindow?

  init(getKeyWindow: @escaping () -> UIWindow?) {
    self.getKeyWindow = getKeyWindow
  }

  func configure() {
    setupKeyboardObserversIfNeeded()
    DispatchQueue.main.async {
      if let window = self.getKeyWindow() {
        let kb = self.checkKeyboardState(for: window)
        let container = self.ensureToastContainer(in: window)
        self.updateToastContainerPosition(for: window, animated: false, overrideState: kb)
        self.updateToastQueuePositions()
        self.toastContainer = container
        self.toastContainerWindow = window
      }
    }
  }

  func showToast(params: PAM_ToastParams, loadImage: @escaping (@escaping (UIImage?) -> Void) -> Void) {
    DispatchQueue.main.async { [weak self] in
      guard let self = self, let window = self.getKeyWindow() else { return }

      self.setupKeyboardObserversIfNeeded()
      let container = self.ensureToastContainer(in: window)
      let keyboardState = self.checkKeyboardState(for: window)
      self.updateToastContainerPosition(for: window, animated: false, overrideState: keyboardState)

      let ms = Double(params.duration ?? 2000)
      let duration: TimeInterval = ms / 1000.0
      var style = ToastStyle()
      style.backgroundColor = UIColor(hex: params.backgroundColor)
      style.messageColor = UIColor(hex: params.textColor)
      style.fadeDuration = 0
      if let fontSize = params.fontSize {
        style.messageFont = .systemFont(ofSize: CGFloat(fontSize))
      }

      loadImage { imageToShow in
        DispatchQueue.main.async {
          if imageToShow != nil, let imageSize = params.imageSize {
            style.imageSize = CGSize(width: CGFloat(imageSize), height: CGFloat(imageSize))
          }
          ToastManager.shared.isTapToDismissEnabled = true
          ToastManager.shared.isQueueEnabled = false

          container.makeToast(
            params.message,
            duration: duration,
            position: .bottom,
            title: nil,
            image: imageToShow,
            style: style
          ) { _ in }

          self.findAndAddToastToQueue(in: container, duration: duration)

          DispatchQueue.main.asyncAfter(deadline: .now() + 0.02) {
            let kb = self.checkKeyboardState(for: window)
            self.updateToastContainerPosition(for: window, animated: false, overrideState: kb)
            self.updateToastQueuePositions()
          }
        }
      }
    }
  }

  // MARK: - Keyboard observers

  private func setupKeyboardObserversIfNeeded() {
    guard !keyboardObserverSetup else { return }
    keyboardObserverSetup = true

    let handleFrame: (Notification) -> Void = { [weak self] notification in
      guard let self = self,
            let frame = notification.userInfo?[UIResponder.keyboardFrameEndUserInfoKey] as? CGRect else { return }
      let height = frame.height
      self.lastKeyboardHeight = height
      self.isKeyboardVisibleState = height > 0
      if let window = self.toastContainerWindow {
        self.updateToastContainerPosition(for: window, animated: true)
      }
    }

    NotificationCenter.default.addObserver(forName: UIResponder.keyboardWillShowNotification, object: nil, queue: .main, using: handleFrame)
    NotificationCenter.default.addObserver(forName: UIResponder.keyboardWillChangeFrameNotification, object: nil, queue: .main, using: handleFrame)
    NotificationCenter.default.addObserver(forName: UIResponder.keyboardWillHideNotification, object: nil, queue: .main) { [weak self] _ in
      guard let self = self else { return }
      self.isKeyboardVisibleState = false
      self.lastKeyboardHeight = 0
      if let window = self.toastContainerWindow {
        self.updateToastContainerPosition(for: window, animated: true)
      }
    }
  }

  // MARK: - Container

  @discardableResult
  private func ensureToastContainer(in window: UIWindow) -> UIView {
    if let existing = toastContainer, existing.superview === window {
      toastContainerWindow = window
      existing.layer.zPosition = CGFloat.greatestFiniteMagnitude
      window.bringSubviewToFront(existing)
      let kb = checkKeyboardState(for: window)
      updateToastContainerPosition(for: window, animated: false, overrideState: kb)
      return existing
    }

    let container = UIView()
    container.translatesAutoresizingMaskIntoConstraints = false
    container.backgroundColor = .clear
    container.isUserInteractionEnabled = false
    container.layer.zPosition = CGFloat.greatestFiniteMagnitude
    window.addSubview(container)
    window.bringSubviewToFront(container)
    NSLayoutConstraint.activate([
      container.leadingAnchor.constraint(equalTo: window.leadingAnchor),
      container.trailingAnchor.constraint(equalTo: window.trailingAnchor),
      container.topAnchor.constraint(equalTo: window.topAnchor),
    ])
    let bottom = container.bottomAnchor.constraint(equalTo: window.bottomAnchor)
    bottom.isActive = true
    toastContainerBottomConstraint = bottom
    toastContainer = container
    toastContainerWindow = window

    let kb = checkKeyboardState(for: window)
    updateToastContainerPosition(for: window, animated: false, overrideState: kb)
    return container
  }

  private func updateToastContainerPosition(
    for window: UIWindow,
    animated: Bool,
    overrideState: (isVisible: Bool, height: CGFloat)? = nil
  ) {
    guard let bottomConstraint = toastContainerBottomConstraint else { return }
    let kb = overrideState ?? checkKeyboardState(for: window)
    let keyboardPadding: CGFloat = 12
    let targetConstant = kb.isVisible ? -(max(0, kb.height + keyboardPadding)) : 0
    guard bottomConstraint.constant != targetConstant else { return }
    bottomConstraint.constant = targetConstant
    if animated {
      UIView.animate(withDuration: 0.25, delay: 0, options: [.curveEaseOut]) { window.layoutIfNeeded() }
    } else {
      window.layoutIfNeeded()
    }
  }

  private func checkKeyboardState(for window: UIWindow) -> (isVisible: Bool, height: CGFloat) {
    if isKeyboardVisibleState, lastKeyboardHeight > 0 { return (true, lastKeyboardHeight) }

    let screenHeight = UIScreen.main.bounds.height
    let keyboardWindow = UIApplication.shared.connectedScenes
      .compactMap { $0 as? UIWindowScene }
      .flatMap { $0.windows }
      .first { String(describing: type(of: $0)).contains("UIRemoteKeyboardWindow") }

    if let kWindow = keyboardWindow {
      let height = max(0, screenHeight - kWindow.frame.origin.y)
      lastKeyboardHeight = height
      isKeyboardVisibleState = height > 0
      return (height > 0, height)
    }

    let bottomInset = window.safeAreaInsets.bottom
    if bottomInset > 0 {
      lastKeyboardHeight = bottomInset
      isKeyboardVisibleState = true
      return (true, bottomInset)
    }
    isKeyboardVisibleState = false
    lastKeyboardHeight = 0
    return (false, 0)
  }

  // MARK: - Toast Queue

  private func findAndAddToastToQueue(in container: UIView, duration: TimeInterval) {
    guard let window = container.window else { return }

    let candidateViews = container.subviews.filter { view in
      guard view.tag != PAMToastManager.toastViewTag else { return false }
      guard view.alpha > 0 && !view.isHidden else { return false }
      guard view.frame.minY > window.bounds.height * 0.3 else { return false }
      guard view.frame.height < window.bounds.height * 0.3 else { return false }
      func containsLabel(_ v: UIView) -> Bool {
        if let l = v as? UILabel, !(l.text?.isEmpty ?? true) { return true }
        return v.subviews.contains { containsLabel($0) }
      }
      return containsLabel(view)
    }

    guard let newToast = candidateViews.sorted(by: { $0.frame.maxY > $1.frame.maxY }).first else { return }

    newToast.tag = PAMToastManager.toastViewTag
    newToast.layer.masksToBounds = false
    newToast.layer.shadowColor = UIColor.black.withAlphaComponent(0.15).cgColor
    newToast.layer.shadowOpacity = 1.0
    newToast.layer.shadowRadius = 6
    newToast.layer.shadowOffset = CGSize(width: 0, height: 2)

    let stackOffset = CGFloat(toastQueue.count) * 8
    let totalOffset = 8 - stackOffset
    toastQueue.append(newToast)
    newToast.layer.zPosition = 1000 + CGFloat(toastQueue.count)
    newToast.layer.removeAllAnimations()

    UIView.performWithoutAnimation {
      newToast.alpha = 0
      newToast.transform = CGAffineTransform(translationX: 0, y: totalOffset)
    }
    UIView.animate(
      withDuration: 0.25, delay: 0,
      options: [.curveEaseOut, .beginFromCurrentState, .allowUserInteraction]
    ) { newToast.alpha = 1.0 }

    updateToastQueuePositions()

    let timer = Timer.scheduledTimer(withTimeInterval: duration, repeats: false) { [weak self] _ in
      DispatchQueue.main.async { self?.fadeOutToast(newToast) }
    }
    toastDurationTimers[newToast] = timer
  }

  private func updateToastQueuePositions() {
    let visibleCount = 3
    let oldestVisible = max(0, toastQueue.count - visibleCount)

    for (index, toast) in toastQueue.enumerated() {
      guard toast.superview != nil else {
        toastQueue.removeAll { $0 == toast }
        toastDurationTimers[toast]?.invalidate()
        toastDurationTimers.removeValue(forKey: toast)
        continue
      }
      if index < oldestVisible { fadeOutToast(toast); continue }

      let displayIndex = toastQueue.count - 1 - index
      let stackOffset = CGFloat(displayIndex) * 8
      let totalOffset: CGFloat = 8 - stackOffset
      let scale = max(0.94, 1.0 - 0.03 * CGFloat(displayIndex))
      toast.layer.zPosition = 1000 + CGFloat(visibleCount - displayIndex)

      UIView.animate(withDuration: 0.3, delay: 0, options: [.curveEaseOut]) {
        toast.transform = CGAffineTransform(translationX: 0, y: totalOffset).scaledBy(x: scale, y: scale)
        toast.alpha = 1.0
      }
    }
  }

  private func fadeOutToast(_ toast: UIView) {
    toastDurationTimers[toast]?.invalidate()
    toastDurationTimers.removeValue(forKey: toast)
    UIView.animate(withDuration: 0.3, animations: {
      toast.alpha = 0
      toast.transform = toast.transform.translatedBy(x: 0, y: -20)
    }) { [weak self] _ in
      toast.removeFromSuperview()
      self?.toastQueue.removeAll { $0 === toast }
      self?.updateToastQueuePositions()
    }
  }
}
