package services

import (
	"context"
	"strings"
	"testing"
	"time"

	"clawreef/internal/models"
)

func TestGatewayTokenAliasTTLConfig(t *testing.T) {
	t.Setenv(gatewayTokenAliasTTLEnv, "")
	if got := gatewayTokenAliasTTL(); got != defaultGatewayTokenAliasTTL {
		t.Fatalf("default gateway token alias TTL = %v, want %v", got, defaultGatewayTokenAliasTTL)
	}

	t.Setenv(gatewayTokenAliasTTLEnv, "2")
	if got := gatewayTokenAliasTTL(); got != 2*time.Hour {
		t.Fatalf("configured gateway token alias TTL = %v, want 2h", got)
	}

	t.Setenv(gatewayTokenAliasTTLEnv, "0")
	if got := gatewayTokenAliasTTL(); got != 0 {
		t.Fatalf("disabled gateway token alias TTL = %v, want 0", got)
	}
}

type stubLLMModelRepository struct {
	active []models.LLMModel
}

func (r *stubLLMModelRepository) List() ([]models.LLMModel, error) {
	items := make([]models.LLMModel, len(r.active))
	copy(items, r.active)
	return items, nil
}

func (r *stubLLMModelRepository) ListActive() ([]models.LLMModel, error) {
	items := make([]models.LLMModel, len(r.active))
	copy(items, r.active)
	return items, nil
}

func (r *stubLLMModelRepository) GetByID(id int) (*models.LLMModel, error) {
	return nil, nil
}

func (r *stubLLMModelRepository) GetByDisplayName(displayName string) (*models.LLMModel, error) {
	return nil, nil
}

func (r *stubLLMModelRepository) Save(model *models.LLMModel) error {
	return nil
}

func (r *stubLLMModelRepository) Delete(id int) error {
	return nil
}

func TestBuildGatewayEnvInjectsGatewayModelCatalog(t *testing.T) {
	t.Setenv("CLAWMANAGER_LLM_GATEWAY_BASE_URL", "http://gateway.example/api/v1/gateway/llm")

	token := "igt_test_token"
	for _, instanceType := range []string{"openclaw", "hermes", "workbuddy"} {
		t.Run(instanceType, func(t *testing.T) {
			service := &instanceService{
				llmModelRepo: &stubLLMModelRepository{
					active: []models.LLMModel{
						{DisplayName: "GPT-4.1"},
						{DisplayName: "Claude 3.7 Sonnet"},
						{DisplayName: "auto"},
						{
							ProviderType:      models.ProviderTypeOpenAICompatible,
							ProtocolType:      models.ProtocolTypeOpenAICompatible,
							BaseURL:           "https://api.deepseek.com",
							ProviderModelName: "deepseek-r1",
							ReasoningEnabled:  true,
						},
					},
				},
			}

			env, err := service.buildGatewayEnv(&models.Instance{
				Type:        instanceType,
				AccessToken: &token,
			})
			if err != nil {
				t.Fatalf("buildGatewayEnv returned error: %v", err)
			}

			if env["CLAWMANAGER_LLM_BASE_URL"] != "http://gateway.example/api/v1/gateway/llm" {
				t.Fatalf("expected CLAWMANAGER_LLM_BASE_URL to use gateway base URL, got %q", env["CLAWMANAGER_LLM_BASE_URL"])
			}
			if env["CLAWMANAGER_LLM_MODEL"] != `["auto","GPT-4.1","Claude 3.7 Sonnet","deepseek-r1"]` {
				t.Fatalf("expected CLAWMANAGER_LLM_MODEL to contain injected model catalog JSON, got %q", env["CLAWMANAGER_LLM_MODEL"])
			}
			if env["CLAWMANAGER_LLM_REASONING"] != `{"Claude 3.7 Sonnet":false,"GPT-4.1":false,"auto":false,"deepseek-r1":true}` {
				t.Fatalf("expected authoritative reasoning settings, got %q", env["CLAWMANAGER_LLM_REASONING"])
			}
			if env["CLAWMANAGER_LLM_REASONING_CONTROL"] != `{"Claude 3.7 Sonnet":"","GPT-4.1":"","auto":"","deepseek-r1":"deepseek-thinking"}` {
				t.Fatalf("expected authoritative reasoning controls, got %q", env["CLAWMANAGER_LLM_REASONING_CONTROL"])
			}
			if env["OPENAI_MODEL"] != "auto" {
				t.Fatalf("expected OPENAI_MODEL to remain the default gateway alias, got %q", env["OPENAI_MODEL"])
			}
			if env["CLAWMANAGER_LLM_API_KEY"] != token || env["OPENAI_API_KEY"] != token {
				t.Fatalf("expected gateway token aliases to be preserved")
			}
		})
	}
}

func TestBuildGatewayEnvEnsuresMissingGatewayToken(t *testing.T) {
	t.Setenv("CLAWMANAGER_LLM_GATEWAY_BASE_URL", "http://gateway.example/api/v1/gateway/llm")

	instanceRepo := &stubGatewayEnvInstanceRepository{fakeRuntimeInstanceRepo: newFakeRuntimeInstanceRepo()}
	service := &instanceService{
		instanceRepo: instanceRepo,
		llmModelRepo: &stubLLMModelRepository{
			active: []models.LLMModel{{DisplayName: "auto"}},
		},
	}
	instance := &models.Instance{
		ID:     68,
		UserID: 1,
		Type:   "openclaw",
	}

	env, err := service.BuildGatewayEnv(instance)
	if err != nil {
		t.Fatalf("BuildGatewayEnv returned error: %v", err)
	}
	if instance.AccessToken == nil || *instance.AccessToken == "" {
		t.Fatal("BuildGatewayEnv did not provision instance access token")
	}
	if env["CLAWMANAGER_LLM_API_KEY"] != *instance.AccessToken {
		t.Fatalf("CLAWMANAGER_LLM_API_KEY = %q, want provisioned token %q", env["CLAWMANAGER_LLM_API_KEY"], *instance.AccessToken)
	}
	if got := instanceRepo.updated[68]; got == nil || got.AccessToken == nil || *got.AccessToken != *instance.AccessToken {
		t.Fatalf("repository update did not persist provisioned token: %#v", instanceRepo.updated[68])
	}
	alias, ok := instanceRepo.aliases[68]
	if !ok {
		t.Fatal("repository did not record provisioned token as a gateway alias")
	}
	if alias.token != *instance.AccessToken {
		t.Fatalf("gateway alias token = %q, want provisioned token %q", alias.token, *instance.AccessToken)
	}
	if alias.expiresAt.Before(time.Now().Add(6 * 24 * time.Hour)) {
		t.Fatalf("gateway alias expiry = %v, want roughly the default compatibility window", alias.expiresAt)
	}
}

func TestBuildGatewayEnvRecordsExistingGatewayTokenAlias(t *testing.T) {
	t.Setenv("CLAWMANAGER_LLM_GATEWAY_BASE_URL", "http://gateway.example/api/v1/gateway/llm")

	token := "igt_existing_token"
	instanceRepo := &stubGatewayEnvInstanceRepository{fakeRuntimeInstanceRepo: newFakeRuntimeInstanceRepo()}
	service := &instanceService{
		instanceRepo: instanceRepo,
		llmModelRepo: &stubLLMModelRepository{
			active: []models.LLMModel{{DisplayName: "auto"}},
		},
	}

	env, err := service.BuildGatewayEnv(&models.Instance{
		ID:          69,
		UserID:      1,
		Type:        "hermes",
		AccessToken: &token,
	})
	if err != nil {
		t.Fatalf("BuildGatewayEnv returned error: %v", err)
	}
	if env["CLAWMANAGER_LLM_API_KEY"] != token {
		t.Fatalf("CLAWMANAGER_LLM_API_KEY = %q, want existing token", env["CLAWMANAGER_LLM_API_KEY"])
	}
	alias, ok := instanceRepo.aliases[69]
	if !ok {
		t.Fatal("repository did not record existing token as a gateway alias")
	}
	if alias.token != token {
		t.Fatalf("gateway alias token = %q, want existing token %q", alias.token, token)
	}
}
func TestBuildGatewayEnvMergesEnvironmentOverrides(t *testing.T) {
	t.Setenv("CLAWMANAGER_LLM_GATEWAY_BASE_URL", "http://gateway.example/api/v1/gateway/llm")
	token := "igt_test_token"
	raw, err := marshalEnvironmentOverrides(map[string]string{
		"CLAWMANAGER_TEAM_ENABLED":   "true",
		"CLAWMANAGER_TEAM_MEMBER_ID": "lite-worker",
		"CUSTOM_GATEWAY_ENV":         "enabled",
	})
	if err != nil {
		t.Fatalf("marshalEnvironmentOverrides returned error: %v", err)
	}
	service := &instanceService{
		llmModelRepo: &stubLLMModelRepository{
			active: []models.LLMModel{{DisplayName: "auto"}},
		},
	}

	env, err := service.BuildGatewayEnv(&models.Instance{
		ID:                       88,
		Type:                     "openclaw",
		RuntimeType:              RuntimeBackendGateway,
		AccessToken:              &token,
		EnvironmentOverridesJSON: raw,
	})
	if err != nil {
		t.Fatalf("BuildGatewayEnv returned error: %v", err)
	}

	if env["CLAWMANAGER_TEAM_ENABLED"] != "true" || env["CLAWMANAGER_TEAM_MEMBER_ID"] != "lite-worker" {
		t.Fatalf("expected Team environment overrides to be merged into gateway env, got %#v", env)
	}
	if env["CUSTOM_GATEWAY_ENV"] != "enabled" {
		t.Fatalf("expected custom gateway environment override to be merged, got %#v", env)
	}
	if env["CLAWMANAGER_LLM_API_KEY"] != token {
		t.Fatalf("expected gateway token env to remain available")
	}
	if env["CLAWMANAGER_RUNTIME_TYPE"] != RuntimeBackendGateway {
		t.Fatalf("expected runtime type marker %q, got %q", RuntimeBackendGateway, env["CLAWMANAGER_RUNTIME_TYPE"])
	}
}

func TestBuildGatewayEnvSkipsUnmanagedRuntime(t *testing.T) {
	token := "igt_test_token"
	service := &instanceService{}

	env, err := service.buildGatewayEnv(&models.Instance{
		Type:        "ubuntu",
		AccessToken: &token,
	})
	if err != nil {
		t.Fatalf("buildGatewayEnv returned error: %v", err)
	}
	if len(env) != 0 {
		t.Fatalf("expected unmanaged runtime to receive no gateway env, got %#v", env)
	}
}

type gatewayTokenAliasRecord struct {
	token     string
	expiresAt time.Time
}

type stubGatewayEnvInstanceRepository struct {
	*fakeRuntimeInstanceRepo
	updated map[int]*models.Instance
	aliases map[int]gatewayTokenAliasRecord
}

func (r *stubGatewayEnvInstanceRepository) Update(instance *models.Instance) error {
	if r.updated == nil {
		r.updated = map[int]*models.Instance{}
	}
	copy := *instance
	r.updated[instance.ID] = &copy
	return nil
}

func (r *stubGatewayEnvInstanceRepository) UpsertGatewayTokenAlias(ctx context.Context, instanceID int, accessToken string, expiresAt time.Time) error {
	if r.aliases == nil {
		r.aliases = map[int]gatewayTokenAliasRecord{}
	}
	r.aliases[instanceID] = gatewayTokenAliasRecord{token: accessToken, expiresAt: expiresAt}
	return nil
}
func TestBuildAgentEnvInjectsHermesAgentConfig(t *testing.T) {
	t.Setenv("CLAWMANAGER_AGENT_CONTROL_BASE_URL", "http://agent-control.example")

	token := "agt_boot_test_token"
	service := &instanceService{}

	env, err := service.buildAgentEnv(&models.Instance{
		ID:                  24,
		Type:                "hermes",
		DiskGB:              20,
		AgentBootstrapToken: &token,
	})
	if err != nil {
		t.Fatalf("buildAgentEnv returned error: %v", err)
	}

	if env["CLAWMANAGER_AGENT_ENABLED"] != "true" {
		t.Fatalf("expected Hermes agent to be enabled")
	}
	if env["CLAWMANAGER_AGENT_BASE_URL"] != "http://agent-control.example" {
		t.Fatalf("expected Hermes agent base URL to be injected, got %q", env["CLAWMANAGER_AGENT_BASE_URL"])
	}
	if env["CLAWMANAGER_AGENT_BOOTSTRAP_TOKEN"] != token {
		t.Fatalf("expected Hermes agent bootstrap token to be injected")
	}
	if env["CLAWMANAGER_AGENT_INSTANCE_ID"] != "24" {
		t.Fatalf("expected Hermes instance id to be injected, got %q", env["CLAWMANAGER_AGENT_INSTANCE_ID"])
	}
	if env["CLAWMANAGER_AGENT_PERSISTENT_DIR"] != "/config/.hermes" {
		t.Fatalf("expected Hermes persistent dir /config/.hermes, got %q", env["CLAWMANAGER_AGENT_PERSISTENT_DIR"])
	}
	if env["CLAWMANAGER_AGENT_DISK_LIMIT_BYTES"] != "21474836480" {
		t.Fatalf("expected Hermes disk limit bytes to be injected, got %q", env["CLAWMANAGER_AGENT_DISK_LIMIT_BYTES"])
	}
}

func TestPersistentVolumeMountPathNormalizesManagedDesktopRuntimes(t *testing.T) {
	for _, instanceType := range []string{"openclaw", "ubuntu", "webtop", "hermes", "workbuddy"} {
		t.Run(instanceType, func(t *testing.T) {
			got := persistentVolumeMountPath(&models.Instance{
				Type:      instanceType,
				MountPath: "/data",
			})
			if got != "/config" {
				t.Fatalf("expected %s PVC mount path /config, got %q", instanceType, got)
			}
		})
	}
}

func TestManagedRuntimePersistentDirKeepsHermesSubdirectory(t *testing.T) {
	got := managedRuntimePersistentDir(&models.Instance{
		Type:      "hermes",
		MountPath: "/config",
	})
	if got != "/config/.hermes" {
		t.Fatalf("expected Hermes persistent dir /config/.hermes, got %q", got)
	}
}

func TestWorkbuddySupportsManagedRuntimeInjection(t *testing.T) {
	if !supportsManagedRuntimeIntegration("workbuddy") {
		t.Fatal("expected Workbuddy to support managed runtime integration")
	}
	if !supportsRuntimeConfigInjection("workbuddy") {
		t.Fatal("expected Workbuddy to support runtime config injection")
	}

	t.Setenv("CLAWMANAGER_AGENT_CONTROL_BASE_URL", "http://agent-control.example")
	token := "agt_boot_workbuddy"
	env, err := (&instanceService{}).buildAgentEnv(&models.Instance{
		ID:                  923,
		Type:                "workbuddy",
		DiskGB:              20,
		MountPath:           "/config",
		AgentBootstrapToken: &token,
	})
	if err != nil {
		t.Fatalf("buildAgentEnv returned error: %v", err)
	}
	if env["CLAWMANAGER_AGENT_RUNTIME_TYPE"] != "workbuddy" {
		t.Fatalf("expected Workbuddy agent runtime type, got %q", env["CLAWMANAGER_AGENT_RUNTIME_TYPE"])
	}
	if env["CLAWMANAGER_AGENT_PERSISTENT_DIR"] != "/config" {
		t.Fatalf("expected Workbuddy agent persistent dir /config, got %q", env["CLAWMANAGER_AGENT_PERSISTENT_DIR"])
	}
}

func TestRuntimeVolumeInitScriptsAddsHermesLayoutMigration(t *testing.T) {
	scripts := runtimeVolumeInitScripts("hermes", "/config")
	if len(scripts) != 1 {
		t.Fatalf("expected one Hermes volume init script, got %d", len(scripts))
	}
	if scripts[0].Name != "data" || scripts[0].MountPath != "/config" {
		t.Fatalf("unexpected Hermes volume init script: %#v", scripts[0])
	}
	if !strings.Contains(scripts[0].Script, `target="$base/.hermes"`) {
		t.Fatalf("expected Hermes init script to target /config/.hermes, got %s", scripts[0].Script)
	}
}

func TestResolveGatewayModelInjectionRequiresActiveModels(t *testing.T) {
	service := &instanceService{
		llmModelRepo: &stubLLMModelRepository{},
	}

	injection, err := service.resolveGatewayModelInjection()
	if err == nil {
		t.Fatalf("expected resolveGatewayModelInjection to fail when no active models exist, got %#v", injection)
	}
}

func TestSecurityModeForInstance(t *testing.T) {
	service := &instanceService{}

	if got := service.securityModeForInstance("openclaw"); got != "chromium-compat" {
		t.Fatalf("expected openclaw to use chromium compat mode, got %q", got)
	}
	if got := service.securityModeForInstance("ubuntu"); got != "default" {
		t.Fatalf("expected ubuntu to use default security mode, got %q", got)
	}

	service.allowPrivilegedPods = true
	if got := service.securityModeForInstance("openclaw"); got != "privileged" {
		t.Fatalf("expected explicit privileged override to win, got %q", got)
	}
}
