package config

import (
	"testing"
	"time"

	"github.com/stretchr/testify/require"
	"gopkg.in/yaml.v3"
)

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

	conf := Default()

	require.Equal(t, ":9090", conf.RPCServer.Address)
	require.Equal(t, ":8080", conf.RPCGatewayServer.Address)
	require.Equal(t, ":9090", conf.RPCGatewayServer.RPCAddress)
	require.Empty(t, conf.Gateway.EventListeners)
	require.Equal(t, "codex", conf.AgentRuntime.DefaultAgent)
	require.Equal(t, 2, conf.TaskService.MaxConcurrency)
}

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

	var conf RootConfig
	err := yaml.Unmarshal([]byte(`
rpcServer:
  address: :19090
rpcGatewayServer:
  address: :18080
  rpcAddress: :19090
gateway:
  eventListeners:
    ticket-manager:
      sources:
        - type: lark
          instanceID: ticket-manager
        - type: application
          instanceID: ticket-manager
      endpoint: http://ticket-manager/events
agentRuntime:
  defaultAgent: codex
  agents:
    codex:
      provider: codex_cli
      codexCLI:
        codexHome: /tmp/codex
        sandboxMode: workspace-write
taskService:
  maxConcurrency: 4
clients:
  core:
    address: core-server:9090
`), &conf)

	require.NoError(t, err)
	require.Equal(t, ":19090", conf.RPCServer.Address)
	require.Equal(t, ":18080", conf.RPCGatewayServer.Address)
	require.Len(t, conf.Gateway.EventListeners["ticket-manager"].Sources, 2)
	require.Equal(t, "ticket-manager", conf.Gateway.EventListeners["ticket-manager"].Sources[0].InstanceID)
	require.Equal(t, MessageSourceApplication, conf.Gateway.EventListeners["ticket-manager"].Sources[1].Type)
	require.Equal(t, "http://ticket-manager/events", conf.Gateway.EventListeners["ticket-manager"].Endpoint)
	require.Equal(t, "/tmp/codex", conf.AgentRuntime.Agents["codex"].CodexCLI.CodexHome)
	require.Equal(t, "codex", conf.AgentRuntime.DefaultAgent)
	require.Equal(t, 4, conf.TaskService.MaxConcurrency)
	require.Equal(t, "core-server:9090", conf.Clients.Core.Address)
}

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

	var conf RootConfig
	err := yaml.Unmarshal([]byte(`
agentRuntime:
  agents:
    codex:
      provider: codex_app_server
      codexAppServer:
        endpoint: ws://agent-codex:4500
        token: shared-token
`), &conf)

	require.NoError(t, err)
	require.Equal(t, "shared-token", conf.AgentRuntime.Agents["codex"].CodexAppServer.Token)
}

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

	var conf RootConfig
	err := yaml.Unmarshal([]byte(`
agentRuntime:
  defaultAgent: codex
  agents:
    codex:
      provider: codex_app_server
      codexAppServer:
        endpoint: ws://agent-codex:4500
        token: shared-token
        timeout: 10m
        model: qwen3.8-max
        sandboxMode: workspace-write
        approvalPolicy: never
    dispatch-triage:
      provider: codex_app_server
      codexAppServer:
        endpoint: ws://agent-codex:4500
        token: shared-token
        timeout: 3m
        model: qwen3.8-flash
        sandboxMode: read-only
        approvalPolicy: never
`), &conf)

	require.NoError(t, err)
	require.Equal(t, "codex", conf.AgentRuntime.DefaultAgent)
	require.Equal(t, "qwen3.8-max", conf.AgentRuntime.Agents["codex"].CodexAppServer.Model)
	triage := conf.AgentRuntime.Agents["dispatch-triage"].CodexAppServer
	require.Equal(t, "ws://agent-codex:4500", triage.Endpoint)
	require.Equal(t, "shared-token", triage.Token)
	require.Equal(t, 3*time.Minute, triage.Timeout)
	require.Equal(t, "qwen3.8-flash", triage.Model)
	require.Equal(t, "read-only", triage.SandboxMode)
	require.Equal(t, "never", triage.ApprovalPolicy)
}

func TestNormalizeExpandsHomeAndUnixEndpoint(t *testing.T) {
	t.Setenv("HOME", "/tmp/agent-gateway-home")
	conf := validConfig()
	conf.AgentRuntime.Agents = map[string]AgentProviderConfig{
		"cli": {
			Provider: AgentProviderCodexCLI,
			CodexCLI: &CodexCLIConfig{
				Binary:         "codex",
				Home:           "~",
				CodexHome:      "~/.codex",
				Timeout:        time.Minute,
				SandboxMode:    "workspace-write",
				ApprovalPolicy: "never",
			},
		},
		"app-server": {
			Provider: AgentProviderCodexAppServer,
			CodexAppServer: &CodexAppServerConfig{
				Endpoint:       "unix://~/.codex/app-server.sock",
				Timeout:        time.Minute,
				SandboxMode:    "workspace-write",
				ApprovalPolicy: "never",
			},
		},
	}
	conf.AgentRuntime.DefaultAgent = "cli"

	conf.Normalize()

	cli := conf.AgentRuntime.Agents["cli"].CodexCLI
	require.Equal(t, "/tmp/agent-gateway-home", cli.Home)
	require.Equal(t, "/tmp/agent-gateway-home/.codex", cli.CodexHome)
	appServer := conf.AgentRuntime.Agents["app-server"].CodexAppServer
	require.Equal(t, "unix:///tmp/agent-gateway-home/.codex/app-server.sock", appServer.Endpoint)
}

func TestNormalizeAppliesProviderDefaults(t *testing.T) {
	t.Setenv("HOME", "/tmp/agent-gateway-home")
	conf := validConfig()
	conf.AgentRuntime.Agents["codex"] = AgentProviderConfig{
		Provider: AgentProviderCodexCLI,
		CodexCLI: &CodexCLIConfig{},
	}

	conf.Normalize()

	provider := conf.AgentRuntime.Agents["codex"].CodexCLI
	require.Equal(t, "codex", provider.Binary)
	require.Equal(t, "/tmp/agent-gateway-home", provider.Home)
	require.Equal(t, "/tmp/agent-gateway-home/.codex", provider.CodexHome)
	require.Equal(t, 10*time.Minute, provider.Timeout)
	require.Equal(t, "workspace-write", provider.SandboxMode)
	require.Equal(t, "on-request", provider.ApprovalPolicy)
	require.NoError(t, conf.Validate())
}

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

	conf := validConfig()
	conf.Gateway.EventListeners["ticket-manager"].Timeout = 0

	conf.Normalize()

	require.Equal(t, 5*time.Second, conf.Gateway.EventListeners["ticket-manager"].Timeout)
}

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

	conf := validConfig()
	provider := conf.AgentRuntime.Agents["codex"]
	provider.Sidecar = &AgentSidecarConfig{Endpoint: "agent-codex:9091"}
	conf.AgentRuntime.Agents["codex"] = provider

	conf.Normalize()

	require.Equal(t, 10*time.Second, conf.AgentRuntime.Agents["codex"].Sidecar.Timeout)
}

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

	t.Run("valid", func(t *testing.T) {
		conf := validConfig()
		require.NoError(t, conf.Validate())
	})
	t.Run("application event listener", func(t *testing.T) {
		conf := validConfig()
		conf.Gateway.EventListeners["ticket-manager"].Sources[0].Type = MessageSourceApplication
		require.NoError(t, conf.Validate())
	})
	t.Run("event listener sources are required", func(t *testing.T) {
		conf := validConfig()
		conf.Gateway.EventListeners["ticket-manager"].Sources = nil
		require.Error(t, conf.Validate())
	})
	t.Run("legacy event listener source is rejected", func(t *testing.T) {
		conf := validConfig()
		conf.Gateway.EventListeners["ticket-manager"].Sources = nil
		err := yaml.Unmarshal([]byte(`
gateway:
  eventListeners:
    ticket-manager:
      source:
        type: lark
        instanceID: ticket-manager
      endpoint: http://ticket-manager/events
`), &conf)
		require.NoError(t, err)
		require.Error(t, conf.Validate())
	})
	t.Run("default provider must exist", func(t *testing.T) {
		conf := validConfig()
		conf.AgentRuntime.DefaultAgent = "missing"
		require.ErrorContains(t, conf.Validate(), `default agent "missing" is not registered`)
	})
	t.Run("Core Server address is required", func(t *testing.T) {
		conf := validConfig()
		conf.Clients.Core.Address = ""
		require.Error(t, conf.Validate())
	})
	t.Run("Sidecar endpoint is required when configured", func(t *testing.T) {
		conf := validConfig()
		provider := conf.AgentRuntime.Agents["codex"]
		provider.Sidecar = &AgentSidecarConfig{Timeout: time.Second}
		conf.AgentRuntime.Agents["codex"] = provider
		require.Error(t, conf.Validate())
	})
	t.Run("Sidecar remains optional", func(t *testing.T) {
		conf := validConfig()
		require.Nil(t, conf.AgentRuntime.Agents["codex"].Sidecar)
		require.NoError(t, conf.Validate())
	})
	t.Run("app server endpoint scheme", func(t *testing.T) {
		conf := validConfig()
		conf.AgentRuntime.Agents["codex"] = AgentProviderConfig{
			Provider: AgentProviderCodexAppServer,
			CodexAppServer: &CodexAppServerConfig{
				Endpoint:       "http://localhost:8080",
				Timeout:        time.Minute,
				SandboxMode:    "workspace-write",
				ApprovalPolicy: "never",
			},
		}
		require.ErrorContains(t, conf.Validate(), "must use unix:// or ws://")
	})
	t.Run("websocket app server token", func(t *testing.T) {
		conf := validConfig()
		conf.AgentRuntime.Agents["codex"] = AgentProviderConfig{
			Provider: AgentProviderCodexAppServer,
			CodexAppServer: &CodexAppServerConfig{
				Endpoint:       "ws://agent-codex:4500",
				Timeout:        time.Minute,
				SandboxMode:    "workspace-write",
				ApprovalPolicy: "never",
			},
		}
		require.ErrorContains(t, conf.Validate(), "token is required for ws:// endpoint")

		provider := conf.AgentRuntime.Agents["codex"]
		provider.CodexAppServer.Token = "shared-token"
		conf.AgentRuntime.Agents["codex"] = provider
		require.NoError(t, conf.Validate())
	})
	t.Run("unix app server does not require token", func(t *testing.T) {
		conf := validConfig()
		conf.AgentRuntime.Agents["codex"] = AgentProviderConfig{
			Provider: AgentProviderCodexAppServer,
			CodexAppServer: &CodexAppServerConfig{
				Endpoint:       "unix:///tmp/codex-app-server.sock",
				Timeout:        time.Minute,
				SandboxMode:    "workspace-write",
				ApprovalPolicy: "never",
			},
		}
		require.NoError(t, conf.Validate())
	})
	t.Run("event listener endpoint scheme", func(t *testing.T) {
		conf := validConfig()
		conf.Gateway.EventListeners["ticket-manager"].Endpoint = "ftp://ticket-manager/events"
		require.ErrorContains(t, conf.Validate(), "endpoint must use http:// or https://")
	})
}

func validConfig() RootConfig {
	conf := Default()
	conf.Database.Host = "127.0.0.1"
	conf.Database.Port = 3306
	conf.Database.User = "root"
	conf.Database.Password = "password"
	conf.Database.Database = "agent_gateway"
	conf.Gateway.EventListeners = map[string]*GatewayEventListenerConfig{
		"ticket-manager": {
			Sources: []MessageSourceConfig{{
				Type:       MessageSourceLark,
				InstanceID: "ticket-manager",
			}},
			Endpoint: "http://ticket-manager/events",
			Timeout:  5 * time.Second,
		},
	}
	conf.AgentRuntime.Agents = map[string]AgentProviderConfig{
		"codex": {
			Provider: AgentProviderCodexCLI,
			CodexCLI: &CodexCLIConfig{
				Binary:         "codex",
				Home:           "~",
				CodexHome:      "~/.codex",
				Timeout:        time.Minute,
				SandboxMode:    "workspace-write",
				ApprovalPolicy: "never",
			},
		},
	}
	conf.Clients.Core = CoreClientConfig{Address: "core-server:9090"}
	return conf
}
