package hazmat

import (
	"bytes"
	"encoding/json"
	"os"
	"path/filepath"
	"reflect"
	"testing"

	"hazmat/hostfacts"
	linuxplatform "hazmat/platform/linux"
)

func TestBuildExplainJSON(t *testing.T) {
	restorePlatformReport := stubExplainPlatformReport(t, &linuxplatform.Report{
		RuntimeOS: "linux",
		NativeBackend: linuxplatform.NativeBackendStatus{
			Supported: false,
			Phase:     "plan-only",
			Reasons:   []string{"Linux native launch helper is not implemented yet"},
		},
	})
	defer restorePlatformReport()

	repoSetup := &repoSetupState{
		AppliedSafe: []repoSetupEffect{
			{
				ID:      "ro:/tmp/toolchain",
				Class:   repoSetupEffectClassSafe,
				Kind:    repoSetupEffectReadOnly,
				Value:   "/tmp/toolchain",
				Sources: []string{"Suggested by project files (node)"},
			},
		},
		PendingExplicit: []repoSetupEffect{
			{
				ID:      "rw:/tmp/cache",
				Class:   repoSetupEffectClassExplicit,
				Kind:    repoSetupEffectWrite,
				Value:   "/tmp/cache",
				Sources: []string{"Learned from previous session denial"},
			},
		},
		record: repoProfileRecord{
			Remembered: repoSetupStoredEffects{ReadOnly: []string{"/tmp/toolchain"}},
		},
	}
	cfg := sessionConfig{
		ProjectDir:            "/tmp/project",
		ReadDirs:              []string{"/tmp/auto", "/tmp/user"},
		WriteDirs:             []string{"/tmp/write"},
		UserReadDirs:          []string{"/tmp/user"},
		AutoReadDirs:          []string{"/tmp/auto"},
		SuggestedIntegrations: []string{"node"},
		ActiveIntegrations:    []string{"python-uv"},
		IntegrationSources:    []string{"uv (uv.lock)"},
		IntegrationDetails:    []string{"python-uv: resolved interpreter via uv"},
		IntegrationWarnings:   []string{"registry redirect active"},
		IntegrationEnv: map[string]string{
			"VIRTUAL_ENV": "/tmp/project/.venv",
			"GOROOT":      "/opt/homebrew/Cellar/go/1.2.3/libexec",
		},
		IntegrationRegistryKeys: []string{"NPM_CONFIG_REGISTRY"},
		CredentialEnvGrants: []sessionCredentialEnvGrant{
			{
				EnvVar:          "OPENAI_API_KEY",
				CredentialID:    credentialProviderOpenAIAPIKey,
				Source:          "host secret store",
				ConsumerHarness: HarnessCodex,
			},
		},
		PlannedHostMutations: []sessionMutation{
			{
				Summary:     "project ACL repair",
				Detail:      "may add bounded collaborative ACLs on /tmp/project and shallow existing paths",
				Persistence: "persistent in project",
				ProofScope:  sessionMutationProofScopeTLAModel,
			},
		},
		IntegrationExcludes: []string{".venv/"},
		ServiceAccess:       []string{"docker"},
		GitSSH: &sessionGitSSHConfig{
			DisplayName: "id_ed25519",
		},
		RoutingReason: "staying in native containment because docker: none is configured",
		SessionNotes:  []string{"Docker files detected but disabled by config"},
		RepoSetup:     repoSetup,
	}

	got := buildExplainJSON("shell", cfg, sessionModeNative, true)
	if got.FormatVersion != explainJSONFormatVersion {
		t.Fatalf("FormatVersion = %d", got.FormatVersion)
	}
	if got.Target != "shell" {
		t.Fatalf("Target = %q", got.Target)
	}
	if got.Mode != string(sessionModeNative) {
		t.Fatalf("Mode = %q", got.Mode)
	}
	if got.NetworkPolicy.Requested != "default" || got.NetworkPolicy.Effective != "default" || !got.NetworkPolicy.Enforced {
		t.Fatalf("NetworkPolicy = %+v, want enforced default policy", got.NetworkPolicy)
	}
	if !reflect.DeepEqual(got.SuggestedIntegrations, []string{"node"}) {
		t.Fatalf("SuggestedIntegrations = %v", got.SuggestedIntegrations)
	}
	if !reflect.DeepEqual(got.ActiveIntegrations, []string{"python-uv"}) {
		t.Fatalf("ActiveIntegrations = %v", got.ActiveIntegrations)
	}
	if got.RepoSetupSummary != "remembered (1 read-only path); additional approval required (1 write path)" {
		t.Fatalf("RepoSetupSummary = %q", got.RepoSetupSummary)
	}
	if !reflect.DeepEqual(got.RepoSetupApplied, []explainJSONRepoSetupEffect{
		{
			Class:   "safe",
			Kind:    "read_only",
			Value:   "/tmp/toolchain",
			Sources: []string{"Suggested by project files (node)"},
		},
	}) {
		t.Fatalf("RepoSetupApplied = %#v", got.RepoSetupApplied)
	}
	if !reflect.DeepEqual(got.RepoSetupPending, []explainJSONRepoSetupEffect{
		{
			Class:   "explicit",
			Kind:    "write",
			Value:   "/tmp/cache",
			Sources: []string{"Learned from previous session denial"},
		},
	}) {
		t.Fatalf("RepoSetupPending = %#v", got.RepoSetupPending)
	}
	if !reflect.DeepEqual(got.IntegrationEnvKeys, []string{"GOROOT", "VIRTUAL_ENV"}) {
		t.Fatalf("IntegrationEnvKeys = %v", got.IntegrationEnvKeys)
	}
	if !reflect.DeepEqual(got.CredentialEnvGrants, []explainJSONCredentialEnvGrant{
		{
			EnvVar:          "OPENAI_API_KEY",
			CredentialID:    string(credentialProviderOpenAIAPIKey),
			Source:          "host secret store",
			ConsumerHarness: string(HarnessCodex),
			Redacted:        true,
		},
	}) {
		t.Fatalf("CredentialEnvGrants = %#v", got.CredentialEnvGrants)
	}
	if got.Snapshot.Enabled {
		t.Fatalf("Snapshot.Enabled = true, want false")
	}
	if !reflect.DeepEqual(got.Snapshot.Excludes, []string{".venv/"}) {
		t.Fatalf("Snapshot.Excludes = %v", got.Snapshot.Excludes)
	}
	if len(got.PlannedHostMutations) != 1 || got.PlannedHostMutations[0].Summary != "project ACL repair" {
		t.Fatalf("PlannedHostMutations = %v", got.PlannedHostMutations)
	}
	if got.GitSSHKey != "id_ed25519" {
		t.Fatalf("GitSSHKey = %q, want id_ed25519", got.GitSSHKey)
	}
	if got.Platform == nil || got.Platform.RuntimeOS != "linux" || got.Platform.NativeBackend.Phase != "plan-only" {
		t.Fatalf("Platform = %+v, want Linux plan-only report", got.Platform)
	}
}

func TestHermesExplainJSONIncludesSharedProviderGrants(t *testing.T) {
	home := t.TempDir()
	t.Setenv("HOME", home)

	for _, spec := range harnessAPIKeyPrompts {
		if err := storeHostAPIKey(spec, "stored-"+spec.EnvVar); err != nil {
			t.Fatalf("storeHostAPIKey(%s): %v", spec.EnvVar, err)
		}
	}

	cfg := sessionConfig{
		HarnessID:  HarnessHermes,
		ProjectDir: "/tmp/project",
	}
	if err := applyHarnessAPIKeyEnvForSession(&cfg, true); err != nil {
		t.Fatalf("applyHarnessAPIKeyEnvForSession: %v", err)
	}

	got := buildExplainJSON("hermes", cfg, sessionModeNative, true)
	want := map[string]string{
		"ANTHROPIC_API_KEY":  string(credentialProviderAnthropicAPIKey),
		"OPENAI_API_KEY":     string(credentialProviderOpenAIAPIKey),
		"GEMINI_API_KEY":     string(credentialProviderGeminiAPIKey),
		"OPENROUTER_API_KEY": string(credentialProviderOpenRouterAPIKey),
	}
	if len(got.CredentialEnvGrants) != len(want) {
		t.Fatalf("Hermes CredentialEnvGrants = %#v, want %d grants", got.CredentialEnvGrants, len(want))
	}
	for _, grant := range got.CredentialEnvGrants {
		wantID, ok := want[grant.EnvVar]
		if !ok {
			t.Fatalf("unexpected Hermes credential grant: %#v", grant)
		}
		if grant.CredentialID != wantID || grant.Source != "host secret store" || grant.ConsumerHarness != string(HarnessHermes) || !grant.Redacted {
			t.Fatalf("Hermes grant for %s = %#v, want credential %s from host store consumed by Hermes", grant.EnvVar, grant, wantID)
		}
		delete(want, grant.EnvVar)
	}
	if len(want) != 0 {
		t.Fatalf("missing Hermes credential grants for %v", want)
	}
}

func TestExplainJSONCommandOutputsStructuredPreview(t *testing.T) {
	isolateConfig(t)
	skipInitCheck(t)

	dir := t.TempDir()
	if err := os.WriteFile(filepath.Join(dir, "package.json"), []byte("{}"), 0o644); err != nil {
		t.Fatalf("write package.json: %v", err)
	}
	canonicalDir, err := filepath.EvalSymlinks(dir)
	if err != nil {
		t.Fatalf("EvalSymlinks(%s): %v", dir, err)
	}

	cmd := newExplainCmd()
	var stdout bytes.Buffer
	var stderr bytes.Buffer
	cmd.SetOut(&stdout)
	cmd.SetErr(&stderr)
	cmd.SetArgs([]string{"--json", "-C", dir})

	if err := cmd.Execute(); err != nil {
		t.Fatalf("Execute: %v", err)
	}
	if stderr.Len() != 0 {
		t.Fatalf("stderr = %q, want empty", stderr.String())
	}

	var preview explainJSONPreview
	if err := json.Unmarshal(stdout.Bytes(), &preview); err != nil {
		t.Fatalf("unmarshal preview: %v\nstdout=%s", err, stdout.String())
	}
	if preview.ProjectDir != canonicalDir {
		t.Fatalf("ProjectDir = %q, want %q", preview.ProjectDir, canonicalDir)
	}
	if !reflect.DeepEqual(preview.SuggestedIntegrations, []string{"node"}) {
		t.Fatalf("SuggestedIntegrations = %v", preview.SuggestedIntegrations)
	}
	if preview.Mode != string(sessionModeNative) {
		t.Fatalf("Mode = %q", preview.Mode)
	}
	if !preview.Snapshot.Enabled {
		t.Fatalf("Snapshot.Enabled = false, want true")
	}
}

func TestExplainJSONCommandIsPlanOnlyAndDoesNotRequireInit(t *testing.T) {
	isolateConfig(t)
	t.Setenv("HOME", t.TempDir())

	savedRequireInit := requireInit
	requireInit = func() error { return os.ErrPermission }
	t.Cleanup(func() { requireInit = savedRequireInit })

	dir := t.TempDir()
	cmd := newExplainCmd()
	var stdout bytes.Buffer
	var stderr bytes.Buffer
	cmd.SetOut(&stdout)
	cmd.SetErr(&stderr)
	cmd.SetArgs([]string{"--json", "-C", dir})

	if err := cmd.Execute(); err != nil {
		t.Fatalf("Execute should not require init for plan-only explain: %v", err)
	}
	if stderr.Len() != 0 {
		t.Fatalf("stderr = %q, want empty", stderr.String())
	}
}

func TestRenderRepoSetupDetails(t *testing.T) {
	got := renderRepoSetupDetails(&repoSetupState{
		AppliedSafe: []repoSetupEffect{
			{
				ID:      "ro:/tmp/toolchain",
				Class:   repoSetupEffectClassSafe,
				Kind:    repoSetupEffectReadOnly,
				Value:   "/tmp/toolchain",
				Sources: []string{"Suggested by project files (node)"},
			},
		},
		PendingSafe: []repoSetupEffect{
			{
				ID:      "exclude:build/",
				Class:   repoSetupEffectClassSafe,
				Kind:    repoSetupEffectSnapshotExclude,
				Value:   "build/",
				Sources: []string{"Suggested by project files (node)"},
			},
		},
		record: repoProfileRecord{
			Remembered: repoSetupStoredEffects{ReadOnly: []string{"/tmp/toolchain"}},
		},
	})

	for _, want := range []string{
		"hazmat: repo setup remembered (1 read-only path); additional repo setup available (1 snapshot exclude)",
		"Applied:",
		"- read-only: /tmp/toolchain (Suggested by project files (node))",
		"Available:",
		"- snapshot exclude: build/ (Suggested by project files (node))",
	} {
		if !bytes.Contains([]byte(got), []byte(want)) {
			t.Fatalf("renderRepoSetupDetails missing %q in:\n%s", want, got)
		}
	}
}

func stubExplainPlatformReport(t *testing.T, report *linuxplatform.Report) func() {
	t.Helper()
	saved := explainPlatformReport
	explainPlatformReport = func(_ hostfacts.HostFacts) *linuxplatform.Report { return report }
	return func() { explainPlatformReport = saved }
}
