package incident_test

import (
	"os"
	"path/filepath"
	"strings"
	"testing"

	"github.com/garagon/aguara/internal/incident"
	"github.com/garagon/aguara/internal/intel"
	"github.com/garagon/aguara/internal/packagecheck"
)

const miasmaAdvisory = "AIKIDO-2026-06-01-redhat-miasma"

// runMiasmaLockfile discovers npm lockfiles under dir and runs them
// through a matcher built from the REAL embedded snapshots, so the
// manual Miasma entries are exercised end to end through the actual
// lockfile parsers and the packagecheck runner (the same path
// `aguara check` takes for a pre-install lockfile scan).
func runMiasmaLockfile(t *testing.T, dir string) *packagecheck.RunResult {
	t.Helper()
	matcher := intel.NewMatcher(incident.EmbeddedSnapshots()...)
	targets, err := packagecheck.Discover(dir, []string{intel.EcosystemNPM})
	if err != nil {
		t.Fatalf("Discover: %v", err)
	}
	res, err := (&packagecheck.Runner{Matcher: matcher}).Run(targets)
	if err != nil {
		t.Fatalf("Run: %v", err)
	}
	return res
}

func writeFixture(t *testing.T, dir, name, content string) {
	t.Helper()
	if err := os.WriteFile(filepath.Join(dir, name), []byte(content), 0o644); err != nil {
		t.Fatalf("write %s: %v", name, err)
	}
}

// TestMiasma_NodeModulesHit covers the installed-tree surface:
// @redhat-cloud-services/chrome@2.3.1 in node_modules must flag
// CRITICAL with the Miasma advisory.
func TestMiasma_NodeModulesHit(t *testing.T) {
	dir := t.TempDir()
	nm := filepath.Join(dir, "node_modules")
	writeNPMPackage(t, nm, "@redhat-cloud-services/chrome", "2.3.1")
	writeNPMPackage(t, nm, "react", "18.3.1") // unrelated; must NOT flag

	result, err := incident.CheckNPM(incident.CheckOptions{Path: nm})
	if err != nil {
		t.Fatalf("CheckNPM error: %v", err)
	}
	if got := len(result.Findings); got != 1 {
		t.Fatalf("expected 1 finding, got %d: %+v", got, result.Findings)
	}
	f := result.Findings[0]
	if f.Severity != incident.SevCritical {
		t.Errorf("expected CRITICAL, got %q", f.Severity)
	}
	want := "@redhat-cloud-services/chrome 2.3.1 is a known compromised npm package (" + miasmaAdvisory + ")"
	if f.Title != want {
		t.Errorf("title = %q, want %q", f.Title, want)
	}
}

// TestMiasma_PackageLockHit covers the pre-install package-lock.json
// surface for the same package+version.
func TestMiasma_PackageLockHit(t *testing.T) {
	dir := t.TempDir()
	writeFixture(t, dir, "package-lock.json", `{
	  "name": "app",
	  "lockfileVersion": 3,
	  "packages": {
	    "": { "name": "app", "version": "1.0.0" },
	    "node_modules/@redhat-cloud-services/chrome": { "version": "2.3.1", "resolved": "https://registry.npmjs.org/@redhat-cloud-services/chrome/-/chrome-2.3.1.tgz" }
	  }
	}`)

	res := runMiasmaLockfile(t, dir)
	assertSingleMiasmaHit(t, res, "@redhat-cloud-services/chrome", "package-lock.json")
}

// TestMiasma_PnpmLockHit covers the pnpm-lock.yaml surface with a
// scoped package path.
func TestMiasma_PnpmLockHit(t *testing.T) {
	dir := t.TempDir()
	// Scoped pnpm keys must be quoted ('@' is a reserved YAML
	// indicator), so this fixture is inline rather than reusing the
	// unscoped trapdoorPnpmLock helper.
	pnpm := "lockfileVersion: '9.0'\n\n" +
		"packages:\n" +
		"  '@redhat-cloud-services/rbac-client@9.0.3':\n" +
		"    resolution:\n" +
		"      integrity: sha512-FAKE_FIXTURE_DO_NOT_VERIFY==\n"
	writeFixture(t, dir, "pnpm-lock.yaml", pnpm)

	res := runMiasmaLockfile(t, dir)
	assertSingleMiasmaHit(t, res, "@redhat-cloud-services/rbac-client", "pnpm-lock.yaml")
}

// TestMiasma_YarnLockHit covers the yarn classic (v1) yarn.lock
// surface.
func TestMiasma_YarnLockHit(t *testing.T) {
	dir := t.TempDir()
	yarn := "# THIS IS AN AUTOGENERATED FILE.\n# yarn lockfile v1\n\n\n" +
		`"@redhat-cloud-services/types@^3.6.4":` + "\n" +
		`  version "3.6.4"` + "\n" +
		`  resolved "https://registry.yarnpkg.com/@redhat-cloud-services/types/-/types-3.6.4.tgz#abc"` + "\n"
	writeFixture(t, dir, "yarn.lock", yarn)

	res := runMiasmaLockfile(t, dir)
	assertSingleMiasmaHit(t, res, "@redhat-cloud-services/types", "yarn.lock")
}

// TestMiasma_NeighborVersionClean guards against accidental range
// expansion: a version adjacent to a compromised pin must NOT flag.
func TestMiasma_NeighborVersionClean(t *testing.T) {
	dir := t.TempDir()
	nm := filepath.Join(dir, "node_modules")
	writeNPMPackage(t, nm, "@redhat-cloud-services/chrome", "2.3.0")                        // clean neighbor
	writeNPMPackage(t, nm, "@redhat-cloud-services/rbac-client", "9.0.2")                   // clean neighbor
	writeNPMPackage(t, nm, "@redhat-cloud-services/types", "3.6.3")                         // clean gap between pinned versions
	writeNPMPackage(t, nm, "@redhat-cloud-services/frontend-components-utilities", "7.4.3") // clean gap between 7.4.2 and 7.4.4

	result, err := incident.CheckNPM(incident.CheckOptions{Path: nm})
	if err != nil {
		t.Fatalf("CheckNPM error: %v", err)
	}
	if len(result.Findings) != 0 {
		t.Errorf("neighbor versions must not flag, got: %+v", result.Findings)
	}
}

// TestMiasma_CleanSimilarScopeClean confirms a non-compromised
// package in the same scope (and a similarly-named scoped package)
// does not flag.
func TestMiasma_CleanSimilarScopeClean(t *testing.T) {
	dir := t.TempDir()
	nm := filepath.Join(dir, "node_modules")
	writeNPMPackage(t, nm, "@redhat-cloud-services/not-compromised", "1.0.0")
	writeNPMPackage(t, nm, "@redhat-cloud-services-fork/chrome", "2.3.1") // similar but different scope

	result, err := incident.CheckNPM(incident.CheckOptions{Path: nm})
	if err != nil {
		t.Fatalf("CheckNPM error: %v", err)
	}
	if len(result.Findings) != 0 {
		t.Errorf("clean / similar-name packages must not flag, got: %+v", result.Findings)
	}
}

// TestMiasma_NewVersionsFire covers the versions added from the OSV /
// GitHub Security Advisory enumeration (the third malicious version per
// package, beyond the 63 originally enumerated by Aikido). Each must
// flag CRITICAL with the Miasma advisory.
func TestMiasma_NewVersionsFire(t *testing.T) {
	cases := []struct{ name, version string }{
		{"@redhat-cloud-services/frontend-components-utilities", "7.4.4"},
		{"@redhat-cloud-services/chrome", "2.3.4"},
		{"@redhat-cloud-services/tsc-transform-imports", "1.2.6"},
	}
	for _, tc := range cases {
		t.Run(tc.name+"@"+tc.version, func(t *testing.T) {
			dir := t.TempDir()
			nm := filepath.Join(dir, "node_modules")
			writeNPMPackage(t, nm, tc.name, tc.version)

			result, err := incident.CheckNPM(incident.CheckOptions{Path: nm})
			if err != nil {
				t.Fatalf("CheckNPM error: %v", err)
			}
			if got := len(result.Findings); got != 1 {
				t.Fatalf("expected 1 finding, got %d: %+v", got, result.Findings)
			}
			f := result.Findings[0]
			if f.Severity != incident.SevCritical {
				t.Errorf("expected CRITICAL, got %q", f.Severity)
			}
			want := tc.name + " " + tc.version + " is a known compromised npm package (" + miasmaAdvisory + ")"
			if f.Title != want {
				t.Errorf("title = %q, want %q", f.Title, want)
			}
		})
	}
}

// TestMiasma_AdvisoryVersionCount locks the full Microsoft/OSV
// enumeration: 32 packages, 96 malicious versions under the single
// Miasma advisory. Guards against an accidental drop or a future
// regen that silently changes the surface.
func TestMiasma_AdvisoryVersionCount(t *testing.T) {
	pkgs, versions := 0, 0
	for _, p := range incident.KnownCompromised {
		if p.Advisory != miasmaAdvisory {
			continue
		}
		pkgs++
		versions += len(p.Versions)
	}
	if pkgs != 32 {
		t.Errorf("Miasma package count = %d, want 32", pkgs)
	}
	if versions != 96 {
		t.Errorf("Miasma version count = %d, want 96", versions)
	}
}

// assertSingleMiasmaHit asserts the run produced exactly one hit for
// the named package, carrying the Miasma advisory, from the expected
// lockfile source.
func assertSingleMiasmaHit(t *testing.T, res *packagecheck.RunResult, wantName, wantSource string) {
	t.Helper()
	if len(res.Hits) != 1 {
		t.Fatalf("expected 1 hit, got %d: %+v", len(res.Hits), res.Hits)
	}
	hit := res.Hits[0]
	if hit.Ref.Name != wantName {
		t.Errorf("hit name = %q, want %q", hit.Ref.Name, wantName)
	}
	if hit.Record.ID != miasmaAdvisory {
		t.Errorf("hit advisory = %q, want %q", hit.Record.ID, miasmaAdvisory)
	}
	if !strings.EqualFold(string(hit.Record.Kind), "compromised") {
		t.Errorf("hit kind = %q, want compromised", hit.Record.Kind)
	}
	if len(res.Ecosystems) != 1 || res.Ecosystems[0].Source != wantSource {
		t.Errorf("ecosystem summary = %+v, want one %s entry", res.Ecosystems, wantSource)
	}
}
