package docker

import (
	"strings"
	"testing"

	"github.com/stretchr/testify/assert"
	"github.com/stretchr/testify/require"
)

func TestRuntimeEnv_Defaults(t *testing.T) {
	env, err := RuntimeEnv(RuntimeEnvOpts{})
	require.NoError(t, err)

	assert.Contains(t, env, "EDITOR=nano")
	assert.Contains(t, env, "VISUAL=nano")
}

func TestRuntimeEnv_EditorOverride(t *testing.T) {
	env, err := RuntimeEnv(RuntimeEnvOpts{
		Editor: "vim",
		Visual: "code",
	})
	require.NoError(t, err)

	assert.Contains(t, env, "EDITOR=vim")
	assert.Contains(t, env, "VISUAL=code")
}

func TestRuntimeEnv_FirewallEnabled(t *testing.T) {
	env, err := RuntimeEnv(RuntimeEnvOpts{
		FirewallEnabled: true,
	})
	require.NoError(t, err)

	assert.Contains(t, env, "CLAWKER_FIREWALL_ENABLED=true")
}

func TestRuntimeEnv_FirewallDisabled(t *testing.T) {
	env, err := RuntimeEnv(RuntimeEnvOpts{
		FirewallEnabled: false,
	})
	require.NoError(t, err)

	for _, e := range env {
		assert.False(t, strings.HasPrefix(e, "CLAWKER_FIREWALL_ENABLED="),
			"should not set CLAWKER_FIREWALL_ENABLED when disabled")
	}
}

func TestRuntimeEnv_AgentEnv(t *testing.T) {
	env, err := RuntimeEnv(RuntimeEnvOpts{
		AgentEnv: map[string]string{"FOO": "bar", "BAZ": "qux"},
	})
	require.NoError(t, err)

	assert.Contains(t, env, "FOO=bar")
	assert.Contains(t, env, "BAZ=qux")
}

func TestRuntimeEnv_InstructionEnv(t *testing.T) {
	env, err := RuntimeEnv(RuntimeEnvOpts{
		InstructionEnv: map[string]string{"NODE_ENV": "production"},
	})
	require.NoError(t, err)

	assert.Contains(t, env, "NODE_ENV=production")
}

func TestRuntimeEnv_NilMaps(t *testing.T) {
	env, err := RuntimeEnv(RuntimeEnvOpts{})
	require.NoError(t, err)

	assert.Contains(t, env, "EDITOR=nano")
}

func TestRuntimeEnv_Deterministic(t *testing.T) {
	opts := RuntimeEnvOpts{
		Editor:          "vim",
		FirewallEnabled: true,
		AgentEnv:        map[string]string{"A": "1", "B": "2"},
	}

	env1, err := RuntimeEnv(opts)
	require.NoError(t, err)
	env2, err := RuntimeEnv(opts)
	require.NoError(t, err)

	assert.Equal(t, env1, env2, "RuntimeEnv should produce consistent output")
}

func TestRuntimeEnv_Precedence(t *testing.T) {
	env, err := RuntimeEnv(RuntimeEnvOpts{
		AgentEnv:       map[string]string{"EDITOR": "vim", "SHARED": "from-agent"},
		InstructionEnv: map[string]string{"SHARED": "from-instructions"},
	})
	require.NoError(t, err)

	assert.Contains(t, env, "EDITOR=vim", "agent env should override base default")
	assert.Contains(t, env, "SHARED=from-instructions", "instruction env should override agent env")
}

func TestRuntimeEnv_256Color(t *testing.T) {
	env, err := RuntimeEnv(RuntimeEnvOpts{
		Is256Color: true,
	})
	require.NoError(t, err)

	assert.Contains(t, env, "TERM=xterm-256color")
}

func TestRuntimeEnv_TrueColor(t *testing.T) {
	env, err := RuntimeEnv(RuntimeEnvOpts{
		Is256Color: true,
		TrueColor:  true,
	})
	require.NoError(t, err)

	assert.Contains(t, env, "TERM=xterm-256color")
	assert.Contains(t, env, "COLORTERM=truecolor")
}

func TestRuntimeEnv_NoColorCapabilities(t *testing.T) {
	env, err := RuntimeEnv(RuntimeEnvOpts{
		Is256Color: false,
		TrueColor:  false,
	})
	require.NoError(t, err)

	for _, e := range env {
		assert.False(t, strings.HasPrefix(e, "TERM="),
			"should not set TERM when no color capabilities")
		assert.False(t, strings.HasPrefix(e, "COLORTERM="),
			"should not set COLORTERM when no color capabilities")
	}
}

func TestRuntimeEnv_AgentEnvOverridesTerm(t *testing.T) {
	env, err := RuntimeEnv(RuntimeEnvOpts{
		Is256Color: true,
		TrueColor:  true,
		AgentEnv:   map[string]string{"TERM": "xterm", "COLORTERM": ""},
	})
	require.NoError(t, err)

	assert.Contains(t, env, "TERM=xterm", "agent env should override terminal detection")
	assert.Contains(t, env, "COLORTERM=", "agent env should override terminal detection")
}

func TestRuntimeEnv_TrueColorWithout256Color(t *testing.T) {
	// Edge case: TrueColor=true with Is256Color=false
	// This is a caller bug (truecolor implies 256), but the function should handle it gracefully
	env, err := RuntimeEnv(RuntimeEnvOpts{
		Is256Color: false,
		TrueColor:  true,
	})
	require.NoError(t, err)

	// Should set COLORTERM but not TERM (following the literal flags)
	assert.Contains(t, env, "COLORTERM=truecolor")
	for _, e := range env {
		assert.False(t, strings.HasPrefix(e, "TERM="),
			"should not set TERM when Is256Color=false")
	}
}

func TestRuntimeEnv_ClawkerIdentity(t *testing.T) {
	env, err := RuntimeEnv(RuntimeEnvOpts{
		Version:         "0.5.0",
		Project:         "myproject",
		Agent:           "dev",
		WorkspaceMode:   "bind",
		WorkspaceSource: "/home/user/myproject",
	})
	require.NoError(t, err)

	assert.Contains(t, env, "CLAWKER_VERSION=0.5.0")
	assert.Contains(t, env, "CLAWKER_PROJECT=myproject")
	assert.Contains(t, env, "CLAWKER_AGENT=dev")
	assert.Contains(t, env, "CLAWKER_WORKSPACE_MODE=bind")
	assert.Contains(t, env, "CLAWKER_WORKSPACE_SOURCE=/home/user/myproject")
}

func TestRuntimeEnv_ClawkerIdentitySnapshotMode(t *testing.T) {
	env, err := RuntimeEnv(RuntimeEnvOpts{
		Project:         "myapp",
		Agent:           "dev",
		WorkspaceMode:   "snapshot",
		WorkspaceSource: "/var/clawker/worktrees/feature-branch",
	})
	require.NoError(t, err)

	assert.Contains(t, env, "CLAWKER_PROJECT=myapp")
	assert.Contains(t, env, "CLAWKER_AGENT=dev")
	assert.Contains(t, env, "CLAWKER_WORKSPACE_MODE=snapshot")
	assert.Contains(t, env, "CLAWKER_WORKSPACE_SOURCE=/var/clawker/worktrees/feature-branch")
}

func TestRuntimeEnv_ClawkerIdentityEmpty(t *testing.T) {
	// When identity fields are empty, they should not be set
	env, err := RuntimeEnv(RuntimeEnvOpts{
		Project:         "",
		Agent:           "",
		WorkspaceMode:   "",
		WorkspaceSource: "",
	})
	require.NoError(t, err)

	for _, e := range env {
		assert.False(t, strings.HasPrefix(e, "CLAWKER_PROJECT="),
			"should not set CLAWKER_PROJECT when empty")
		assert.False(t, strings.HasPrefix(e, "CLAWKER_AGENT="),
			"should not set CLAWKER_AGENT when empty")
		assert.False(t, strings.HasPrefix(e, "CLAWKER_WORKSPACE_MODE="),
			"should not set CLAWKER_WORKSPACE_MODE when empty")
		assert.False(t, strings.HasPrefix(e, "CLAWKER_WORKSPACE_SOURCE="),
			"should not set CLAWKER_WORKSPACE_SOURCE when empty")
	}
}

func TestRuntimeEnv_SSHForwardingEnabled(t *testing.T) {
	env, err := RuntimeEnv(RuntimeEnvOpts{
		SSHForwardingEnabled: true,
	})
	require.NoError(t, err)

	assert.Contains(t, env, "SSH_AUTH_SOCK=/home/claude/.ssh/agent.sock",
		"SSH_AUTH_SOCK should be set when SSH forwarding enabled")

	// Should also have CLAWKER_REMOTE_SOCKETS with ssh-agent entry
	var found bool
	for _, e := range env {
		if val, ok := strings.CutPrefix(e, "CLAWKER_REMOTE_SOCKETS="); ok {
			found = true
			assert.Contains(t, val, `"type":"ssh-agent"`)
			assert.Contains(t, val, `"path":"/home/claude/.ssh/agent.sock"`)
		}
	}
	require.True(t, found, "expected CLAWKER_REMOTE_SOCKETS env var")
}

func TestRuntimeEnv_GPGForwardingEnabled(t *testing.T) {
	env, err := RuntimeEnv(RuntimeEnvOpts{
		GPGForwardingEnabled: true,
	})
	require.NoError(t, err)

	// Should NOT have SSH_AUTH_SOCK
	for _, e := range env {
		assert.False(t, strings.HasPrefix(e, "SSH_AUTH_SOCK="),
			"should not set SSH_AUTH_SOCK when only GPG forwarding enabled")
	}

	// Should have CLAWKER_REMOTE_SOCKETS with gpg-agent entry
	var found bool
	for _, e := range env {
		if val, ok := strings.CutPrefix(e, "CLAWKER_REMOTE_SOCKETS="); ok {
			found = true
			assert.Contains(t, val, `"type":"gpg-agent"`)
			assert.Contains(t, val, `"path":"/home/claude/.gnupg/S.gpg-agent"`)
		}
	}
	require.True(t, found, "expected CLAWKER_REMOTE_SOCKETS env var")

	// Should override gpg.program via GIT_CONFIG env vars
	assert.Contains(t, env, "GIT_CONFIG_COUNT=1")
	assert.Contains(t, env, "GIT_CONFIG_KEY_0=gpg.program")
	assert.Contains(t, env, "GIT_CONFIG_VALUE_0=/usr/bin/gpg")
}

func TestRuntimeEnv_BothForwardingEnabled(t *testing.T) {
	env, err := RuntimeEnv(RuntimeEnvOpts{
		GPGForwardingEnabled: true,
		SSHForwardingEnabled: true,
	})
	require.NoError(t, err)

	assert.Contains(t, env, "SSH_AUTH_SOCK=/home/claude/.ssh/agent.sock")

	// Should have both sockets in CLAWKER_REMOTE_SOCKETS
	var found bool
	for _, e := range env {
		if val, ok := strings.CutPrefix(e, "CLAWKER_REMOTE_SOCKETS="); ok {
			found = true
			assert.Contains(t, val, `"type":"gpg-agent"`)
			assert.Contains(t, val, `"type":"ssh-agent"`)
		}
	}
	require.True(t, found, "expected CLAWKER_REMOTE_SOCKETS env var")
}

func TestRuntimeEnv_NoForwardingNoSocketVars(t *testing.T) {
	env, err := RuntimeEnv(RuntimeEnvOpts{})
	require.NoError(t, err)

	for _, e := range env {
		assert.False(t, strings.HasPrefix(e, "CLAWKER_REMOTE_SOCKETS="),
			"should not set CLAWKER_REMOTE_SOCKETS when no forwarding")
		assert.False(t, strings.HasPrefix(e, "SSH_AUTH_SOCK="),
			"should not set SSH_AUTH_SOCK when no forwarding")
		assert.False(t, strings.HasPrefix(e, "GIT_CONFIG_COUNT="),
			"should not set GIT_CONFIG_COUNT when no forwarding")
	}
}

func TestRuntimeEnv_ClawkerIdentityPartial(t *testing.T) {
	// Partial identity (e.g., orphaned project with no project name)
	env, err := RuntimeEnv(RuntimeEnvOpts{
		Agent:           "dev",
		WorkspaceMode:   "bind",
		WorkspaceSource: "/tmp/orphaned-dir",
	})
	require.NoError(t, err)

	// Should NOT have project
	for _, e := range env {
		assert.False(t, strings.HasPrefix(e, "CLAWKER_PROJECT="),
			"should not set CLAWKER_PROJECT when empty")
	}

	// Should have agent, mode, and source
	assert.Contains(t, env, "CLAWKER_AGENT=dev")
	assert.Contains(t, env, "CLAWKER_WORKSPACE_MODE=bind")
	assert.Contains(t, env, "CLAWKER_WORKSPACE_SOURCE=/tmp/orphaned-dir")
}

func TestRuntimeEnv_ClawkerVersion(t *testing.T) {
	env, err := RuntimeEnv(RuntimeEnvOpts{Version: "1.2.3"})
	require.NoError(t, err)
	assert.Contains(t, env, "CLAWKER_VERSION=1.2.3")
}

func TestRuntimeEnv_ClawkerVersionDev(t *testing.T) {
	env, err := RuntimeEnv(RuntimeEnvOpts{Version: "DEV"})
	require.NoError(t, err)
	assert.Contains(t, env, "CLAWKER_VERSION=DEV")
}

func TestRuntimeEnv_ClawkerVersionEmpty(t *testing.T) {
	env, err := RuntimeEnv(RuntimeEnvOpts{})
	require.NoError(t, err)
	for _, e := range env {
		assert.False(t, strings.HasPrefix(e, "CLAWKER_VERSION="),
			"should not set CLAWKER_VERSION when empty")
	}
}

func TestRuntimeEnv_OTELResourceAttributes(t *testing.T) {
	env, err := RuntimeEnv(RuntimeEnvOpts{
		Project: "myapp",
		Agent:   "ralph",
	})
	require.NoError(t, err)

	assert.Contains(t, env, "OTEL_RESOURCE_ATTRIBUTES=project=myapp,agent=ralph")
}

func TestRuntimeEnv_OTELResourceAttributesProjectOnly(t *testing.T) {
	env, err := RuntimeEnv(RuntimeEnvOpts{
		Project: "myapp",
	})
	require.NoError(t, err)

	assert.Contains(t, env, "OTEL_RESOURCE_ATTRIBUTES=project=myapp")
}

func TestRuntimeEnv_OTELResourceAttributesAgentOnly(t *testing.T) {
	env, err := RuntimeEnv(RuntimeEnvOpts{
		Agent: "ralph",
	})
	require.NoError(t, err)

	assert.Contains(t, env, "OTEL_RESOURCE_ATTRIBUTES=agent=ralph")
}

func TestRuntimeEnv_OTELResourceAttributesEmpty(t *testing.T) {
	env, err := RuntimeEnv(RuntimeEnvOpts{})
	require.NoError(t, err)

	for _, e := range env {
		assert.False(t, strings.HasPrefix(e, "OTEL_RESOURCE_ATTRIBUTES="),
			"should not set OTEL_RESOURCE_ATTRIBUTES when no project or agent")
	}
}

func TestRuntimeEnv_OTELResourceAttributesOverriddenByAgentEnv(t *testing.T) {
	env, err := RuntimeEnv(RuntimeEnvOpts{
		Project:  "myapp",
		Agent:    "ralph",
		AgentEnv: map[string]string{"OTEL_RESOURCE_ATTRIBUTES": "custom=value"},
	})
	require.NoError(t, err)

	assert.Contains(t, env, "OTEL_RESOURCE_ATTRIBUTES=custom=value",
		"agent env should override auto-generated resource attributes")
}

func TestRuntimeEnv_OTELResourceAttributesOverriddenByInstructionEnv(t *testing.T) {
	env, err := RuntimeEnv(RuntimeEnvOpts{
		Project:        "myapp",
		Agent:          "ralph",
		InstructionEnv: map[string]string{"OTEL_RESOURCE_ATTRIBUTES": "service.name=custom"},
	})
	require.NoError(t, err)

	assert.Contains(t, env, "OTEL_RESOURCE_ATTRIBUTES=service.name=custom",
		"instruction env should override auto-generated resource attributes")
}

func TestRuntimeEnv_TelemetryDisabledWhenMonitoringInactive(t *testing.T) {
	env, err := RuntimeEnv(RuntimeEnvOpts{
		MonitoringActive: false,
	})
	require.NoError(t, err)

	assert.Contains(t, env, "CLAUDE_CODE_ENABLE_TELEMETRY=0",
		"telemetry should be disabled when monitoring stack is not running")
}

func TestRuntimeEnv_TelemetryEnabledWhenMonitoringActive(t *testing.T) {
	env, err := RuntimeEnv(RuntimeEnvOpts{
		MonitoringActive: true,
	})
	require.NoError(t, err)

	for _, e := range env {
		assert.False(t, strings.HasPrefix(e, "CLAUDE_CODE_ENABLE_TELEMETRY="),
			"should not set CLAUDE_CODE_ENABLE_TELEMETRY when monitoring is active (Dockerfile default applies)")
	}
}
