package config

import (
	"context"
	"os"
	"path/filepath"
	"testing"
	"time"

	"github.com/stretchr/testify/require"
	"github.com/tidbcloud/aisupport/packages/sdk-go/infra/config/loader"
)

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

	conf, err := loader.NewConfigurationLoader(
		[]loader.ConfigurationSource[RootConfig]{
			loader.NewConfigurationSourceDefault[RootConfig](),
		},
		true,
	).Load(context.Background())
	require.NoError(t, err)
	require.Equal(t, 2*time.Minute, conf.Services.Workspace.Timeline.WatchInterval)
	require.Equal(t, 5*time.Minute, conf.Services.Workspace.Timeline.SyncWarRoomInterval)
	require.Equal(t, 20*time.Minute, conf.Services.Workspace.Timeline.SyncCaseWorkspaceInterval)
	require.False(t, conf.Services.Workspace.Timeline.Lark.Enabled)
	require.False(t, conf.Services.Workspace.Timeline.Lark.EventIngestionEnabled)
	require.False(t, conf.Services.Workspace.Timeline.Lark.ReconciliationEnabled)
	require.Equal(t, 72*time.Hour, conf.Services.Workspace.Timeline.Lark.IngestionLookback)
}

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

	conf, err := loader.NewConfigurationLoader(
		[]loader.ConfigurationSource[RootConfig]{
			loader.NewConfigurationSourceDefault[RootConfig](),
		},
		true,
	).Load(context.Background())
	require.NoError(t, err)
	require.Equal(t, "agent-gateway:9090", conf.Clients.AgentGateway.Address)
	require.Equal(t, 5*time.Second, conf.Clients.AgentGateway.Timeout)
	require.Empty(t, conf.Services.Agent.AgentID)
	require.Equal(t, "ticket-manager", conf.Services.Agent.InstanceID)
	require.Equal(t, ".", conf.Services.Agent.WorkingDirectory)
	require.True(t, conf.Services.Agent.GroupChatAllowed())
	require.Equal(t, 8000, conf.Services.Agent.MaxMessageSize)
	require.Equal(t, 4000, conf.Services.Agent.CommentaryMessageMergeBuffer)
}

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

	configPath := filepath.Join(t.TempDir(), "config.yaml")
	require.NoError(t, os.WriteFile(configPath, []byte(`
clients:
  agentGateway:
    address: gateway.example:9191
    timeout: 8s
services:
  agent:
    agentID: shared-agent
    instanceId: support-bot
    workingDirectory: /workspace/support
    allowGroupChat: false
    maxMessageSize: 1200
    commentaryMessageMergeBuffer: 600
  dispatch:
    triage:
      agentModel: model-triage
`), 0o600))

	conf, err := loader.NewConfigurationLoader(
		[]loader.ConfigurationSource[RootConfig]{
			loader.NewConfigurationSourceDefault[RootConfig](),
			loader.NewConfigurationSourceFile[RootConfig](configPath),
		},
		true,
	).Load(context.Background())
	require.NoError(t, err)
	require.Equal(t, "gateway.example:9191", conf.Clients.AgentGateway.Address)
	require.Equal(t, 8*time.Second, conf.Clients.AgentGateway.Timeout)
	require.Equal(t, "shared-agent", conf.Services.Agent.AgentID)
	require.Equal(t, "support-bot", conf.Services.Agent.InstanceID)
	require.Equal(t, "/workspace/support", conf.Services.Agent.WorkingDirectory)
	require.False(t, conf.Services.Agent.GroupChatAllowed())
	require.Equal(t, 1200, conf.Services.Agent.MaxMessageSize)
	require.Equal(t, 600, conf.Services.Agent.CommentaryMessageMergeBuffer)
	require.Equal(t, "model-triage", conf.Services.Dispatch.Triage.AgentModel)
}

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

	conf, err := loader.NewConfigurationLoader(
		[]loader.ConfigurationSource[RootConfig]{
			loader.NewConfigurationSourceDefault[RootConfig](),
		},
		true,
	).Load(context.Background())
	require.NoError(t, err)
	require.Empty(t, conf.Services.Dispatch.Triage.AgentModel)
	require.False(t, conf.Services.Dispatch.StalledAutoAssignmentRecovery.Enabled)
	require.Empty(t, conf.Services.Dispatch.StalledAutoAssignmentRecovery.ProjectKeys)
	require.Equal(t, 5*time.Minute, conf.Services.Dispatch.StalledAutoAssignmentRecovery.Interval)
	require.Equal(t, 24*time.Hour, conf.Services.Dispatch.StalledAutoAssignmentRecovery.Lookback)
	require.Equal(t, 5*time.Minute, conf.Services.Dispatch.StalledAutoAssignmentRecovery.MinAge)
	require.Equal(t, 3, conf.Services.Dispatch.StalledAutoAssignmentRecovery.MaxRetries)
	require.Equal(t, 50, conf.Services.Dispatch.StalledAutoAssignmentRecovery.PageSize)
	require.Equal(t, 200, conf.Services.Dispatch.StalledAutoAssignmentRecovery.MaxIssuesPerRun)
}

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

	_, err := loader.NewConfigurationLoader(
		[]loader.ConfigurationSource[DispatchStalledAutoAssignmentRecoveryConfig]{
			loader.NewConfigurationSourceDefault[DispatchStalledAutoAssignmentRecoveryConfig](),
			loader.NewConfigurationSourceMemory(&DispatchStalledAutoAssignmentRecoveryConfig{Enabled: true}),
		},
		false,
	).Load(context.Background())
	require.Error(t, err)
	require.ErrorContains(t, err, "ProjectKeys")
}

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

	configPath := filepath.Join(t.TempDir(), "config.yaml")
	require.NoError(t, os.WriteFile(configPath, []byte(`
services:
  dispatch:
    stalledAutoAssignmentRecovery:
      enabled: true
      projectKeys:
        - TCID
        - TCOC
`), 0o600))

	conf, err := loader.NewConfigurationLoader(
		[]loader.ConfigurationSource[RootConfig]{
			loader.NewConfigurationSourceDefault[RootConfig](),
			loader.NewConfigurationSourceFile[RootConfig](configPath),
		},
		true,
	).Load(context.Background())
	require.NoError(t, err)
	require.Equal(t, []string{"TCID", "TCOC"}, conf.Services.Dispatch.StalledAutoAssignmentRecovery.ProjectKeys)
}

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

	conf, err := loader.NewConfigurationLoader(
		[]loader.ConfigurationSource[RootConfig]{
			loader.NewConfigurationSourceDefault[RootConfig](),
		},
		true,
	).Load(context.Background())
	require.NoError(t, err)
	require.Equal(t, time.Hour, conf.Services.Workspace.ArchiveCaseWorkspaceInterval)
	require.Equal(t, 7*24*time.Hour, conf.Services.Workspace.ArchiveCaseWorkspaceRecoveryThreshold)
}

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

	configPath := filepath.Join(t.TempDir(), "config.yaml")
	require.NoError(t, os.WriteFile(configPath, []byte(`
services:
  workspace:
    archiveCaseWorkspaceInterval: 2h
    archiveCaseWorkspaceRecoveryThreshold: 240h
    timeline:
      watchInterval: 2m
      syncWarRoomInterval: 7m
      syncCaseWorkspaceInterval: 31m
`), 0o600))

	conf, err := loader.NewConfigurationLoader(
		[]loader.ConfigurationSource[RootConfig]{
			loader.NewConfigurationSourceDefault[RootConfig](),
			loader.NewConfigurationSourceFile[RootConfig](configPath),
		},
		true,
	).Load(context.Background())
	require.NoError(t, err)
	require.Equal(t, 2*time.Hour, conf.Services.Workspace.ArchiveCaseWorkspaceInterval)
	require.Equal(t, 10*24*time.Hour, conf.Services.Workspace.ArchiveCaseWorkspaceRecoveryThreshold)
	require.Equal(t, 2*time.Minute, conf.Services.Workspace.Timeline.WatchInterval)
	require.Equal(t, 7*time.Minute, conf.Services.Workspace.Timeline.SyncWarRoomInterval)
	require.Equal(t, 31*time.Minute, conf.Services.Workspace.Timeline.SyncCaseWorkspaceInterval)
}

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

	tests := []struct {
		name      string
		config    string
		wantField string
	}{
		{
			name:      "interval",
			wantField: "ArchiveCaseWorkspaceInterval",
			config: `services:
  workspace:
    archiveCaseWorkspaceInterval: -1h
`,
		},
		{
			name:      "recovery threshold",
			wantField: "ArchiveCaseWorkspaceRecoveryThreshold",
			config: `services:
  workspace:
    archiveCaseWorkspaceRecoveryThreshold: -1h
`,
		},
	}

	for _, tt := range tests {
		t.Run(tt.name, func(t *testing.T) {
			t.Parallel()

			configPath := filepath.Join(t.TempDir(), "config.yaml")
			require.NoError(t, os.WriteFile(configPath, []byte(tt.config), 0o600))

			_, err := loader.NewConfigurationLoader(
				[]loader.ConfigurationSource[RootConfig]{
					loader.NewConfigurationSourceDefault[RootConfig](),
					loader.NewConfigurationSourceFile[RootConfig](configPath),
				},
				false,
			).Load(context.Background())
			require.Error(t, err)
			require.ErrorContains(t, err, tt.wantField)
		})
	}
}

func TestApplicationAgentDefaults(t *testing.T) {
	t.Parallel()
	conf, err := loader.NewConfigurationLoader([]loader.ConfigurationSource[RootConfig]{loader.NewConfigurationSourceDefault[RootConfig]()}, true).Load(context.Background())
	require.NoError(t, err)
	require.Empty(t, conf.Services.Workspace.Timeline.AgentModel)
	require.Equal(t, 2*time.Minute, conf.Services.Workspace.Timeline.AgentTimeout)
	require.Empty(t, conf.Services.Workspace.Advisor.AddTask.AgentModel)
}

func TestTaskAdvisorModelSelectionConfig(t *testing.T) {
	t.Parallel()
	for _, tc := range []struct {
		name, yaml, agentModel string
	}{
		{name: "omitted", yaml: "{}"},
		{name: "empty", yaml: `agentModel: ""`},
		{name: "explicit", yaml: "agentModel: model-task-advisor", agentModel: "model-task-advisor"},
	} {
		t.Run(tc.name, func(t *testing.T) {
			t.Parallel()
			path := filepath.Join(t.TempDir(), "advisor.yaml")
			require.NoError(t, os.WriteFile(path, []byte(tc.yaml), 0o600))
			conf, err := loader.NewConfigurationLoader([]loader.ConfigurationSource[WorkspaceServiceAddTaskAdvisorConfig]{
				loader.NewConfigurationSourceDefault[WorkspaceServiceAddTaskAdvisorConfig](),
				loader.NewConfigurationSourceFile[WorkspaceServiceAddTaskAdvisorConfig](path),
			}, false).Load(context.Background())
			require.NoError(t, err)
			require.Equal(t, tc.agentModel, conf.AgentModel)
		})
	}
}

func TestTimelineModelSelectionConfig(t *testing.T) {
	t.Parallel()
	for _, tc := range []struct {
		name, yaml, agentModel string
	}{
		{name: "omitted", yaml: "{}"},
		{name: "empty", yaml: `agentModel: ""`},
		{name: "explicit", yaml: "agentModel: model-timeline", agentModel: "model-timeline"},
	} {
		t.Run(tc.name, func(t *testing.T) {
			t.Parallel()
			path := filepath.Join(t.TempDir(), "timeline.yaml")
			require.NoError(t, os.WriteFile(path, []byte(tc.yaml), 0o600))
			conf, err := loader.NewConfigurationLoader([]loader.ConfigurationSource[WorkspaceServiceTimelineConfig]{
				loader.NewConfigurationSourceDefault[WorkspaceServiceTimelineConfig](),
				loader.NewConfigurationSourceFile[WorkspaceServiceTimelineConfig](path),
			}, false).Load(context.Background())
			require.NoError(t, err)
			require.Equal(t, tc.agentModel, conf.AgentModel)
		})
	}
}

func TestAgentServiceSelectionConfig(t *testing.T) {
	t.Parallel()
	for _, tc := range []struct{ name, yaml, agentID string }{
		{name: "omitted", yaml: "{}"},
		{name: "empty", yaml: `agentID: ""`},
		{name: "explicit", yaml: "agentID: shared-agent", agentID: "shared-agent"},
	} {
		t.Run(tc.name, func(t *testing.T) {
			t.Parallel()
			path := filepath.Join(t.TempDir(), "agent.yaml")
			require.NoError(t, os.WriteFile(path, []byte(tc.yaml), 0o600))
			conf, err := loader.NewConfigurationLoader([]loader.ConfigurationSource[AgentServiceConfig]{
				loader.NewConfigurationSourceDefault[AgentServiceConfig](),
				loader.NewConfigurationSourceFile[AgentServiceConfig](path),
			}, true).Load(context.Background())
			require.NoError(t, err)
			require.Equal(t, tc.agentID, conf.AgentID)
		})
	}
}
