// Copyright 2026 Cisco Systems, Inc. and its affiliates
//
// Licensed under the Apache License, Version 2.0 (the "License");
// you may not use this file except in compliance with the License.
// You may obtain a copy of the License at
//
//     http://www.apache.org/licenses/LICENSE-2.0
//
// Unless required by applicable law or agreed to in writing, software
// distributed under the License is distributed on an "AS IS" BASIS,
// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
// See the License for the specific language governing permissions and
// limitations under the License.
//
// SPDX-License-Identifier: Apache-2.0

package tui

import (
	"strings"
	"testing"
)

func TestBuildRegistry(t *testing.T) {
	registry := BuildRegistry()

	if len(registry) == 0 {
		t.Fatal("registry is empty")
	}

	t.Run("minimum_command_count", func(t *testing.T) {
		if len(registry) < 50 {
			t.Errorf("expected at least 50 commands, got %d", len(registry))
		}
	})

	t.Run("all_entries_have_required_fields", func(t *testing.T) {
		for _, entry := range registry {
			if entry.TUIName == "" {
				t.Error("entry has empty TUIName")
			}
			if entry.CLIBinary == "" {
				t.Errorf("entry %q has empty CLIBinary", entry.TUIName)
			}
			if len(entry.CLIArgs) == 0 {
				t.Errorf("entry %q has empty CLIArgs", entry.TUIName)
			}
			if entry.Description == "" {
				t.Errorf("entry %q has empty Description", entry.TUIName)
			}
			if entry.Category == "" {
				t.Errorf("entry %q has empty Category", entry.TUIName)
			}
		}
	})

	t.Run("cli_binary_is_known", func(t *testing.T) {
		for _, entry := range registry {
			if entry.CLIBinary != "defenseclaw" && entry.CLIBinary != "defenseclaw-gateway" {
				t.Errorf("entry %q has unexpected CLIBinary %q", entry.TUIName, entry.CLIBinary)
			}
		}
	})

	t.Run("needs_arg_implies_arg_hint", func(t *testing.T) {
		for _, entry := range registry {
			if entry.NeedsArg && entry.ArgHint == "" {
				t.Errorf("entry %q has NeedsArg=true but empty ArgHint", entry.TUIName)
			}
		}
	})

	t.Run("no_duplicate_tui_names", func(t *testing.T) {
		seen := make(map[string]bool)
		for _, entry := range registry {
			if seen[entry.TUIName] {
				t.Errorf("duplicate TUIName: %q", entry.TUIName)
			}
			seen[entry.TUIName] = true
		}
	})

	t.Run("expected_categories", func(t *testing.T) {
		cats := map[string]bool{
			"setup": false, "scan": false, "enforce": false,
			"daemon": false, "info": false, "policy": false,
			"sandbox": false, "install": false, "other": false,
		}
		for _, entry := range registry {
			cats[entry.Category] = true
		}
		for cat, found := range cats {
			if !found {
				t.Errorf("expected category %q not found in registry", cat)
			}
		}
	})
}

func TestBuildRegistryIncludesEnterpriseSetupSurfaces(t *testing.T) {
	registry := BuildRegistry()
	seen := make(map[string]CmdEntry, len(registry))
	for _, entry := range registry {
		seen[entry.TUIName] = entry
	}

	required := []string{
		"init first-run",
		"quickstart",
		"doctor run",
		"doctor --fix",
		"keys set",
		"setup redaction",
		"setup observability add",
		"setup local-observability reset",
		"setup webhook add",
		"skill search",
		"uninstall dry-run",
		"uninstall --all --yes",
		"reset --yes",
		"connector verify",
		"connector teardown",
		"gateway provenance show",
	}
	for _, name := range required {
		if _, ok := seen[name]; !ok {
			t.Errorf("BuildRegistry missing %q", name)
		}
	}

	if entry := seen["doctor --fix"]; !containsArg(entry.CLIArgs, "--fix") || !containsArg(entry.CLIArgs, "--yes") {
		t.Fatalf("doctor --fix should route to doctor --fix --yes, got %v", entry.CLIArgs)
	}
	if entry := seen["setup redaction"]; !entry.NeedsArg || entry.ArgHint == "" {
		t.Fatalf("setup redaction should prompt for status/on/off, got %+v", entry)
	}
}

func TestBuildRegistryIncludesCLINativeAliases(t *testing.T) {
	registry := BuildRegistry()
	seen := make(map[string]CmdEntry, len(registry))
	for _, entry := range registry {
		seen[entry.TUIName] = entry
	}

	required := []string{
		"setup",
		"setup webhook",
		"setup observability",
		"skill block",
		"skill allow",
		"skill install",
		"mcp set",
		"mcp unset",
		"plugin quarantine",
		"plugin remove",
		"tool block",
		"policy edit",
		"keys",
		"config",
		"reset",
		"uninstall",
	}
	for _, name := range required {
		if _, ok := seen[name]; !ok {
			t.Errorf("BuildRegistry missing CLI-native alias %q", name)
		}
	}

	if entry := seen["mcp set"]; entry.CLIBinary != "defenseclaw" || strings.Join(entry.CLIArgs, " ") != "mcp set" {
		t.Fatalf("mcp set should route to defenseclaw mcp set, got %+v", entry)
	}
	if entry := seen["skill block"]; entry.CLIBinary != "defenseclaw" || strings.Join(entry.CLIArgs, " ") != "skill block" {
		t.Fatalf("skill block should route to defenseclaw skill block, got %+v", entry)
	}
}

func containsArg(args []string, want string) bool {
	for _, arg := range args {
		if arg == want {
			return true
		}
	}
	return false
}

func TestMatchCommand(t *testing.T) {
	registry := BuildRegistry()

	tests := []struct {
		name      string
		input     string
		wantTUI   string
		wantExtra string
		wantNil   bool
	}{
		{
			name:    "empty_input",
			input:   "",
			wantNil: true,
		},
		{
			name:    "no_match",
			input:   "nonexistent-command",
			wantNil: true,
		},
		{
			name:      "exact_match_no_arg",
			input:     "init",
			wantTUI:   "init",
			wantExtra: "",
		},
		{
			name:      "exact_match_no_arg_status",
			input:     "status",
			wantTUI:   "status",
			wantExtra: "",
		},
		{
			name:      "match_with_extra_arg",
			input:     "scan skill my-agent",
			wantTUI:   "scan skill",
			wantExtra: "my-agent",
		},
		{
			name:      "match_mcp_scan_with_url",
			input:     "scan mcp https://example.com/mcp",
			wantTUI:   "scan mcp",
			wantExtra: "https://example.com/mcp",
		},
		{
			name:      "longer_prefix_wins",
			input:     "scan skill --all",
			wantTUI:   "scan skill --all",
			wantExtra: "",
		},
		{
			name:      "block_skill_with_target",
			input:     "block skill evil-skill",
			wantTUI:   "block skill",
			wantExtra: "evil-skill",
		},
		{
			name:      "noun_first_skill_block_with_target",
			input:     "skill block evil-skill",
			wantTUI:   "skill block",
			wantExtra: "evil-skill",
		},
		{
			name:      "noun_first_mcp_set_with_flags",
			input:     `mcp set context7 --url https://example.com/mcp`,
			wantTUI:   "mcp set",
			wantExtra: "context7 --url https://example.com/mcp",
		},
		{
			name:      "daemon_start",
			input:     "start",
			wantTUI:   "start",
			wantExtra: "",
		},
		{
			name:      "daemon_restart",
			input:     "restart",
			wantTUI:   "restart",
			wantExtra: "",
		},
		{
			name:      "policy_list",
			input:     "policy list",
			wantTUI:   "policy list",
			wantExtra: "",
		},
		{
			name:      "doctor",
			input:     "doctor",
			wantTUI:   "doctor",
			wantExtra: "",
		},
		{
			name:      "doctor_run_alias",
			input:     "doctor run",
			wantTUI:   "doctor run",
			wantExtra: "",
		},
		{
			name:      "sandbox_exec_with_arg",
			input:     "sandbox exec ls -la",
			wantTUI:   "sandbox exec",
			wantExtra: "ls -la",
		},
		{
			name:      "whitespace_trimmed",
			input:     "  status  ",
			wantTUI:   "status",
			wantExtra: "",
		},
	}

	for _, tt := range tests {
		t.Run(tt.name, func(t *testing.T) {
			entry, extra := MatchCommand(tt.input, registry)

			if tt.wantNil {
				if entry != nil {
					t.Errorf("expected nil entry, got %q", entry.TUIName)
				}
				return
			}

			if entry == nil {
				t.Fatalf("expected entry for %q, got nil", tt.input)
				return
			}
			if entry.TUIName != tt.wantTUI {
				t.Errorf("TUIName = %q, want %q", entry.TUIName, tt.wantTUI)
			}
			if extra != tt.wantExtra {
				t.Errorf("extra = %q, want %q", extra, tt.wantExtra)
			}
		})
	}
}

func TestMatchCommandCLIMapping(t *testing.T) {
	registry := BuildRegistry()

	tests := []struct {
		input     string
		wantBin   string
		wantArgs  []string
		wantExtra string
	}{
		{
			input:     "scan skill my-skill",
			wantBin:   "defenseclaw",
			wantArgs:  []string{"skill", "scan"},
			wantExtra: "my-skill",
		},
		{
			input:     "block mcp https://mcp.example.com",
			wantBin:   "defenseclaw",
			wantArgs:  []string{"mcp", "block"},
			wantExtra: "https://mcp.example.com",
		},
		{
			input:    "restart",
			wantBin:  "defenseclaw-gateway",
			wantArgs: []string{"restart"},
		},
		{
			input:    "policy evaluate",
			wantBin:  "defenseclaw-gateway",
			wantArgs: []string{"policy", "evaluate"},
		},
		{
			input:    "setup guardrail",
			wantBin:  "defenseclaw",
			wantArgs: []string{"setup", "guardrail"},
		},
	}

	for _, tt := range tests {
		t.Run(tt.input, func(t *testing.T) {
			entry, extra := MatchCommand(tt.input, registry)
			if entry == nil {
				t.Fatalf("no match for %q", tt.input)
				return
			}
			if entry.CLIBinary != tt.wantBin {
				t.Errorf("CLIBinary = %q, want %q", entry.CLIBinary, tt.wantBin)
			}
			if len(entry.CLIArgs) != len(tt.wantArgs) {
				t.Fatalf("CLIArgs = %v, want %v", entry.CLIArgs, tt.wantArgs)
			}
			for i, a := range entry.CLIArgs {
				if a != tt.wantArgs[i] {
					t.Errorf("CLIArgs[%d] = %q, want %q", i, a, tt.wantArgs[i])
				}
			}
			if extra != tt.wantExtra {
				t.Errorf("extra = %q, want %q", extra, tt.wantExtra)
			}
		})
	}
}

func TestBuildCLIArgsSplitsTailIntoArguments(t *testing.T) {
	registry := BuildRegistry()
	entry, extra := MatchCommand(`sandbox exec -- ls -la "/tmp/scan report.txt"`, registry)
	if entry == nil {
		t.Fatal("expected sandbox exec entry")
	}

	args, err := buildCLIArgs(entry, extra)
	if err != nil {
		t.Fatalf("buildCLIArgs returned error: %v", err)
	}

	want := []string{"sandbox", "exec", "--", "ls", "-la", "/tmp/scan report.txt"}
	if len(args) != len(want) {
		t.Fatalf("args = %v, want %v", args, want)
	}
	for i := range want {
		if args[i] != want[i] {
			t.Fatalf("args[%d] = %q, want %q", i, args[i], want[i])
		}
	}
}

func TestBuildCLIArgsPreservesQuotedJSON(t *testing.T) {
	registry := BuildRegistry()
	entry, extra := MatchCommand(`set mcp context7 --args '["-y", "@modelcontextprotocol/server"]' --url https://example.com/mcp`, registry)
	if entry == nil {
		t.Fatal("expected set mcp entry")
	}

	args, err := buildCLIArgs(entry, extra)
	if err != nil {
		t.Fatalf("buildCLIArgs returned error: %v", err)
	}

	want := []string{
		"mcp", "set", "context7",
		"--args", `["-y", "@modelcontextprotocol/server"]`,
		"--url", "https://example.com/mcp",
	}
	if len(args) != len(want) {
		t.Fatalf("args = %v, want %v", args, want)
	}
	for i := range want {
		if args[i] != want[i] {
			t.Fatalf("args[%d] = %q, want %q", i, args[i], want[i])
		}
	}
}

func TestBuildRegistryAlertsAliasUsesPlainAlertsCommand(t *testing.T) {
	registry := BuildRegistry()

	for _, entry := range registry {
		if entry.TUIName != "alerts" {
			continue
		}
		want := []string{"alerts", "--no-tui"}
		if len(entry.CLIArgs) != len(want) {
			t.Fatalf("alerts CLIArgs = %v, want %v", entry.CLIArgs, want)
		}
		for i := range want {
			if entry.CLIArgs[i] != want[i] {
				t.Fatalf("alerts CLIArgs[%d] = %q, want %q", i, entry.CLIArgs[i], want[i])
			}
		}
		return
	}

	t.Fatal("alerts entry not found")
}

func TestCommandExecutorInitialState(t *testing.T) {
	executor := NewCommandExecutor()

	if executor.IsRunning() {
		t.Error("new executor should not be running")
	}
}

func TestCommandRegistryCategories(t *testing.T) {
	registry := BuildRegistry()

	catCount := make(map[string]int)
	for _, entry := range registry {
		catCount[entry.Category]++
	}

	t.Run("scan_commands_exist", func(t *testing.T) {
		if catCount["scan"] < 5 {
			t.Errorf("expected at least 5 scan commands, got %d", catCount["scan"])
		}
	})

	t.Run("enforce_commands_exist", func(t *testing.T) {
		if catCount["enforce"] < 10 {
			t.Errorf("expected at least 10 enforce commands, got %d", catCount["enforce"])
		}
	})

	t.Run("daemon_commands_exist", func(t *testing.T) {
		if catCount["daemon"] < 3 {
			t.Errorf("expected at least 3 daemon commands, got %d", catCount["daemon"])
		}
	})

	t.Run("policy_commands_exist", func(t *testing.T) {
		if catCount["policy"] < 5 {
			t.Errorf("expected at least 5 policy commands, got %d", catCount["policy"])
		}
	})
}

func TestCommandRegistryNoPrefixInTUINames(t *testing.T) {
	registry := BuildRegistry()

	for _, entry := range registry {
		if strings.HasPrefix(entry.TUIName, "defenseclaw ") {
			t.Errorf("TUIName %q should not have defenseclaw prefix", entry.TUIName)
		}
		if strings.HasPrefix(entry.TUIName, "defenseclaw-gateway ") {
			t.Errorf("TUIName %q should not have defenseclaw-gateway prefix", entry.TUIName)
		}
	}
}
