package cmd

import (
	"bytes"
	"os"
	"strings"
	"testing"

	"github.com/mcpjungle/mcpjungle/pkg/testhelpers"
	"github.com/mcpjungle/mcpjungle/pkg/types"
	"github.com/spf13/cobra"
)

func TestCreateCommandStructure(t *testing.T) {
	t.Parallel()

	// Test command properties
	testhelpers.AssertEqual(t, "create", createCmd.Use)
	testhelpers.AssertEqual(t, "Create entities in mcpjungle", createCmd.Short)

	// Test command annotations
	annotationTests := []testhelpers.CommandAnnotationTest{
		{Key: "group", Expected: string(subCommandGroupAdvanced)},
		{Key: "order", Expected: "4"},
	}
	testhelpers.TestCommandAnnotations(t, createCmd.Annotations, annotationTests)

	// Test subcommands count
	subcommands := createCmd.Commands()
	testhelpers.AssertEqual(t, 3, len(subcommands))
}

func TestCreateMcpClientSubcommand(t *testing.T) {
	t.Parallel()

	// Test command properties
	testhelpers.AssertEqual(t, "mcp-client [name] | --conf <file>", createMcpClientCmd.Use)
	testhelpers.AssertEqual(t, "Create an authenticated MCP client (Enterprise mode)", createMcpClientCmd.Short)
	testhelpers.AssertNotNil(t, createMcpClientCmd.Long)
	testhelpers.AssertTrue(t, len(createMcpClientCmd.Long) > 0, "Long description should not be empty")

	// Test command functions
	testhelpers.AssertNotNil(t, createMcpClientCmd.RunE)
	testhelpers.AssertNotNil(t, createMcpClientCmd.Args)

	// Test command flags
	allowFlag := createMcpClientCmd.Flags().Lookup("allow")
	testhelpers.AssertNotNil(t, allowFlag)
	testhelpers.AssertTrue(t, len(allowFlag.Usage) > 0, "Allow flag should have usage description")

	descriptionFlag := createMcpClientCmd.Flags().Lookup("description")
	testhelpers.AssertNotNil(t, descriptionFlag)
	testhelpers.AssertTrue(t, len(descriptionFlag.Usage) > 0, "Description flag should have usage description")

	accessTokenFlag := createMcpClientCmd.Flags().Lookup("access-token")
	testhelpers.AssertNotNil(t, accessTokenFlag)
	testhelpers.AssertTrue(t, len(accessTokenFlag.Usage) > 0, "Access token flag should have usage description")

	configFlag := createMcpClientCmd.Flags().Lookup("conf")
	testhelpers.AssertNotNil(t, configFlag)
	testhelpers.AssertTrue(t, len(configFlag.Usage) > 0, "Config flag should have usage description")
}

func TestCreateUserSubcommand(t *testing.T) {
	// Test command properties
	testhelpers.AssertEqual(t, "user [username] | --conf <file>", createUserCmd.Use)
	testhelpers.AssertEqual(t, "Create a new user (Enterprise mode)", createUserCmd.Short)
	testhelpers.AssertNotNil(t, createUserCmd.Long)
	testhelpers.AssertTrue(t, len(createUserCmd.Long) > 0, "Long description should not be empty")

	// Test command functions
	testhelpers.AssertNotNil(t, createUserCmd.RunE)
	testhelpers.AssertNotNil(t, createUserCmd.Args)

	// Test command flags
	configFlag := createUserCmd.Flags().Lookup("conf")
	testhelpers.AssertNotNil(t, configFlag)
	testhelpers.AssertTrue(t, len(configFlag.Usage) > 0, "Config flag should have usage description")
}

func TestCreateToolGroupSubcommand(t *testing.T) {
	// Test command properties
	testhelpers.AssertEqual(t, "group --conf <file>", createToolGroupCmd.Use)
	testhelpers.AssertEqual(t, "Create a Group of MCP Tools", createToolGroupCmd.Short)
	testhelpers.AssertNotNil(t, createToolGroupCmd.Long)
	testhelpers.AssertTrue(t, len(createToolGroupCmd.Long) > 0, "Long description should not be empty")

	// Test command functions
	testhelpers.AssertNotNil(t, createToolGroupCmd.RunE)

	// Test command flags
	confFlag := createToolGroupCmd.Flags().Lookup("conf")
	testhelpers.AssertNotNil(t, confFlag)
	testhelpers.AssertTrue(t, len(confFlag.Usage) > 0, "Conf flag should have usage description")
}

func TestCreateCommandVariables(t *testing.T) {
	// Test that command variables are properly initialized to empty values
	testhelpers.AssertEqual(t, "", createMcpClientCmdAllowedServers)
	testhelpers.AssertEqual(t, "", createMcpClientCmdDescription)
	testhelpers.AssertEqual(t, "", createMcpClientCmdConfigFilePath)
	testhelpers.AssertEqual(t, "", createUserCmdConfigFilePath)
	testhelpers.AssertEqual(t, "", createToolGroupConfigFilePath)
}

// Test allow list parsing logic
func TestParseAllowList(t *testing.T) {
	testCases := []struct {
		name     string
		input    string
		expected []string
	}{
		{"empty string", "", []string{}},
		{"single server", "server1", []string{"server1"}},
		{"multiple servers", "server1,server2,server3", []string{"server1", "server2", "server3"}},
		{"servers with spaces", "server1, server2 , server3", []string{"server1", "server2", "server3"}},
		{"servers with empty elements", "server1,,server2", []string{"server1", "server2"}},
		{"servers with only spaces", "server1,  ,server2", []string{"server1", "server2"}},
	}

	for _, tc := range testCases {
		t.Run(tc.name, func(t *testing.T) {
			// Parse allow list (simulating the logic from create.go)
			allowList := make([]string, 0)
			for _, s := range strings.Split(tc.input, ",") {
				trimmed := strings.TrimSpace(s)
				if trimmed != "" {
					allowList = append(allowList, trimmed)
				}
			}

			// Compare results
			if len(tc.expected) != len(allowList) {
				t.Errorf("Expected length %d, got %d", len(tc.expected), len(allowList))
				return
			}
			for i, expected := range tc.expected {
				if expected != allowList[i] {
					t.Errorf("Expected[%d] = %s, got %s", i, expected, allowList[i])
				}
			}
		})
	}
}

// Integration tests for create commands
func TestCreateCommandIntegration(t *testing.T) {
	// Verify that createCmd is properly added to rootCmd
	testhelpers.AssertNotNil(t, createCmd)

	// Test all create subcommands are properly configured
	subcommands := createCmd.Commands()
	expectedSubcommands := []string{"mcp-client", "user", "group"}

	testhelpers.AssertEqual(t, len(expectedSubcommands), len(subcommands))

	for _, expected := range expectedSubcommands {
		found := false
		for _, subcmd := range subcommands {
			if subcmd.Name() == expected {
				found = true
				break
			}
		}
		testhelpers.AssertTrue(t, found, "Expected subcommand '"+expected+"' not found")
	}
}

// Test argument validation
func TestCreateCommandArgumentValidation(t *testing.T) {
	// Test that commands properly validate arguments
	testhelpers.AssertNotNil(t, createMcpClientCmd.Args)
	testhelpers.AssertNotNil(t, createUserCmd.Args)
	// createToolGroupCmd doesn't have Args validation, which is correct

	// Test various invalid input scenarios
	testCases := []struct {
		name        string
		args        []string
		expectError bool
	}{
		{"empty args", []string{}, true},
		{"too many args", []string{"arg1", "arg2", "arg3"}, true},
		{"valid single arg", []string{"valid-arg"}, false},
	}

	for _, tc := range testCases {
		t.Run(tc.name, func(t *testing.T) {
			// Test mcp-client command args validation
			if createMcpClientCmd.Args != nil {
				err := createMcpClientCmd.Args(createMcpClientCmd, tc.args)
				if tc.expectError {
					testhelpers.AssertError(t, err)
				} else {
					testhelpers.AssertNoError(t, err)
				}
			}

			// Test user command args validation
			if createUserCmd.Args != nil {
				err := createUserCmd.Args(createUserCmd, tc.args)
				if tc.expectError {
					testhelpers.AssertError(t, err)
				} else {
					testhelpers.AssertNoError(t, err)
				}
			}
		})
	}
}

func TestResolveAccessTokenFromConfig(t *testing.T) {
	t.Parallel()

	t.Run("direct token wins", func(t *testing.T) {
		t.Parallel()
		token, err := resolveAccessTokenFromConfig("direct-token", types.AccessTokenRef{})
		testhelpers.AssertNoError(t, err)
		testhelpers.AssertEqual(t, "direct-token", token)
	})

	t.Run("env var used when set", func(t *testing.T) {
		t.Parallel()
		env := "MCPJ_TEST_TOKEN"
		_ = os.Setenv(env, "  env-token  ")
		defer os.Unsetenv(env)

		token, err := resolveAccessTokenFromConfig("", types.AccessTokenRef{Env: env})
		testhelpers.AssertNoError(t, err)
		testhelpers.AssertEqual(t, "env-token", token)
	})

	t.Run("env var empty and no file -> error", func(t *testing.T) {
		t.Parallel()
		env := "MCPJ_TEST_TOKEN_EMPTY"
		_ = os.Setenv(env, "   ")
		defer os.Unsetenv(env)

		_, err := resolveAccessTokenFromConfig("", types.AccessTokenRef{Env: env})
		testhelpers.AssertError(t, err)
	})

	t.Run("file is used when provided", func(t *testing.T) {
		t.Parallel()
		f, err := os.CreateTemp("", "mcpj-token-*")
		testhelpers.AssertNoError(t, err)
		_ = os.WriteFile(f.Name(), []byte("  file-token\n"), 0o600)
		defer os.Remove(f.Name())

		token, err := resolveAccessTokenFromConfig("", types.AccessTokenRef{File: f.Name()})
		testhelpers.AssertNoError(t, err)
		testhelpers.AssertEqual(t, "file-token", token)
	})

	t.Run("env empty but file present -> file used", func(t *testing.T) {
		t.Parallel()
		env := "MCPJ_TEST_TOKEN_EMPTY2"
		_ = os.Setenv(env, " ")
		defer os.Unsetenv(env)

		f, err := os.CreateTemp("", "mcpj-token-*")
		testhelpers.AssertNoError(t, err)
		_ = os.WriteFile(f.Name(), []byte("from-file"), 0o600)
		defer os.Remove(f.Name())

		token, err := resolveAccessTokenFromConfig("", types.AccessTokenRef{Env: env, File: f.Name()})
		testhelpers.AssertNoError(t, err)
		testhelpers.AssertEqual(t, "from-file", token)
	})

	t.Run("missing file -> error", func(t *testing.T) {
		t.Parallel()
		_, err := resolveAccessTokenFromConfig("", types.AccessTokenRef{File: "/no/such/file/xxxx"})
		testhelpers.AssertError(t, err)
	})

	t.Run("empty file -> error", func(t *testing.T) {
		t.Parallel()
		f, err := os.CreateTemp("", "mcpj-token-empty-*")
		testhelpers.AssertNoError(t, err)
		// leave file empty
		defer os.Remove(f.Name())

		_, err = resolveAccessTokenFromConfig("", types.AccessTokenRef{File: f.Name()})
		testhelpers.AssertError(t, err)
	})

	t.Run("nothing provided -> empty and no error", func(t *testing.T) {
		t.Parallel()
		token, err := resolveAccessTokenFromConfig("", types.AccessTokenRef{})
		testhelpers.AssertNoError(t, err)
		testhelpers.AssertEqual(t, "", token)
	})
}

func TestParseAllowListBasicCases(t *testing.T) {
	t.Parallel()

	testCases := []struct {
		name     string
		input    string
		expected []string
	}{
		{"empty string", "", []string{}},
		{"single server", "server1", []string{"server1"}},
		{"multiple servers", "server1,server2,server3", []string{"server1", "server2", "server3"}},
		{"servers with spaces", "server1, server2 , server3", []string{"server1", "server2", "server3"}},
		{"servers with empty elements", "server1,,server2", []string{"server1", "server2"}},
		{"servers with only spaces", "server1,  ,server2", []string{"server1", "server2"}},
	}

	for _, tc := range testCases {
		tc := tc
		t.Run(tc.name, func(t *testing.T) {
			t.Parallel()
			got := parseAllowList(tc.input, &cobra.Command{})
			for i, srv := range got {
				testhelpers.AssertEqual(t, tc.expected[i], srv)
			}
		})
	}
}

func TestParseAllowListWildcardWarns(t *testing.T) {
	t.Parallel()

	buf := &bytes.Buffer{}
	cmd := &cobra.Command{}
	cmd.SetOut(buf)

	input := "serverA, " + types.AllowAllMcpServers + ", serverB"
	expected := []string{"serverA", types.AllowAllMcpServers, "serverB"}

	got := parseAllowList(input, cmd)
	for i, srv := range got {
		testhelpers.AssertEqual(t, expected[i], srv)
	}

	out := buf.String()
	testhelpers.AssertTrue(t, strings.Contains(out, "NOTE:"), "expected warning to contain NOTE:")
	testhelpers.AssertTrue(t, strings.Contains(out, "access to all MCP Servers"), "expected warning body")
}
