import Foundation
import Testing

@testable import NucleicCore

@Suite("Host-command conflict detection")
struct HostCommandConflictTests {
    private func sim(_ a: String, _ b: String) -> Bool {
        HostCommandConflictDetector.similarity(a, b) != nil
    }

    // MARK: - similarity()

    @Test func identicalCommandsConflict() {
        #expect(sim("swift build", "swift build"))
    }

    @Test func normalizesWhitespaceAndCase() {
        // Trivial formatting/case differences must not defeat the match.
        #expect(sim("SWIFT   build", "swift build"))
        #expect(sim("  make  ", "make"))
    }

    @Test func anyTwoBuildsConflict() {
        // Within a project, ANY two builds contend (same or different toolchain): they share the
        // toolchain and, for nvrsion sessions, the same trunk build directory. Project scoping lives
        // in `conflicts`; at the command level every build pair matches.
        #expect(sim("swift build", "swift test"))
        #expect(sim("make", "make test"))
        #expect(sim("swift build", "swift build && swift test"))
        #expect(sim("swift build", "npm run build"))
        #expect(sim("cargo build", "go build"))
    }

    @Test func nearIdenticalNonBuildCommandsConflict() {
        // Outside the modelled build category, only a near-identical command (same op, minor
        // arg/flag differences) counts — caught by the token-set Jaccard net.
        #expect(sim("python run.py --fast", "python run.py"))
    }

    @Test func unrelatedReadsDoNotConflict() {
        #expect(!sim("cat a.txt", "cat b.txt"))
        #expect(!sim("ls src", "grep TODO src"))
    }

    @Test func emptyCommandsNeverConflict() {
        #expect(!sim("", "swift build"))
        #expect(!sim("   ", "   "))
    }

    // MARK: - conflicts()

    private let projectP = ProjectID(rawValue: "p")

    private func running(_ id: String, _ command: String, project: String = "p") -> RunningHostCommand {
        RunningHostCommand(
            sessionID: SessionID(rawValue: id), projectID: ProjectID(rawValue: project),
            title: "Session \(id)", command: command, startedAt: Date(timeIntervalSince1970: 0))
    }

    @Test func conflictsExcludesOwnSession() {
        // A session never conflicts with its own running entry.
        let running = [running("self", "swift build")]
        let hits = HostCommandConflictDetector.conflicts(
            command: "swift build", sessionID: SessionID(rawValue: "self"), projectID: projectP,
            against: running)
        #expect(hits.isEmpty)
    }

    @Test func conflictsFindsSimilarRunningCommandAndNamesSession() {
        let running = [
            running("a", "swift build"),
            running("b", "cat notes.md"),
            running("c", "go build"),  // different toolchain, same project → still a build conflict
        ]
        let hits = HostCommandConflictDetector.conflicts(
            command: "swift build", sessionID: SessionID(rawValue: "me"), projectID: projectP,
            against: running)
        let ids = Set(hits.map(\.sessionID))
        #expect(ids == [SessionID(rawValue: "a"), SessionID(rawValue: "c")])
        // Each conflict carries the other session's title + command for the in-chat prompt.
        let a = hits.first { $0.sessionID == SessionID(rawValue: "a") }
        #expect(a?.sessionTitle == "Session a")
        #expect(a?.command == "swift build")
        #expect(a?.reason.isEmpty == false)
    }

    @Test func conflictsScopedToSameProject() {
        // A build in another project shares only host CPU, which we tolerate — not a conflict.
        let running = [running("other", "swift build", project: "q")]
        let hits = HostCommandConflictDetector.conflicts(
            command: "swift build", sessionID: SessionID(rawValue: "me"), projectID: projectP,
            against: running)
        #expect(hits.isEmpty)
    }

    @Test func conflictsEmptyWhenNothingSimilarRuns() {
        let running = [running("a", "cat a.txt"), running("b", "ls src")]
        let hits = HostCommandConflictDetector.conflicts(
            command: "swift build", sessionID: SessionID(rawValue: "me"), projectID: projectP,
            against: running)
        #expect(hits.isEmpty)
    }
}
