import AppKit
import Foundation
import OSLog

/// Handles window matching and session-to-window mapping algorithms.
@MainActor
final class WindowMatcher {
    private let logger = Logger(
        subsystem: BundleIdentifiers.loggerSubsystem,
        category: "WindowMatcher"
    )

    private let processTracker = ProcessTracker()

    /// Find a window for a specific terminal and session
    func findWindow(
        for terminal: Terminal,
        sessionID: String,
        sessionInfo: ServerSessionInfo?,
        tabReference: String?,
        tabID: String?,
        terminalWindows: [WindowInfo]
    )
        -> WindowInfo?
    {
        // Filter windows for the specific terminal
        let filteredWindows = terminalWindows.filter { $0.terminalApp == terminal }

        // First try to find window by process PID traversal
        if let sessionInfo, let sessionPID = sessionInfo.pid {
            logger.debug("Attempting to find window by process PID: \(sessionPID)")

            // For debugging: log the process tree
            processTracker.logProcessTree(for: pid_t(sessionPID))

            // Try to find the parent process (shell) that owns this session
            if let parentPID = processTracker.getParentProcessID(of: pid_t(sessionPID)) {
                logger.debug("Found parent process PID: \(parentPID)")

                // Look for windows owned by the parent process
                let parentPIDWindows = filteredWindows.filter { window in
                    window.ownerPID == parentPID
                }

                if parentPIDWindows.count == 1 {
                    logger.info("Found single window by parent process match: PID \(parentPID)")
                    return parentPIDWindows.first
                } else if parentPIDWindows.count > 1 {
                    logger
                        .info(
                            "Found \(parentPIDWindows.count) windows for PID \(parentPID), checking session ID in titles"
                        )

                    // Multiple windows - try to match by session ID in title
                    if let matchingWindow = parentPIDWindows.first(where: { window in
                        window.title?.contains("Session \(sessionID)") ?? false
                    }) {
                        logger.info("Found window by session ID '\(sessionID)' in title")
                        return matchingWindow
                    }

                    // If no session ID match, return first window
                    logger.warning("No window with session ID in title, using first window")
                    return parentPIDWindows.first
                }

                // If direct parent match fails, try to find grandparent or higher ancestors
                var currentPID = parentPID
                var depth = 0
                while depth < 10 { // Increased depth for nested shell sessions
                    if let grandParentPID = processTracker.getParentProcessID(of: currentPID) {
                        logger.debug("Checking ancestor process PID: \(grandParentPID) at depth \(depth + 2)")

                        let ancestorPIDWindows = filteredWindows.filter { window in
                            window.ownerPID == grandParentPID
                        }

                        if ancestorPIDWindows.count == 1 {
                            logger
                                .info(
                                    "Found single window by ancestor process match: PID \(grandParentPID) at depth \(depth + 2)"
                                )
                            return ancestorPIDWindows.first
                        } else if ancestorPIDWindows.count > 1 {
                            logger
                                .info(
                                    "Found \(ancestorPIDWindows.count) windows for ancestor PID \(grandParentPID), checking session ID"
                                )

                            // Multiple windows - try to match by session ID in title
                            if let matchingWindow = ancestorPIDWindows.first(where: { window in
                                window.title?.contains("Session \(sessionID)") ?? false
                            }) {
                                logger.info("Found window by session ID '\(sessionID)' in title")
                                return matchingWindow
                            }

                            // If no session ID match, return first window
                            return ancestorPIDWindows.first
                        }

                        currentPID = grandParentPID
                        depth += 1
                    } else {
                        break
                    }
                }
            }
        }

        // Fallback: try to find window by title containing session path or command
        if let sessionInfo {
            let workingDir = sessionInfo.workingDir
            let dirName = (workingDir as NSString).lastPathComponent

            // Look for windows whose title contains the directory name
            if let matchingWindow = filteredWindows.first(where: { window in
                WindowEnumerator.windowTitleContains(window, identifier: dirName) ||
                    WindowEnumerator.windowTitleContains(window, identifier: workingDir)
            }) {
                logger.debug("Found window by directory match: \(dirName)")
                return matchingWindow
            }
        }

        // For Terminal.app with specific tab reference
        if terminal == .terminal, let tabRef = tabReference {
            if let windowID = WindowEnumerator.extractWindowID(from: tabRef) {
                if let matchingWindow = filteredWindows.first(where: { $0.windowID == windowID }) {
                    logger.debug("Found Terminal.app window by ID: \(windowID)")
                    return matchingWindow
                }
            }
        }

        // For iTerm2 with tab ID
        if terminal == .iTerm2, let tabID {
            // Try to match by window title which often includes the window ID
            if let matchingWindow = filteredWindows.first(where: { window in
                WindowEnumerator.windowTitleContains(window, identifier: tabID)
            }) {
                logger.debug("Found iTerm2 window by ID in title: \(tabID)")
                return matchingWindow
            }
        }

        // Fallback: return the most recently created window (highest window ID)
        if let latestWindow = filteredWindows.max(by: { $0.windowID < $1.windowID }) {
            logger.debug("Using most recent window as fallback for session: \(sessionID)")
            return latestWindow
        }

        return nil
    }

    /// Find a terminal window for a session that was attached via `vt`
    func findWindowForSession(
        _ sessionID: String,
        sessionInfo: ServerSessionInfo,
        allWindows: [WindowInfo]
    )
        -> WindowInfo?
    {
        // First try to find window by process PID traversal
        if let sessionPID = sessionInfo.pid {
            logger.debug("Scanning for window by process PID: \(sessionPID) for session \(sessionID)")

            // Log the process tree for debugging
            processTracker.logProcessTree(for: pid_t(sessionPID))

            // Try to traverse up the process tree to find a terminal window
            var currentPID = pid_t(sessionPID)
            var depth = 0
            let maxDepth = 20 // Increased depth for deeply nested sessions

            while depth < maxDepth {
                // Check if any window is owned by this PID
                if let matchingWindow = allWindows.first(where: { window in
                    window.ownerPID == currentPID
                }) {
                    logger.info("Found window by PID \(currentPID) at depth \(depth) for session \(sessionID)")
                    return matchingWindow
                }

                // Move up to parent process
                if let parentPID = processTracker.getParentProcessID(of: currentPID) {
                    if parentPID == 0 || parentPID == 1 {
                        // Reached root process
                        break
                    }
                    currentPID = parentPID
                    depth += 1
                } else {
                    break
                }
            }

            logger.debug("Process traversal completed at depth \(depth) without finding window")
        }

        // Fallback: Find by working directory
        let workingDir = sessionInfo.workingDir
        let dirName = (workingDir as NSString).lastPathComponent

        logger.debug("Trying to match by directory: \(dirName) or full path: \(workingDir)")

        // Look for windows whose title contains the directory name
        if let matchingWindow = allWindows.first(where: { window in
            if let title = window.title {
                let matches = title.contains(dirName) || title.contains(workingDir)
                if matches {
                    logger.debug("Window title '\(title)' matches directory")
                }
                return matches
            }
            return false
        }) {
            logger.info("Found window by directory match: \(dirName) for session \(sessionID)")
            return matchingWindow
        }

        // Try to match by activity status (for sessions with specific activities)
        if let activity = sessionInfo.activityStatus?.specificStatus?.status, !activity.isEmpty {
            logger.debug("Trying to match by activity: \(activity)")

            if let matchingWindow = allWindows.first(where: { window in
                if let title = window.title {
                    return title.contains(activity)
                }
                return false
            }) {
                logger.info("Found window by activity match: \(activity) for session \(sessionID)")
                return matchingWindow
            }
        }

        logger.warning("Could not find window for session \(sessionID) after all attempts")
        logger.debug("Available windows: \(allWindows.count)")
        for (index, window) in allWindows.enumerated() {
            logger
                .debug(
                    "  Window \(index): PID=\(window.ownerPID), Terminal=\(window.terminalApp.rawValue), Title=\(window.title ?? "<no title>")"
                )
        }

        return nil
    }

    /// Find matching tab using accessibility APIs
    func findMatchingTab(tabs: [AXElement], sessionInfo: ServerSessionInfo?) -> AXElement? {
        guard let sessionInfo else { return nil }

        let workingDir = sessionInfo.workingDir
        let dirName = (workingDir as NSString).lastPathComponent
        let sessionID = sessionInfo.id
        let activityStatus = sessionInfo.activityStatus?.specificStatus?.status
        let sessionName = sessionInfo.name

        logger.debug("Looking for tab matching session \(sessionID) in \(tabs.count) tabs")
        logger.debug("  Working dir: \(workingDir)")
        logger.debug("  Dir name: \(dirName)")
        logger.debug("  Session name: \(sessionName)")
        logger.debug("  Activity: \(activityStatus ?? "none")")

        for (index, tab) in tabs.enumerated() {
            if let title = tab.title {
                logger.debug("Tab \(index) title: \(title)")

                // Check for session ID match first (most precise)
                if title.contains(sessionID) || title.contains("TTY_SESSION_ID=\(sessionID)") {
                    logger.info("Found tab by session ID match at index \(index)")
                    return tab
                }

                // Check for session name match
                if !sessionName.isEmpty, title.contains(sessionName) {
                    logger.info("Found tab by session name match: \(sessionName) at index \(index)")
                    return tab
                }

                // Check for activity status match
                if let activity = activityStatus, !activity.isEmpty, title.contains(activity) {
                    logger.info("Found tab by activity match: \(activity) at index \(index)")
                    return tab
                }

                // Check for directory match - be more flexible
                let titleLower = title.lowercased()
                let dirNameLower = dirName.lowercased()
                let workingDirLower = workingDir.lowercased()

                if titleLower.contains(dirNameLower) || titleLower.contains(workingDirLower) {
                    logger.info("Found tab by directory match at index \(index)")
                    return tab
                }

                // Check if the tab title ends with the directory name (common pattern)
                if title.hasSuffix(dirName) || title.hasSuffix(" - \(dirName)") {
                    logger.info("Found tab by directory suffix match at index \(index)")
                    return tab
                }
            } else {
                logger.debug("Tab \(index): Could not get title")
            }
        }

        logger.warning("No matching tab found for session \(sessionID)")
        return nil
    }
}
