import AppKit
import Foundation
import OSLog

/// Handles focusing specific terminal windows and tabs.
@MainActor
final class WindowFocuser {
    private let logger = Logger(
        subsystem: BundleIdentifiers.loggerSubsystem,
        category: "WindowFocuser"
    )

    private let windowMatcher = WindowMatcher()
    private let highlightEffect: WindowHighlightEffect

    init() {
        // Load configuration from UserDefaults
        let config = Self.loadHighlightConfig()
        self.highlightEffect = WindowHighlightEffect(config: config)

        // Observe UserDefaults changes
        NotificationCenter.default.addObserver(
            self,
            selector: #selector(userDefaultsDidChange),
            name: UserDefaults.didChangeNotification,
            object: nil
        )
    }

    deinit {
        NotificationCenter.default.removeObserver(self)
    }

    /// Load highlight configuration from UserDefaults
    private static func loadHighlightConfig() -> WindowHighlightConfig {
        let defaults = UserDefaults.standard
        let isEnabled = defaults.object(forKey: "windowHighlightEnabled") as? Bool ?? true
        let style = defaults.string(forKey: "windowHighlightStyle") ?? "default"

        guard isEnabled else {
            return WindowHighlightConfig(
                color: .clear,
                duration: 0,
                borderWidth: 0,
                glowRadius: 0,
                isEnabled: false
            )
        }

        switch style {
        case "subtle":
            return .subtle
        case "neon":
            return .neon
        case "custom":
            // Load custom color
            let colorData = defaults.data(forKey: "windowHighlightColor") ?? Data()
            if !colorData.isEmpty,
               let nsColor = try? NSKeyedUnarchiver.unarchivedObject(ofClass: NSColor.self, from: colorData)
            {
                return WindowHighlightConfig(
                    color: nsColor,
                    duration: 0.8,
                    borderWidth: 4.0,
                    glowRadius: 12.0,
                    isEnabled: true
                )
            }
            return .default
        default:
            return .default
        }
    }

    /// Handle UserDefaults changes
    @objc
    private func userDefaultsDidChange(_ notification: Notification) {
        // Update highlight configuration when settings change
        let newConfig = Self.loadHighlightConfig()
        highlightEffect.updateConfig(newConfig)
    }

    /// Focus a window based on terminal type
    func focusWindow(_ windowInfo: WindowInfo) {
        switch windowInfo.terminalApp {
        case .terminal:
            // Terminal.app has special AppleScript support for tab selection
            focusTerminalAppWindow(windowInfo)
        case .iTerm2:
            // iTerm2 uses its own tab system, needs special handling
            focusiTerm2Window(windowInfo)
        default:
            // All other terminals that use macOS standard tabs
            focusWindowUsingAccessibility(windowInfo)
        }
    }

    /// Focuses a Terminal.app window/tab.
    private func focusTerminalAppWindow(_ windowInfo: WindowInfo) {
        if let tabRef = windowInfo.tabReference {
            // Use stored tab reference to select the tab
            // The tabRef format is "tab id X of window id Y"
            // Escape the tab reference to prevent injection
            let escapedTabRef = tabRef.replacingOccurrences(of: "\"", with: "\\\"")
                .replacingOccurrences(of: "\n", with: "\\n")
                .replacingOccurrences(of: "\r", with: "\\r")

            let script = """
            tell application "Terminal"
                activate
                set selected of \(escapedTabRef) to true
                set frontmost of window id \(AppleScriptSecurity.escapeNumber(windowInfo.windowID)) to true
            end tell
            """

            do {
                try AppleScriptExecutor.shared.execute(script)
                logger.info("Focused Terminal.app tab using reference: \(tabRef)")
            } catch {
                logger.error("Failed to focus Terminal.app tab: \(error)")
                // Fallback to accessibility
                focusWindowUsingAccessibility(windowInfo)
            }
        } else {
            // Fallback to window ID based focusing
            let script = """
            tell application "Terminal"
                activate
                set allWindows to windows
                repeat with w in allWindows
                    if id of w is \(AppleScriptSecurity.escapeNumber(windowInfo.windowID)) then
                        set frontmost of w to true
                        exit repeat
                    end if
                end repeat
            end tell
            """

            do {
                try AppleScriptExecutor.shared.execute(script)
            } catch {
                logger.error("Failed to focus Terminal.app window: \(error)")
                focusWindowUsingAccessibility(windowInfo)
            }
        }
    }

    /// Focuses an iTerm2 window.
    private func focusiTerm2Window(_ windowInfo: WindowInfo) {
        // iTerm2 has its own tab system that doesn't use standard macOS tabs
        // We need to use AppleScript to find and select the correct tab

        let sessionInfo = SessionMonitor.shared.sessions[windowInfo.sessionID]
        let workingDir = sessionInfo?.workingDir ?? ""
        let dirName = (workingDir as NSString).lastPathComponent

        // Escape all user-provided values to prevent injection
        let escapedSessionID = AppleScriptSecurity.escapeString(windowInfo.sessionID)
        let escapedDirName = AppleScriptSecurity.escapeString(dirName)
        let escapedTabID = windowInfo.tabID.map { AppleScriptSecurity.escapeString($0) } ?? ""

        // Try to find and focus the tab with matching content
        let script = """
        tell application "iTerm2"
            activate

            -- Look through all windows
            repeat with w in windows
                -- Look through all tabs in the window
                repeat with t in tabs of w
                    -- Look through all sessions in the tab
                    repeat with s in sessions of t
                        -- Check if the session's name or working directory matches
                        set sessionName to name of s

                        -- Try to match by session content
                        if sessionName contains "\(escapedSessionID)" or sessionName contains "\(escapedDirName)" then
                            -- Found it! Select this tab and window
                            select w
                            select t
                            select s
                            return "Found and selected session"
                        end if
                    end repeat
                end repeat
            end repeat

            -- If we have a window ID, at least focus that window
            if "\(escapedTabID)" is not "" then
                try
                    tell window id "\(escapedTabID)"
                        select
                    end tell
                end try
            end if
        end tell
        """

        do {
            let result = try AppleScriptExecutor.shared.executeWithResult(script)
            logger.info("iTerm2 focus result: \(result)")
        } catch {
            logger.error("Failed to focus iTerm2 window/tab: \(error)")
            // Fallback to accessibility
            focusWindowUsingAccessibility(windowInfo)
        }
    }

    /// Get the first tab group in a window (improved approach based on screenshot)
    private func getTabGroup(from window: AXElement) -> AXElement? {
        guard let children = window.children else {
            return nil
        }

        // Find the first element with role kAXTabGroupRole
        return children.first { elem in
            elem.role == kAXTabGroupRole
        }
    }

    /// Select the correct tab in a window that uses macOS standard tabs
    private func selectTab(
        tabs: [AXElement],
        windowInfo: WindowInfo,
        sessionInfo: ServerSessionInfo?
    ) {
        logger.debug("Attempting to select tab for session \(windowInfo.sessionID) from \(tabs.count) tabs")

        // Try to find the correct tab
        if let matchingTab = windowMatcher.findMatchingTab(tabs: tabs, sessionInfo: sessionInfo) {
            // Found matching tab - select it using kAXPressAction (most reliable)
            if matchingTab.press() {
                logger.info("Successfully selected matching tab for session \(windowInfo.sessionID)")
            } else {
                logger.warning("Failed to select tab with kAXPressAction")

                // Try alternative selection method - set as selected
                if matchingTab.isAttributeSettable(kAXSelectedAttribute) {
                    let setResult = matchingTab.setSelected(true)
                    if setResult == .success {
                        logger.info("Selected tab using AXSelected attribute")
                    } else {
                        logger.error("Failed to set AXSelected attribute, error: \(setResult.rawValue)")
                    }
                }
            }
        } else if tabs.count == 1 {
            // If only one tab, select it
            tabs[0].press()
            logger.info("Selected the only available tab")
        } else {
            // Multiple tabs but no match - try to find by index or select first
            logger
                .warning(
                    "Multiple tabs (\(tabs.count)) but could not identify correct one for session \(windowInfo.sessionID)"
                )

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

    /// Select a tab by index in a tab group (helper method from screenshot)
    private func selectTab(at index: Int, in group: AXElement) -> Bool {
        guard let tabs = group.tabs,
              index < tabs.count
        else {
            logger.warning("Could not get tabs from group or index out of bounds")
            return false
        }

        return tabs[index].press()
    }

    /// Focuses a window by using the process PID directly
    private func focusWindowUsingPID(_ windowInfo: WindowInfo) -> Bool {
        // Get session info for better matching
        let sessionInfo = SessionMonitor.shared.sessions[windowInfo.sessionID]
        // Create AXElement directly from the PID
        let axProcess = AXElement.application(pid: windowInfo.ownerPID)

        // Get windows from this specific process
        guard let windows = axProcess.windows,
              !windows.isEmpty
        else {
            logger.debug("PID-based lookup failed for PID \(windowInfo.ownerPID), no windows found")
            return false
        }

        logger.info("Found \(windows.count) window(s) for PID \(windowInfo.ownerPID)")

        // Single window case - simple!
        if windows.count == 1 {
            logger.info("Single window found for PID \(windowInfo.ownerPID), focusing it directly")
            let window = windows[0]

            // Show highlight effect
            highlightEffect.highlightWindow(window, bounds: window.frame())

            // Focus the window
            window.setMain(true)
            window.setFocused(true)

            // Bring app to front
            if let app = NSRunningApplication(processIdentifier: windowInfo.ownerPID) {
                app.activate()
            }

            return true
        }

        // Multiple windows - need to be smarter
        logger.info("Multiple windows found for PID \(windowInfo.ownerPID), using scoring system")

        // Use our existing scoring logic but only on these PID-specific windows
        var bestMatch: (window: AXElement, score: Int)?

        for (index, window) in windows.enumerated() {
            var matchScore = 0

            // Check window title for session ID or working directory (most reliable)
            if let title = window.title {
                logger.debug("Window \(index) title: '\(title)'")

                // Check for session ID in title
                if title.contains(windowInfo.sessionID) || title.contains("TTY_SESSION_ID=\(windowInfo.sessionID)") {
                    matchScore += 200 // Highest score for session ID match
                    logger.debug("Window \(index) has session ID in title!")
                }

                // Check for working directory in title
                if let sessionInfo {
                    let workingDir = sessionInfo.workingDir
                    let dirName = (workingDir as NSString).lastPathComponent

                    if !dirName
                        .isEmpty &&
                        (title.contains(dirName) || title.hasSuffix(dirName) || title.hasSuffix(" - \(dirName)"))
                    {
                        matchScore += 100 // High score for directory match
                        logger.debug("Window \(index) has working directory in title: \(dirName)")
                    }

                    // Check for session name
                    if !sessionInfo.name.isEmpty && title.contains(sessionInfo.name) {
                        matchScore += 150 // High score for session name match
                        logger.debug("Window \(index) has session name in title: \(sessionInfo.name)")
                    }
                }
            }

            // Check window ID (less reliable for terminals)
            if let axWindowID = window.windowID {
                if axWindowID == windowInfo.windowID {
                    matchScore += 50 // Lower score since window IDs can be unreliable
                    logger.debug("Window \(index) has matching ID: \(axWindowID)")
                }
            }

            // Check bounds if available (least reliable as windows can move)
            if let bounds = windowInfo.bounds,
               let windowFrame = window.frame()
            {
                let tolerance: CGFloat = 5.0
                if abs(windowFrame.origin.x - bounds.origin.x) < tolerance &&
                    abs(windowFrame.origin.y - bounds.origin.y) < tolerance &&
                    abs(windowFrame.width - bounds.width) < tolerance &&
                    abs(windowFrame.height - bounds.height) < tolerance
                {
                    matchScore += 25 // Lowest score for bounds match
                    logger.debug("Window \(index) bounds match")
                }
            }

            if matchScore > 0 {
                if bestMatch == nil || matchScore > bestMatch?.score ?? 0 {
                    bestMatch = (window, matchScore)
                }
            }
        }

        if let best = bestMatch {
            logger.info("Focusing best match window with score \(best.score) for PID \(windowInfo.ownerPID)")

            // Show highlight effect
            highlightEffect.highlightWindow(best.window, bounds: best.window.frame())

            // Focus the window
            best.window.setMain(true)
            best.window.setFocused(true)

            // Bring app to front
            if let app = NSRunningApplication(processIdentifier: windowInfo.ownerPID) {
                app.activate()
            }

            return true
        }

        logger.error("No matching window found for PID \(windowInfo.ownerPID)")
        return false
    }

    /// Focuses a window using Accessibility APIs.
    private func focusWindowUsingAccessibility(_ windowInfo: WindowInfo) {
        // First try PID-based approach
        if focusWindowUsingPID(windowInfo) {
            logger.info("Successfully focused window using PID-based approach")
            return
        }

        // Fallback to the original approach if PID-based fails
        logger.info("Falling back to terminal app-based window search")

        // First bring the application to front
        if let app = NSRunningApplication(processIdentifier: windowInfo.ownerPID) {
            app.activate()
            logger.info("Activated application with PID: \(windowInfo.ownerPID)")
        }

        // Use AXElement to focus the specific window
        let axApp = AXElement.application(pid: windowInfo.ownerPID)

        guard let windows = axApp.windows,
              !windows.isEmpty
        else {
            logger.error("Failed to get windows for application")
            return
        }

        logger
            .info(
                "Found \(windows.count) windows for \(windowInfo.terminalApp.rawValue), looking for window ID: \(windowInfo.windowID)"
            )

        // Get session info for tab matching
        let sessionInfo = SessionMonitor.shared.sessions[windowInfo.sessionID]

        // First, try to find window with matching tab content
        var bestMatchWindow: (window: AXElement, score: Int)?

        for (index, window) in windows.enumerated() {
            var matchScore = 0
            var windowMatches = false

            // Try window ID attribute for matching
            if let axWindowID = window.windowID {
                if axWindowID == windowInfo.windowID {
                    windowMatches = true
                    matchScore += 100 // High score for exact ID match
                }
                logger
                    .debug(
                        "Window \(index) windowID: \(axWindowID), target: \(windowInfo.windowID), matches: \(windowMatches)"
                    )
            }

            // Check window position and size as secondary validation
            if let bounds = windowInfo.bounds,
               let windowFrame = window.frame()
            {
                // Check if bounds approximately match (within 5 pixels tolerance)
                let tolerance: CGFloat = 5.0
                if abs(windowFrame.origin.x - bounds.origin.x) < tolerance &&
                    abs(windowFrame.origin.y - bounds.origin.y) < tolerance &&
                    abs(windowFrame.width - bounds.width) < tolerance &&
                    abs(windowFrame.height - bounds.height) < tolerance
                {
                    matchScore += 50 // Medium score for bounds match
                    logger
                        .debug(
                            "Window \(index) bounds match! Position: (\(windowFrame.origin.x), \(windowFrame.origin.y)), Size: (\(windowFrame.width), \(windowFrame.height))"
                        )
                }
            }

            // Check window title for session information
            if let title = window.title {
                logger.debug("Window \(index) title: '\(title)'")

                // Check for session ID in title (most reliable)
                if title.contains(windowInfo.sessionID) || title.contains("TTY_SESSION_ID=\(windowInfo.sessionID)") {
                    matchScore += 200 // Highest score
                    logger.debug("Window \(index) has session ID in title!")
                }

                // Check for session-specific information
                if let sessionInfo {
                    let workingDir = sessionInfo.workingDir
                    let dirName = (workingDir as NSString).lastPathComponent

                    if !dirName.isEmpty && (title.contains(dirName) || title.hasSuffix(dirName)) {
                        matchScore += 100
                        logger.debug("Window \(index) has working directory in title")
                    }

                    if !sessionInfo.name.isEmpty && title.contains(sessionInfo.name) {
                        matchScore += 150
                        logger.debug("Window \(index) has session name in title")
                    }
                }

                // Original title match logic as fallback
                if !title
                    .isEmpty && (windowInfo.title?.contains(title) ?? false || title.contains(windowInfo.title ?? ""))
                {
                    matchScore += 25 // Low score for title match
                }
            }

            // Keep track of best match
            if matchScore > 0 {
                if bestMatchWindow == nil || matchScore > bestMatchWindow?.score ?? 0 {
                    bestMatchWindow = (window, matchScore)
                    logger.debug("Window \(index) is new best match with score: \(matchScore)")
                }
            }

            // Try the improved approach: get tab group first
            if let tabGroup = getTabGroup(from: window) {
                // Get tabs from the tab group
                if let tabs = tabGroup.tabs,
                   !tabs.isEmpty
                {
                    logger.info("Window \(index) has tab group with \(tabs.count) tabs")

                    // Try to find matching tab
                    if windowMatcher.findMatchingTab(tabs: tabs, sessionInfo: sessionInfo) != nil {
                        // Found the tab! Focus the window and select the tab
                        logger.info("Found matching tab in window \(index)")

                        // Show highlight effect
                        highlightEffect.highlightWindow(window, bounds: window.frame())

                        // Make window main and focused
                        window.setMain(true)
                        window.setFocused(true)

                        // Select the tab
                        selectTab(tabs: tabs, windowInfo: windowInfo, sessionInfo: sessionInfo)

                        return
                    }
                }
            } else {
                // Fallback: Try direct tabs attribute (older approach)
                if let tabs = window.tabs,
                   !tabs.isEmpty
                {
                    logger.info("Window \(index) has \(tabs.count) tabs (direct attribute)")

                    // Try to find matching tab
                    if windowMatcher.findMatchingTab(tabs: tabs, sessionInfo: sessionInfo) != nil {
                        // Found the tab! Focus the window and select the tab
                        logger.info("Found matching tab in window \(index)")

                        // Show highlight effect
                        highlightEffect.highlightWindow(window, bounds: window.frame())

                        // Make window main and focused
                        window.setMain(true)
                        window.setFocused(true)

                        // Select the tab
                        selectTab(tabs: tabs, windowInfo: windowInfo, sessionInfo: sessionInfo)

                        return
                    }
                }
            }
        }

        // After checking all windows, use the best match if we found one
        if let bestMatch = bestMatchWindow {
            logger.info("Using best match window with score \(bestMatch.score) for window ID \(windowInfo.windowID)")

            // Show highlight effect
            highlightEffect.highlightWindow(bestMatch.window, bounds: bestMatch.window.frame())

            // Focus the best matching window
            bestMatch.window.setMain(true)
            bestMatch.window.setFocused(true)

            // Try to select tab if available
            if sessionInfo != nil {
                // Try to get tabs and select the right one
                if let tabGroup = getTabGroup(from: bestMatch.window) {
                    if let tabs = tabGroup.tabs,
                       !tabs.isEmpty
                    {
                        selectTab(tabs: tabs, windowInfo: windowInfo, sessionInfo: sessionInfo)
                    }
                } else {
                    // Try direct tabs attribute
                    if let tabs = bestMatch.window.tabs,
                       !tabs.isEmpty
                    {
                        selectTab(tabs: tabs, windowInfo: windowInfo, sessionInfo: sessionInfo)
                    }
                }
            }

            logger.info("Focused best match window for session \(windowInfo.sessionID)")
        } else {
            // No match found at all - log error but don't focus random window
            logger
                .error(
                    "Failed to find window with ID \(windowInfo.windowID) for session \(windowInfo.sessionID). No windows matched by ID, position, or title."
                )
        }
    }
}
