import Foundation
import UIKit

/// Telegram-parity in-app notification banner manager.
/// Shows a floating banner at the top of the screen when a message arrives
/// in foreground for a non-active, non-muted chat.
///
/// Queue: one banner at a time. New banner replaces current.
/// Auto-dismiss: 5 seconds. Swipe-up or tap dismisses immediately.
@MainActor
final class InAppBannerManager {

    static let shared = InAppBannerManager()

    private var dismissTask: Task<Void, Never>?
    private var dismissGeneration: UInt64 = 0
    private var overlayWindow: InAppBannerWindow?
    private weak var overlayController: InAppBannerOverlayViewController?

    /// Notification posted by SessionManager.processIncomingMessage
    /// when a foreground message should trigger an in-app banner.
    static let showBannerNotification = Notification.Name("InAppBannerManager.showBanner")

    private init() {
        NotificationCenter.default.addObserver(
            forName: Self.showBannerNotification,
            object: nil,
            queue: .main
        ) { [weak self] notification in
            guard let data = notification.object as? BannerData else { return }
            Task { @MainActor [weak self] in
                self?.show(data)
            }
        }
    }

    func show(_ data: BannerData) {
        // Replace current banner.
        dismissTask?.cancel()
        dismissTask = nil
        dismissGeneration &+= 1
        guard let controller = ensureOverlayController() else { return }

        controller.presentBanner(
            data: data,
            onTap: { [weak self] in
                self?.openChat(with: data)
            },
            onExpand: { [weak self] in
                self?.openChat(with: data)
            },
            onDismiss: { [weak self] in
                self?.dismiss(animated: false)
            },
            onDragBegan: { [weak self] in
                self?.cancelAutoDismiss()
            },
            onDragEndedWithoutAction: { [weak self] in
                self?.resumeAutoDismiss()
            }
        )

        scheduleAutoDismiss(for: dismissGeneration)
    }

    func dismiss(animated: Bool = true) {
        dismissTask?.cancel()
        dismissTask = nil
        dismissGeneration &+= 1

        guard let controller = overlayController else {
            teardownOverlayIfNeeded()
            return
        }

        controller.dismissBanner(animated: animated) { [weak self] in
            self?.teardownOverlayIfNeeded()
        }
    }

    /// Cancel auto-dismiss timer (e.g., during active pan gesture).
    /// Telegram: cancels timeout when abs(translation) > 4pt.
    func cancelAutoDismiss() {
        dismissTask?.cancel()
        dismissTask = nil
    }

    /// Resume auto-dismiss timer after a gesture ends without dismiss/expand.
    /// Telegram resets timeout when the banner springs back to origin.
    func resumeAutoDismiss() {
        guard overlayController?.hasBanner == true else { return }
        scheduleAutoDismiss(for: dismissGeneration)
    }

    private func scheduleAutoDismiss(for generation: UInt64) {
        dismissTask?.cancel()
        // Auto-dismiss after 5 seconds (Telegram parity).
        dismissTask = Task { [weak self] in
            try? await Task.sleep(for: .seconds(5))
            guard !Task.isCancelled else { return }
            await MainActor.run {
                guard let self, generation == self.dismissGeneration else { return }
                self.dismiss(animated: true)
            }
        }
    }

    private func openChat(with data: BannerData) {
        dismiss(animated: false)
        let route = ChatRoute(
            publicKey: data.senderKey,
            title: data.senderName,
            username: "",
            verified: data.verified
        )
        AppDelegate.pendingChatRoute = route
        AppDelegate.pendingChatRouteTimestamp = Date()
        NotificationCenter.default.post(
            name: .openChatFromNotification,
            object: route
        )
    }

    private func ensureOverlayController() -> InAppBannerOverlayViewController? {
        if let controller = overlayController {
            if overlayWindow?.isHidden == true {
                overlayWindow?.isHidden = false
            }
            return controller
        }

        guard let scene = activeWindowScene else { return nil }

        let window = InAppBannerWindow(windowScene: scene)
        window.backgroundColor = .clear
        window.windowLevel = .statusBar + 1

        let controller = InAppBannerOverlayViewController()
        window.rootViewController = controller
        window.isHidden = false

        overlayWindow = window
        overlayController = controller
        return controller
    }

    private var activeWindowScene: UIWindowScene? {
        let scenes = UIApplication.shared.connectedScenes.compactMap { $0 as? UIWindowScene }
        if let active = scenes.first(where: { $0.activationState == .foregroundActive }) {
            return active
        }
        return scenes.first(where: { $0.activationState == .foregroundInactive })
    }

    private func teardownOverlayIfNeeded() {
        guard overlayController?.hasBanner != true else { return }
        overlayWindow?.isHidden = true
        overlayWindow = nil
        overlayController = nil
    }

    /// Dismiss banner only if it belongs to the specified dialog.
    /// Called when a cross-device read receipt arrives via WebSocket.
    func dismissIfMatchingDialog(_ dialogKey: String) {
        guard let controller = overlayController,
              controller.currentBannerKey == dialogKey else { return }
        dismiss(animated: true)
    }

    // MARK: - Data

    struct BannerData: Identifiable {
        let senderKey: String
        let senderName: String
        let messagePreview: String
        let isGroup: Bool
        let verified: Int

        var id: String { senderKey }
    }
}

private final class InAppBannerWindow: UIWindow {
    override func hitTest(_ point: CGPoint, with event: UIEvent?) -> UIView? {
        let result = super.hitTest(point, with: event)
        // If only the window itself was hit (no subview claimed it), return nil
        // so the touch passes through to the app window below.
        return result === self ? nil : result
    }
}

private final class InAppBannerOverlayViewController: UIViewController {

    private let passthroughView = InAppBannerPassthroughView()
    private var bannerView: InAppBannerView?
    private(set) var currentBannerKey: String?

    var hasBanner: Bool {
        bannerView != nil
    }

    override func loadView() {
        view = passthroughView
        view.backgroundColor = .clear
    }

    override func viewDidAppear(_ animated: Bool) {
        super.viewDidAppear(animated)
        view.isUserInteractionEnabled = true
    }

    func presentBanner(
        data: InAppBannerManager.BannerData,
        onTap: @escaping () -> Void,
        onExpand: @escaping () -> Void,
        onDismiss: @escaping () -> Void,
        onDragBegan: @escaping () -> Void,
        onDragEndedWithoutAction: @escaping () -> Void
    ) {
        dismissBanner(animated: false) {}
        currentBannerKey = data.senderKey

        let banner = InAppBannerView(frame: .zero)
        banner.translatesAutoresizingMaskIntoConstraints = false
        banner.applyCallbacks(
            onTap: onTap,
            onDismiss: onDismiss,
            onExpand: onExpand,
            onDragBegan: onDragBegan,
            onDragEndedWithoutAction: onDragEndedWithoutAction
        )
        banner.configure(
            senderName: data.senderName,
            messagePreview: data.messagePreview,
            senderKey: data.senderKey,
            isGroup: data.isGroup
        )

        view.addSubview(banner)
        NSLayoutConstraint.activate([
            banner.leadingAnchor.constraint(equalTo: view.leadingAnchor, constant: 8),
            banner.trailingAnchor.constraint(equalTo: view.trailingAnchor, constant: -8),
            banner.topAnchor.constraint(equalTo: view.safeAreaLayoutGuide.topAnchor, constant: InAppBannerView.preferredTopInset),
            banner.heightAnchor.constraint(equalToConstant: InAppBannerView.preferredHeight),
        ])

        view.layoutIfNeeded()
        bannerView = banner
        passthroughView.bannerView = banner
        banner.animateIn()
    }

    func dismissBanner(animated: Bool, completion: @escaping () -> Void) {
        guard let banner = bannerView else {
            completion()
            return
        }

        let remove = { [weak self, weak banner] in
            guard let self else {
                completion()
                return
            }
            banner?.removeFromSuperview()
            if self.bannerView === banner {
                self.bannerView = nil
                self.currentBannerKey = nil
            }
            self.passthroughView.bannerView = nil
            completion()
        }

        if animated {
            banner.animateOut(completion: remove)
        } else {
            remove()
        }
    }
}

private final class InAppBannerPassthroughView: UIView {

    weak var bannerView: UIView?

    override func point(inside point: CGPoint, with event: UIEvent?) -> Bool {
        guard let bannerView else { return false }
        let pointInBanner = convert(point, to: bannerView)
        return bannerView.bounds.contains(pointInBanner)
    }

    override func hitTest(_ point: CGPoint, with event: UIEvent?) -> UIView? {
        guard let bannerView else { return nil }
        let pointInBanner = convert(point, to: bannerView)
        guard bannerView.bounds.contains(pointInBanner) else { return nil }
        return bannerView.hitTest(pointInBanner, with: event)
    }
}
