import Testing

@testable import NucleicCore

@Suite struct HostCommandSummaryTests {
    /// The motivating case: the exact shape the agent runs to build the app — a `cd` to the repo
    /// root, an env-prefixed `swift build` with paired value-flags. It decomposes into a named
    /// working directory, one `swift build` invocation with its env and flags parsed, and a
    /// command-derived purpose that names the product and channel.
    @Test func realWorldSwiftBuild() {
        let command = #"cd "$(git rev-parse --show-toplevel 2>/dev/null || pwd)" && NUCLEIC_CHANNEL=dev swift build --build-system native --product nucleic-local"#
        let summary = HostCommandSummary.summary(for: command)
        #expect(summary?.workingDirectory == "the repository root")
        #expect(summary?.invocations.count == 1)
        let inv = summary?.invocations.first
        #expect(inv?.program == "swift")
        #expect(inv?.actions == ["build"])
        #expect(inv?.env.map(\.display) == ["NUCLEIC_CHANNEL=dev"])
        #expect(inv?.flags.map(\.display) == ["--build-system native", "--product nucleic-local"])
        #expect(inv?.arguments.isEmpty == true)
        #expect(summary?.purpose == "Build the Swift product `nucleic-local` (dev channel)")
    }

    /// A value-flag written `--flag value` pairs with its value (not read as an operand), and the
    /// filter surfaces in the purpose. A release configuration is called out too.
    @Test func swiftTestAndConfiguration() {
        let test = HostCommandSummary.summary(for: "swift test --filter HostCommandSummaryTests")
        #expect(test?.invocations.first?.actions == ["test"])
        #expect(test?.invocations.first?.flags.map(\.display) == ["--filter HostCommandSummaryTests"])
        #expect(test?.purpose == "Run the Swift test suite (filter: HostCommandSummaryTests)")

        let release = HostCommandSummary.summary(for: "swift build -c release")
        #expect(release?.purpose == "Build the Swift package in release")
    }

    /// make targets are open-ended actions (no fixed vocabulary), so several targets all parse.
    @Test func makeTargets() {
        #expect(HostCommandSummary.summary(for: "make")?.purpose == "Run the default make target")
        let multi = HostCommandSummary.summary(for: "make clean build")
        #expect(multi?.invocations.first?.actions == ["clean", "build"])
        #expect(multi?.purpose == "Run the make targets `clean, build`")
    }

    /// `npm run <script>` keeps `run` as the action and the script name as an operand (not a
    /// second action), and the purpose names the script.
    @Test func npmRunScript() {
        let summary = HostCommandSummary.summary(for: "npm run build")
        #expect(summary?.invocations.first?.program == "npm")
        #expect(summary?.invocations.first?.actions == ["run"])
        #expect(summary?.invocations.first?.arguments == ["build"])
        #expect(summary?.purpose == "Run the `build` npm script")
    }

    /// A git host command is parsed without swallowing the remote/branch operands as actions, and
    /// its purpose reuses the shared git phrasing.
    @Test func gitReusesGitPhrasing() {
        let summary = HostCommandSummary.summary(for: "git push origin main")
        #expect(summary?.invocations.first?.actions == ["push"])
        #expect(summary?.invocations.first?.arguments == ["origin", "main"])
        #expect(summary?.purpose == "Push to origin/main")
    }

    /// A destructive `rm` dominates the headline (highest salience) even amid other ops, and is
    /// flagged on its invocation — exactly what a user must notice before allowing a host command.
    @Test func destructiveRemoveLeads() {
        let summary = HostCommandSummary.summary(for: "swift build && rm -rf .build")
        #expect(summary?.isDestructive == true)
        #expect(summary?.invocations.contains { $0.destructive && $0.program == "rm" } == true)
        #expect(summary?.purpose == "Delete .build on the host")
    }

    /// A `sudo` prefix is stripped to find the real program but recorded as an elevation flag.
    @Test func sudoIsFlagged() {
        let summary = HostCommandSummary.summary(for: "sudo make install")
        #expect(summary?.isElevated == true)
        #expect(summary?.invocations.first?.program == "make")
        #expect(summary?.invocations.first?.actions == ["install"])
    }

    /// A bare script invocation: the program is its basename, a script operand isn't mistaken for
    /// an action, and the purpose names the script.
    @Test func interpreterScripts() {
        let py = HostCommandSummary.summary(for: "python3 tools/gen.py --check")
        #expect(py?.invocations.first?.program == "python3")
        #expect(py?.invocations.first?.actions.isEmpty == true)
        #expect(py?.invocations.first?.arguments == ["tools/gen.py"])
        #expect(py?.purpose == "Run the Python script `tools/gen.py`")

        let sh = HostCommandSummary.summary(for: "./scripts/package-app.sh dev")
        #expect(sh?.invocations.first?.program == "package-app.sh")
        #expect(sh?.purpose == "Run `package-app.sh dev`")
    }

    /// An unrecognized read-only command falls back to a restated purpose, ranked low so it never
    /// outshouts a real op in a pipeline.
    @Test func unknownAndReadOnlyFallback() {
        #expect(HostCommandSummary.summary(for: "ls -la")?.purpose == "Inspect the host (`ls`)")
        #expect(HostCommandSummary.summary(for: "")?.purpose == nil)  // blank → no summary
        #expect(HostCommandSummary.summary(for: "   ") == nil)
    }
}
