import XCTest
@testable import CommandNotch

@MainActor
final class WorkspaceRegistryTests: XCTestCase {
    func testRegistryCreatesDefaultWorkspaceWhenEmpty() {
        let registry = makeRegistry()

        XCTAssertEqual(registry.allWorkspaceSummaries().count, 1)
        XCTAssertEqual(registry.allWorkspaceSummaries().first?.name, "Main")
        XCTAssertNotNil(registry.controller(for: registry.defaultWorkspaceID))
    }

    func testCreateRenameAndDeleteWorkspaceUpdatesSummaries() {
        let registry = makeRegistry()
        let createdID = registry.createWorkspace(named: "Docs")

        XCTAssertEqual(registry.allWorkspaceSummaries().map(\.name), ["Main", "Docs"])

        registry.renameWorkspace(id: createdID, to: "Review")
        XCTAssertEqual(registry.allWorkspaceSummaries().map(\.name), ["Main", "Review"])

        registry.deleteWorkspace(id: createdID)
        XCTAssertEqual(registry.allWorkspaceSummaries().map(\.name), ["Main"])
    }

    func testDeletingLastWorkspaceIsIgnored() {
        let registry = makeRegistry()
        let onlyWorkspaceID = registry.defaultWorkspaceID

        registry.deleteWorkspace(id: onlyWorkspaceID)

        XCTAssertEqual(registry.allWorkspaceSummaries().count, 1)
        XCTAssertEqual(registry.allWorkspaceSummaries().first?.id, onlyWorkspaceID)
    }

    func testDeletionFallbackPrefersRequestedWorkspaceWhenAvailable() {
        let registry = makeRegistry()
        let docsID = registry.createWorkspace(named: "Docs")
        let reviewID = registry.createWorkspace(named: "Review")

        let fallback = registry.deletionFallbackWorkspaceID(
            forDeleting: docsID,
            preferredFallback: reviewID
        )

        XCTAssertEqual(fallback, reviewID)
    }

    func testRegistryLoadsPersistedWorkspacesFromStore() {
        let store = InMemoryWorkspaceStore()
        let docsID = UUID(uuidString: "11111111-1111-1111-1111-111111111111")!
        store.savedSummaries = [WorkspaceSummary(id: docsID, name: "Docs")]

        let registry = makeRegistry(initialWorkspaces: nil, store: store)

        XCTAssertEqual(registry.allWorkspaceSummaries().count, 1)
        XCTAssertEqual(registry.allWorkspaceSummaries().first?.id, docsID)
        XCTAssertEqual(registry.allWorkspaceSummaries().first?.name, "Docs")
        XCTAssertEqual(registry.defaultWorkspaceID, docsID)
    }

    func testRegistryPersistsWorkspaceSummaryChanges() {
        let store = InMemoryWorkspaceStore()
        let registry = makeRegistry(store: store)
        let createdID = registry.createWorkspace(named: "Docs")

        XCTAssertEqual(store.savedSummaries.map(\.name), ["Main", "Docs"])

        registry.renameWorkspace(id: createdID, to: "Review")
        XCTAssertEqual(store.savedSummaries.map(\.name), ["Main", "Review"])

        registry.deleteWorkspace(id: createdID)
        XCTAssertEqual(store.savedSummaries.map(\.name), ["Main"])
    }

    func testUpdateWorkspaceHotkeyPersistsAndUpdatesSummary() {
        let store = InMemoryWorkspaceStore()
        let registry = makeRegistry(store: store)
        let docsID = registry.createWorkspace(named: "Docs")
        let hotkey = HotkeyBinding.cmdShiftDigit(4)

        registry.updateWorkspaceHotkey(id: docsID, to: hotkey)

        XCTAssertEqual(registry.summary(for: docsID)?.hotkey, hotkey)
        XCTAssertEqual(store.savedSummaries.last?.hotkey, hotkey)
    }

    func testNextAndPreviousWorkspaceWrapAroundRegistryOrder() {
        let registry = makeRegistry()
        let mainID = registry.defaultWorkspaceID
        let docsID = registry.createWorkspace(named: "Docs")
        let reviewID = registry.createWorkspace(named: "Review")

        XCTAssertEqual(registry.nextWorkspaceID(after: mainID), docsID)
        XCTAssertEqual(registry.nextWorkspaceID(after: reviewID), mainID)
        XCTAssertEqual(registry.previousWorkspaceID(before: mainID), reviewID)
        XCTAssertEqual(registry.previousWorkspaceID(before: docsID), mainID)
    }

    private func makeRegistry(
        initialWorkspaces: [WorkspaceSummary]? = [],
        store: (any WorkspaceStoreType)? = nil
    ) -> WorkspaceRegistry {
        let settingsProvider = TestSettingsProvider()
        let sessionFactory = UnusedTerminalSessionFactory()

        return WorkspaceRegistry(
            initialWorkspaces: initialWorkspaces,
            store: store,
            controllerFactory: { summary in
                WorkspaceController(
                    summary: summary,
                    sessionFactory: sessionFactory,
                    settingsProvider: settingsProvider,
                    bootstrapDefaultTab: false
                )
            }
        )
    }
}

@MainActor
final class WorkspaceControllerTests: XCTestCase {
    func testNewTabUsesActiveTabCurrentDirectory() {
        let factory = RecordingTerminalSessionFactory()
        let controller = WorkspaceController(
            summary: WorkspaceSummary(name: "Main"),
            sessionFactory: factory,
            settingsProvider: TestSettingsProvider(),
            bootstrapDefaultTab: false
        )

        controller.newTab()
        controller.activeTab?.currentDirectory = "/tmp/Raycast"

        controller.newTab()

        XCTAssertEqual(factory.requestedDirectories, [nil, "/tmp/Raycast"])
        XCTAssertEqual(controller.activeTab?.currentDirectory, "/tmp/Raycast")
        XCTAssertEqual(controller.tabs.count, 2)
        XCTAssertEqual(controller.activeTabIndex, 1)
    }

    func testNewTabNormalizesCurrentDirectoryFileURL() {
        let factory = RecordingTerminalSessionFactory()
        let controller = WorkspaceController(
            summary: WorkspaceSummary(name: "Main"),
            sessionFactory: factory,
            settingsProvider: TestSettingsProvider(),
            bootstrapDefaultTab: false
        )
        let expectedPath = "/tmp/Raycast Folder"

        controller.newTab()
        controller.activeTab?.currentDirectory = URL(fileURLWithPath: expectedPath).absoluteString

        controller.newTab()

        XCTAssertEqual(controller.activeTab?.currentDirectory, expectedPath)
    }

    func testNewTabFallsBackToHomeDirectoryWhenWorkspaceHasNoTabs() {
        let factory = RecordingTerminalSessionFactory()
        let controller = WorkspaceController(
            summary: WorkspaceSummary(name: "Main"),
            sessionFactory: factory,
            settingsProvider: TestSettingsProvider(),
            bootstrapDefaultTab: false
        )

        controller.newTab()

        XCTAssertEqual(factory.requestedDirectories, [nil])
        XCTAssertEqual(controller.activeTab?.currentDirectory, NSHomeDirectory())
        XCTAssertEqual(controller.tabs.count, 1)
        XCTAssertEqual(controller.activeTabIndex, 0)
    }
}

private final class InMemoryWorkspaceStore: WorkspaceStoreType {
    var savedSummaries: [WorkspaceSummary] = []

    func loadWorkspaceSummaries() -> [WorkspaceSummary] {
        savedSummaries
    }

    func saveWorkspaceSummaries(_ summaries: [WorkspaceSummary]) {
        savedSummaries = summaries
    }
}

private final class TestSettingsProvider: TerminalSessionConfigurationProviding {
    let terminalSessionConfiguration = TerminalSessionConfiguration(fontSize: 13, theme: .terminalApp, shellPath: "")
    let hotkeySettings = AppSettings.default.hotkeys
    let terminalSizePresets = TerminalSizePresetStore.loadDefaults()
}

private final class RecordingTerminalSessionFactory: TerminalSessionFactoryType {
    private(set) var requestedDirectories: [String?] = []

    @MainActor
    func makeSession(
        fontSize: CGFloat,
        theme: TerminalTheme,
        shellPath: String,
        initialDirectory: String?
    ) -> TerminalSession {
        requestedDirectories.append(initialDirectory)
        return TerminalSession(
            fontSize: fontSize,
            theme: theme,
            shellPath: shellPath,
            initialDirectory: initialDirectory,
            startImmediately: false
        )
    }
}

private struct UnusedTerminalSessionFactory: TerminalSessionFactoryType {
    @MainActor
    func makeSession(
        fontSize: CGFloat,
        theme: TerminalTheme,
        shellPath: String,
        initialDirectory: String?
    ) -> TerminalSession {
        fatalError("WorkspaceRegistryTests should not create live terminal sessions.")
    }
}
