import CoreGraphics
import Foundation
import Testing
@testable import CodexBar

struct MenuBarVisibilityWatcherTests {
    @Test
    func `does not flag intentionally hidden status item`() {
        let snapshot = StatusItemVisibilitySnapshot(
            isVisible: false,
            hasButton: true,
            hasWindow: false,
            hasScreen: false,
            buttonWidth: 0)

        #expect(!MenuBarVisibilityWatcher.isBlockedSnapshot(snapshot: snapshot))
    }

    @Test
    func `flags visible item without attached window`() {
        let snapshot = StatusItemVisibilitySnapshot(
            isVisible: true,
            hasButton: true,
            hasWindow: false,
            hasScreen: false,
            buttonWidth: 18)

        #expect(MenuBarVisibilityWatcher.isBlockedSnapshot(snapshot: snapshot))
    }

    @Test
    func `flags visible item without button`() {
        let snapshot = StatusItemVisibilitySnapshot(
            isVisible: true,
            hasButton: false,
            hasWindow: false,
            hasScreen: false,
            buttonWidth: 0)

        #expect(MenuBarVisibilityWatcher.isBlockedSnapshot(snapshot: snapshot))
    }

    @Test
    func `flags visible item with zero width`() {
        let snapshot = StatusItemVisibilitySnapshot(
            isVisible: true,
            hasButton: true,
            hasWindow: true,
            hasScreen: true,
            buttonWidth: 0)

        #expect(MenuBarVisibilityWatcher.isBlockedSnapshot(snapshot: snapshot))
    }

    @Test
    func `allows visible item attached to a screen with width`() {
        let snapshot = StatusItemVisibilitySnapshot(
            isVisible: true,
            hasButton: true,
            hasWindow: true,
            hasScreen: true,
            buttonWidth: 18)

        #expect(!MenuBarVisibilityWatcher.isBlockedSnapshot(snapshot: snapshot))
    }

    @Test
    func `window probe matches autosave name and reports display bounds`() {
        let snapshots = MenuBarStatusItemWindowProbe.snapshots(
            matching: ["codexbar-merged"],
            windowInfo: [[
                kCGWindowName as String: "codexbar-merged",
                kCGWindowOwnerName as String: "Control Center",
                kCGWindowIsOnscreen as String: true,
                kCGWindowBounds as String: [
                    "X": 1680,
                    "Y": 0,
                    "Width": 70,
                    "Height": 24,
                ],
            ]],
            displayBounds: [CGRect(x: 0, y: 0, width: 2056, height: 1329)])

        #expect(snapshots.count == 1)
        #expect(snapshots.first?.name == "codexbar-merged")
        #expect(snapshots.first?.ownerName == "Control Center")
        #expect(snapshots.first?.isOnscreen == true)
        #expect(snapshots.first?.isWithinDisplayBounds == true)
    }

    @Test
    func `window probe detects offscreen status item by bounds`() {
        let snapshots = MenuBarStatusItemWindowProbe.snapshots(
            matching: ["codexbar-merged"],
            windowInfo: [[
                kCGWindowName as String: "codexbar-merged",
                kCGWindowOwnerName as String: "Control Center",
                kCGWindowIsOnscreen as String: true,
                kCGWindowBounds as String: [
                    "X": 2023,
                    "Y": 0,
                    "Width": 71,
                    "Height": 24,
                ],
            ]],
            displayBounds: [CGRect(x: 0, y: 0, width: 2056, height: 1329)])

        #expect(snapshots.count == 1)
        #expect(snapshots.first?.isOnscreen == true)
        #expect(snapshots.first?.isWithinDisplayBounds == false)
    }

    @Test
    func `window probe identifies Tahoe Control Center blocked proxy geometry`() {
        let snapshot = MenuBarStatusItemWindowSnapshot(
            name: "codexbar-merged",
            ownerName: "Control Center",
            bounds: CGRect(x: 0, y: -22, width: 76, height: 22),
            isOnscreen: true,
            displayBounds: nil)

        #expect(snapshot.isTahoeBlockedProxy)
    }

    @Test
    func `window probe does not classify generic offscreen manager placement as Tahoe proxy`() {
        let snapshot = MenuBarStatusItemWindowSnapshot(
            name: "codexbar-merged",
            ownerName: "Control Center",
            bounds: CGRect(x: 2023, y: 0, width: 71, height: 24),
            isOnscreen: true,
            displayBounds: nil)

        #expect(!snapshot.isTahoeBlockedProxy)
    }

    @Test
    func `window probe does not classify stale hidden Control Center record as Tahoe proxy`() {
        let snapshot = MenuBarStatusItemWindowSnapshot(
            name: "codexbar-merged",
            ownerName: "Control Center",
            bounds: CGRect(x: 0, y: -22, width: 76, height: 22),
            isOnscreen: false,
            displayBounds: nil)

        #expect(!snapshot.isTahoeBlockedProxy)
    }

    @Test
    func `allows visible item attached to a detached screen`() {
        let snapshot = StatusItemVisibilitySnapshot(
            isVisible: true,
            hasButton: true,
            hasWindow: true,
            hasScreen: true,
            isOnCurrentScreen: false,
            buttonWidth: 18)

        #expect(!MenuBarVisibilityWatcher.isBlockedSnapshot(snapshot: snapshot))
    }

    @Test
    func `classifies detached live item as displaced but not blocked`() {
        let snapshot = StatusItemVisibilitySnapshot(
            isVisible: true,
            hasButton: true,
            hasWindow: true,
            hasScreen: false,
            isOnCurrentScreen: false,
            buttonWidth: 18)

        #expect(!MenuBarVisibilityWatcher.isBlockedSnapshot(snapshot: snapshot))
        #expect(MenuBarVisibilityWatcher.isDisplacedSnapshot(snapshot: snapshot))
    }

    @Test
    func `classifies stale screen live item as displaced but not blocked`() {
        let snapshot = StatusItemVisibilitySnapshot(
            isVisible: true,
            hasButton: true,
            hasWindow: true,
            hasScreen: true,
            isOnCurrentScreen: false,
            buttonWidth: 18)

        #expect(!MenuBarVisibilityWatcher.isBlockedSnapshot(snapshot: snapshot))
        #expect(MenuBarVisibilityWatcher.isDisplacedSnapshot(snapshot: snapshot))
    }

    @Test
    func `guidance shows once then repeats after a day`() throws {
        let defaults = try #require(UserDefaults(suiteName: "MenuBarVisibilityWatcherTests"))
        defaults.removePersistentDomain(forName: "MenuBarVisibilityWatcherTests")
        let now = Date(timeIntervalSince1970: 1000)

        #expect(MenuBarVisibilityWatcher.shouldShowGuidance(defaults: defaults, now: now))

        MenuBarVisibilityWatcher.markGuidanceShown(defaults: defaults, now: now)

        #expect(!MenuBarVisibilityWatcher.shouldShowGuidance(
            defaults: defaults,
            now: now.addingTimeInterval(MenuBarVisibilityWatcher.guidanceRepeatInterval - 1)))
        #expect(MenuBarVisibilityWatcher.shouldShowGuidance(
            defaults: defaults,
            now: now.addingTimeInterval(MenuBarVisibilityWatcher.guidanceRepeatInterval)))
    }

    @Test
    func `startup recovery triggers for blocked visible snapshot`() {
        let launchedAt = Date(timeIntervalSince1970: 1000)
        let blocked = StatusItemVisibilitySnapshot(
            isVisible: true,
            hasButton: true,
            hasWindow: false,
            hasScreen: false,
            buttonWidth: 18)

        #expect(MenuBarVisibilityWatcher.shouldAttemptStartupRecovery(
            appLaunchedAt: launchedAt,
            now: launchedAt.addingTimeInterval(2),
            snapshots: [blocked]))
    }

    @Test
    func `startup recovery retries detached Tahoe proxy corroborated by Control Center geometry`() {
        let launchedAt = Date(timeIntervalSince1970: 1000)
        let detachedProxy = StatusItemVisibilitySnapshot(
            isVisible: true,
            hasButton: true,
            hasWindow: true,
            hasScreen: false,
            isOnCurrentScreen: false,
            buttonWidth: 76)
        let blockedWindow = MenuBarStatusItemWindowSnapshot(
            name: "codexbar-merged",
            ownerName: "Control Center",
            bounds: CGRect(x: 0, y: -22, width: 76, height: 22),
            isOnscreen: true,
            displayBounds: nil)

        #expect(!MenuBarVisibilityWatcher.isBlockedSnapshot(snapshot: detachedProxy))
        #expect(MenuBarVisibilityWatcher.shouldAttemptStartupRecovery(
            appLaunchedAt: launchedAt,
            now: launchedAt.addingTimeInterval(2),
            snapshots: [detachedProxy],
            windowSnapshots: [blockedWindow],
            detectTahoeBlockedStatusItem: true))
    }

    @Test
    func `startup recovery retries expected hidden Tahoe item with enabled default and no window`() {
        let launchedAt = Date(timeIntervalSince1970: 1000)
        let hidden = StatusItemVisibilitySnapshot(
            isVisible: false,
            hasButton: true,
            hasWindow: false,
            hasScreen: false,
            buttonWidth: 76)
        let evidence = StatusItemStartupVisibilityEvidence(
            autosaveName: "codexbar-merged",
            expectsVisibility: true,
            visibilityDefault: true,
            snapshot: hidden)

        #expect(MenuBarVisibilityWatcher.shouldAttemptStartupRecovery(
            appLaunchedAt: launchedAt,
            now: launchedAt.addingTimeInterval(2),
            snapshots: [hidden],
            evidence: [evidence],
            detectTahoeBlockedStatusItem: true))
    }

    @Test
    func `startup recovery ignores hidden Tahoe item without app and defaults visibility agreement`() {
        let launchedAt = Date(timeIntervalSince1970: 1000)
        let hidden = StatusItemVisibilitySnapshot(
            isVisible: false,
            hasButton: true,
            hasWindow: false,
            hasScreen: false,
            buttonWidth: 76)
        let intentionallyHidden = StatusItemStartupVisibilityEvidence(
            autosaveName: "codexbar-merged",
            expectsVisibility: false,
            visibilityDefault: true,
            snapshot: hidden)
        let disabledByUser = StatusItemStartupVisibilityEvidence(
            autosaveName: "codexbar-merged",
            expectsVisibility: true,
            visibilityDefault: false,
            snapshot: hidden)
        let unknownDefault = StatusItemStartupVisibilityEvidence(
            autosaveName: "codexbar-merged",
            expectsVisibility: true,
            visibilityDefault: nil,
            snapshot: hidden)

        for evidence in [intentionallyHidden, disabledByUser, unknownDefault] {
            #expect(!MenuBarVisibilityWatcher.shouldAttemptStartupRecovery(
                appLaunchedAt: launchedAt,
                now: launchedAt.addingTimeInterval(2),
                snapshots: [hidden],
                evidence: [evidence],
                detectTahoeBlockedStatusItem: true))
        }
    }

    @Test
    func `startup recovery ignores hidden item when matching window still exists`() {
        let launchedAt = Date(timeIntervalSince1970: 1000)
        let hidden = StatusItemVisibilitySnapshot(
            isVisible: false,
            hasButton: true,
            hasWindow: false,
            hasScreen: false,
            buttonWidth: 76)
        let evidence = StatusItemStartupVisibilityEvidence(
            autosaveName: "codexbar-merged",
            expectsVisibility: true,
            visibilityDefault: true,
            snapshot: hidden)
        let existingWindow = MenuBarStatusItemWindowSnapshot(
            name: "codexbar-merged",
            ownerName: "Control Center",
            bounds: CGRect(x: 1500, y: 0, width: 76, height: 24),
            isOnscreen: true,
            displayBounds: CGRect(x: 0, y: 0, width: 2056, height: 1329))

        #expect(!MenuBarVisibilityWatcher.shouldAttemptStartupRecovery(
            appLaunchedAt: launchedAt,
            now: launchedAt.addingTimeInterval(2),
            snapshots: [hidden],
            evidence: [evidence],
            windowSnapshots: [existingWindow],
            detectTahoeBlockedStatusItem: true))
    }

    @Test
    func `startup recovery ignores stale hidden matching window record`() {
        let launchedAt = Date(timeIntervalSince1970: 1000)
        let hidden = StatusItemVisibilitySnapshot(
            isVisible: false,
            hasButton: true,
            hasWindow: false,
            hasScreen: false,
            buttonWidth: 76)
        let evidence = StatusItemStartupVisibilityEvidence(
            autosaveName: "codexbar-merged",
            expectsVisibility: true,
            visibilityDefault: true,
            snapshot: hidden)
        let staleWindow = MenuBarStatusItemWindowSnapshot(
            name: "codexbar-merged",
            ownerName: "Control Center",
            bounds: CGRect(x: 1500, y: 0, width: 76, height: 24),
            isOnscreen: false,
            displayBounds: CGRect(x: 0, y: 0, width: 2056, height: 1329))

        #expect(MenuBarVisibilityWatcher.shouldAttemptStartupRecovery(
            appLaunchedAt: launchedAt,
            now: launchedAt.addingTimeInterval(2),
            snapshots: [hidden],
            evidence: [evidence],
            windowSnapshots: [staleWindow],
            detectTahoeBlockedStatusItem: true))
    }

    @Test
    func `startup recovery keeps hidden no-window detection Tahoe only`() {
        let launchedAt = Date(timeIntervalSince1970: 1000)
        let hidden = StatusItemVisibilitySnapshot(
            isVisible: false,
            hasButton: true,
            hasWindow: false,
            hasScreen: false,
            buttonWidth: 76)
        let evidence = StatusItemStartupVisibilityEvidence(
            autosaveName: "codexbar-merged",
            expectsVisibility: true,
            visibilityDefault: true,
            snapshot: hidden)

        #expect(!MenuBarVisibilityWatcher.shouldAttemptStartupRecovery(
            appLaunchedAt: launchedAt,
            now: launchedAt.addingTimeInterval(2),
            snapshots: [hidden],
            evidence: [evidence]))
    }

    @Test
    func `startup recovery ignores detached live item without Tahoe proxy corroboration`() {
        let launchedAt = Date(timeIntervalSince1970: 1000)
        let managed = StatusItemVisibilitySnapshot(
            isVisible: true,
            hasButton: true,
            hasWindow: true,
            hasScreen: false,
            isOnCurrentScreen: false,
            buttonWidth: 18)

        #expect(!MenuBarVisibilityWatcher.shouldAttemptStartupRecovery(
            appLaunchedAt: launchedAt,
            now: launchedAt.addingTimeInterval(2),
            snapshots: [managed],
            detectTahoeBlockedStatusItem: true))
    }

    @Test
    func `startup recovery ignores live item attached to a stale screen`() {
        let launchedAt = Date(timeIntervalSince1970: 1000)
        let managed = StatusItemVisibilitySnapshot(
            isVisible: true,
            hasButton: true,
            hasWindow: true,
            hasScreen: true,
            isOnCurrentScreen: false,
            buttonWidth: 18)

        #expect(!MenuBarVisibilityWatcher.shouldAttemptStartupRecovery(
            appLaunchedAt: launchedAt,
            now: launchedAt.addingTimeInterval(2),
            snapshots: [managed]))
    }

    @Test
    func `startup recovery triggers when one split status item is blocked`() {
        let launchedAt = Date(timeIntervalSince1970: 1000)
        let healthy = StatusItemVisibilitySnapshot(
            isVisible: true,
            hasButton: true,
            hasWindow: true,
            hasScreen: true,
            buttonWidth: 18)
        let blocked = StatusItemVisibilitySnapshot(
            isVisible: true,
            hasButton: true,
            hasWindow: false,
            hasScreen: false,
            buttonWidth: 18)

        #expect(MenuBarVisibilityWatcher.shouldAttemptStartupRecovery(
            appLaunchedAt: launchedAt,
            now: launchedAt.addingTimeInterval(2),
            snapshots: [healthy, blocked]))
    }

    @Test
    func `startup recovery ignores stale checks`() {
        let launchedAt = Date(timeIntervalSince1970: 1000)
        let blocked = StatusItemVisibilitySnapshot(
            isVisible: true,
            hasButton: true,
            hasWindow: false,
            hasScreen: false,
            buttonWidth: 18)

        #expect(!MenuBarVisibilityWatcher.shouldAttemptStartupRecovery(
            appLaunchedAt: launchedAt,
            now: launchedAt.addingTimeInterval(MenuBarVisibilityWatcher.startupFreshnessInterval + 1),
            snapshots: [blocked]))
    }

    @Test
    func `startup recovery ignores healthy visible snapshot`() {
        let launchedAt = Date(timeIntervalSince1970: 1000)
        let healthy = StatusItemVisibilitySnapshot(
            isVisible: true,
            hasButton: true,
            hasWindow: true,
            hasScreen: true,
            buttonWidth: 18)

        #expect(!MenuBarVisibilityWatcher.shouldAttemptStartupRecovery(
            appLaunchedAt: launchedAt,
            now: launchedAt.addingTimeInterval(2),
            snapshots: [healthy]))
    }

    @Test
    func `screen change placement refresh ignores display removal with healthy status item`() {
        let healthy = StatusItemVisibilitySnapshot(
            isVisible: true,
            hasButton: true,
            hasWindow: true,
            hasScreen: true,
            buttonWidth: 18)

        #expect(!MenuBarVisibilityWatcher.shouldRefreshScreenChangePlacement(
            previousScreenCount: 2,
            currentScreenCount: 1,
            snapshots: [healthy]))
    }

    @Test
    func `screen change placement refresh ignores display removal when no status item is visible`() {
        let hidden = StatusItemVisibilitySnapshot(
            isVisible: false,
            hasButton: true,
            hasWindow: true,
            hasScreen: true,
            buttonWidth: 18)

        #expect(!MenuBarVisibilityWatcher.shouldRefreshScreenChangePlacement(
            previousScreenCount: 2,
            currentScreenCount: 1,
            snapshots: [hidden]))
    }

    @Test
    func `screen change recovery triggers for blocked status item without display count change`() {
        let blocked = StatusItemVisibilitySnapshot(
            isVisible: true,
            hasButton: true,
            hasWindow: false,
            hasScreen: false,
            buttonWidth: 18)

        #expect(MenuBarVisibilityWatcher.shouldAttemptScreenChangeRecovery(snapshots: [blocked]))
    }

    @Test
    func `screen change placement refresh triggers for detached live item after display removal`() {
        let displaced = StatusItemVisibilitySnapshot(
            isVisible: true,
            hasButton: true,
            hasWindow: true,
            hasScreen: false,
            isOnCurrentScreen: false,
            buttonWidth: 18)

        #expect(MenuBarVisibilityWatcher.shouldRefreshScreenChangePlacement(
            previousScreenCount: 2,
            currentScreenCount: 1,
            snapshots: [displaced]))
    }

    @Test
    func `screen change placement refresh triggers for stale screen live item after display removal`() {
        let displaced = StatusItemVisibilitySnapshot(
            isVisible: true,
            hasButton: true,
            hasWindow: true,
            hasScreen: true,
            isOnCurrentScreen: false,
            buttonWidth: 18)

        #expect(MenuBarVisibilityWatcher.shouldRefreshScreenChangePlacement(
            previousScreenCount: 2,
            currentScreenCount: 1,
            snapshots: [displaced]))
    }

    @Test
    func `screen change placement refresh ignores healthy item when display count does not shrink`() {
        let healthy = StatusItemVisibilitySnapshot(
            isVisible: true,
            hasButton: true,
            hasWindow: true,
            hasScreen: true,
            buttonWidth: 18)

        #expect(!MenuBarVisibilityWatcher.shouldRefreshScreenChangePlacement(
            previousScreenCount: 1,
            currentScreenCount: 2,
            snapshots: [healthy]))
    }

    @Test
    func `screen change placement refresh triggers for displaced live item when display count is unchanged`() {
        let displaced = StatusItemVisibilitySnapshot(
            isVisible: true,
            hasButton: true,
            hasWindow: true,
            hasScreen: true,
            isOnCurrentScreen: false,
            buttonWidth: 18)

        #expect(MenuBarVisibilityWatcher.shouldRefreshScreenChangePlacement(
            previousScreenCount: 2,
            currentScreenCount: 2,
            snapshots: [displaced]))
    }

    @Test
    func `manager parked item with live window is not blocked`() {
        // A menu bar manager parks items off the active screen with the window intact.
        // hasAnyBlockedVisibleSnapshot must return false so verifyScreenChangeRecoveryIfNeeded
        // does not trigger repeated recreation that corrupts Control Center.
        let managed = StatusItemVisibilitySnapshot(
            isVisible: true,
            hasButton: true,
            hasWindow: true,
            hasScreen: false,
            isOnCurrentScreen: false,
            buttonWidth: 18)

        #expect(!MenuBarVisibilityWatcher.hasAnyBlockedVisibleSnapshot([managed]))
        #expect(MenuBarVisibilityWatcher.hasAnyDisplacedVisibleSnapshot([managed]))
    }

    @Test
    func `manager parked item with live window on stale screen is not blocked`() {
        let managed = StatusItemVisibilitySnapshot(
            isVisible: true,
            hasButton: true,
            hasWindow: true,
            hasScreen: true,
            isOnCurrentScreen: false,
            buttonWidth: 18)

        #expect(!MenuBarVisibilityWatcher.hasAnyBlockedVisibleSnapshot([managed]))
        #expect(MenuBarVisibilityWatcher.hasAnyDisplacedVisibleSnapshot([managed]))
    }

    @Test
    func `item without window is blocked regardless of screen state`() {
        // A missing window cannot be caused by a manager parking the item; it signals
        // a genuine system block and must trigger recovery.
        let blocked = StatusItemVisibilitySnapshot(
            isVisible: true,
            hasButton: true,
            hasWindow: false,
            hasScreen: false,
            isOnCurrentScreen: false,
            buttonWidth: 18)

        #expect(MenuBarVisibilityWatcher.hasAnyBlockedVisibleSnapshot([blocked]))
        #expect(!MenuBarVisibilityWatcher.hasAnyDisplacedVisibleSnapshot([blocked]))
    }
}
