import Foundation
import Testing

@testable import NucleicCore

/// The container-service master switch is subordinate to Nucleic Control: a Control project pins
/// it on (and locks the toggle), and a fresh install adopts leftover controlled repos on disk.
/// These exercise `ProjectCloner.controlledRemnants()` and `AppStore.reconcileContainerService()`.
/// Settings isolation comes from `.isolatedContainerSettings` (each test gets its own task-local
/// UserDefaults suite, never the global domain); still serialized because the remnant tests share
/// the real on-disk control base (`ProjectCloner.controlBase`).
@Suite("Container-service reconcile — Control pins the switch", .serialized, .isolatedContainerSettings)
@MainActor
struct ContainerServiceReconcileTests {
    private func makeStore() -> AppStore {
        let database = try! GRDBMetadataStore(path: nil)
        let worktrees = GitWorktreeManager()
        let transcriptsDir = URL(fileURLWithPath: (NSTemporaryDirectory() as NSString)
            .appendingPathComponent("nucleic-cs-tests-\(UUID().uuidString)"))
        return AppStore(database: database, worktrees: worktrees, transcriptsDir: transcriptsDir) { _ in
            ScriptedBackend { _, _ in }
        }
    }

    /// Create a leftover controlled checkout as a *direct* child of the control base (a `.git`
    /// entry one level down — how `createProject` clones them), returning its path. The caller
    /// removes it. Uses a UUID name so it never collides with the user's real control repos.
    private func makeControlRemnant() throws -> String {
        let fm = FileManager.default
        try fm.createDirectory(atPath: ProjectCloner.controlBase, withIntermediateDirectories: true)
        let path = (ProjectCloner.controlBase as NSString)
            .appendingPathComponent("nucleic-remnant-\(UUID().uuidString)")
        try fm.createDirectory(
            atPath: (path as NSString).appendingPathComponent(".git"), withIntermediateDirectories: true)
        return path
    }

    // MARK: - controlledRemnants (pure filesystem scan)

    @Test func controlledRemnantsFindsCheckoutUnderControlBase() throws {
        let remnant = try makeControlRemnant()
        defer { try? FileManager.default.removeItem(atPath: remnant) }

        let found = ProjectCloner.controlledRemnants().map(GitWorktreeManager.canonical)
        #expect(found.contains(GitWorktreeManager.canonical(remnant)))
    }

    @Test func controlledRemnantsIgnoresDirsWithoutGitAndStrayFiles() throws {
        let fm = FileManager.default
        try fm.createDirectory(atPath: ProjectCloner.controlBase, withIntermediateDirectories: true)
        // A directory with no `.git` — not a checkout.
        let notARepo = (ProjectCloner.controlBase as NSString)
            .appendingPathComponent("nucleic-notrepo-\(UUID().uuidString)")
        try fm.createDirectory(atPath: notARepo, withIntermediateDirectories: true)
        // A stray file sitting at the base — not a directory at all.
        let strayFile = (ProjectCloner.controlBase as NSString)
            .appendingPathComponent("nucleic-stray-\(UUID().uuidString)")
        try Data().write(to: URL(fileURLWithPath: strayFile))
        defer {
            try? fm.removeItem(atPath: notARepo)
            try? fm.removeItem(atPath: strayFile)
        }

        let found = ProjectCloner.controlledRemnants().map(GitWorktreeManager.canonical)
        #expect(!found.contains(GitWorktreeManager.canonical(notARepo)))
        #expect(!found.contains(GitWorktreeManager.canonical(strayFile)))
    }

    // MARK: - Lock-on (a registered Control project pins the switch)

    @Test func controlProjectPinsServiceOnAndLocksToggle() async throws {
        // The trait binds an isolated per-test defaults suite; mutate that, never the global domain.
        let defaults = ContainerServiceSettings.defaults
        // Start with the service explicitly off; a Control project must flip and pin it.
        defaults.set(false, forKey: ContainerServiceSettings.serviceEnabledKey)
        // Mark first-launch done so the remnant scan doesn't confound this assertion.
        defaults.set(true, forKey: ContainerServiceSettings.didCompleteFirstLaunchKey)

        let repo = try await GitTestRepo(controlled: true)  // lands under the control base
        defer { repo.cleanup() }
        let store = makeStore()
        let project = try #require(
            await store.addProject(name: "ctl", rootPath: repo.root, defaultBranch: "main"))
        #expect(project.isNucleicControlled)

        // addProject → loadProjects → reconcileContainerService.
        #expect(store.containerServiceLockedOn)
        #expect(ContainerServiceSettings.serviceEnabled)
    }

    @Test func noControlProjectLeavesToggleUnlocked() async throws {
        ContainerServiceSettings.defaults.set(
            true, forKey: ContainerServiceSettings.didCompleteFirstLaunchKey)

        let repo = try await GitTestRepo()  // a plain, non-controlled project (temp dir)
        defer { repo.cleanup() }
        let store = makeStore()
        let project = try #require(
            await store.addProject(name: "plain", rootPath: repo.root, defaultBranch: "main"))
        #expect(!project.isNucleicControlled)

        #expect(!store.containerServiceLockedOn)
    }

    // MARK: - First-install remnant adopt (one-shot)

    @Test func firstInstallAdoptsControlRemnants() async throws {
        // The trait's fresh suite is a genuinely fresh install: switch off, marker unset.
        let defaults = ContainerServiceSettings.defaults

        let remnant = try makeControlRemnant()
        defer { try? FileManager.default.removeItem(atPath: remnant) }

        let store = makeStore()
        await store.loadProjects()  // fresh install: marker unset → scan runs → adopt

        #expect(ContainerServiceSettings.serviceEnabled)
        #expect(defaults.bool(forKey: ContainerServiceSettings.didCompleteFirstLaunchKey))
    }

    @Test func remnantScanRunsOnlyOnFirstInstall() async throws {
        let defaults = ContainerServiceSettings.defaults  // fresh suite: marker unset

        let remnant = try makeControlRemnant()
        let store = makeStore()

        // First launch: remnant present → service adopted on, marker set.
        await store.loadProjects()
        #expect(ContainerServiceSettings.serviceEnabled)

        // The user later turns the service off. Even with a remnant still on disk, a *second*
        // launch must not re-adopt it — the scan is one-shot, gated by the now-set marker. (No
        // registered Control project, so lock-on doesn't re-enable it either.)
        defaults.set(false, forKey: ContainerServiceSettings.serviceEnabledKey)
        try? FileManager.default.removeItem(atPath: remnant)
        await store.loadProjects()
        #expect(!ContainerServiceSettings.serviceEnabled)
    }
}
