package devops

import (
	"testing"

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

func strPtr(s string) *string { return &s }

func testOperation() *Operation {
	manual := StartPolicyManual
	return &Operation{
		Name:   "RestartChangefeed",
		Target: TargetUndefined,
		Actions: []OperationAction{
			{Name: "RestartChangefeed", PreferStartPolicy: &manual},
			{Name: "NotifyOwner", Optional: true},
		},
		Params: map[string]ParamDefine{
			"changefeed_id": {Type: ParamTypeString},
			"cluster_type":  {Type: ParamTypeString, Enum: []string{"CHANGEFEED", "CHANGEFEED_NG"}},
			"replicas":      {Type: ParamTypeInt, Optional: true},
			"labels":        {Type: ParamTypeMap, Optional: true},
			"force":         {Type: ParamTypeString, Default: strPtr("false")},
			"oncall":        {Type: ParamTypeString, Const: strPtr("cdc")},
		},
	}
}

func TestBuildParamsFillsDefaultsAndConsts(t *testing.T) {
	params, err := BuildParams(testOperation(), map[string]any{
		"changefeed_id": "cf-1",
		"cluster_type":  "CHANGEFEED_NG",
	})
	require.NoError(t, err)
	require.Equal(t, map[string]string{
		"changefeed_id": "cf-1",
		"cluster_type":  "CHANGEFEED_NG",
		"force":         "false",
		"oncall":        "cdc",
	}, params)
}

func TestBuildParamsRejectsUnknownParam(t *testing.T) {
	_, err := BuildParams(testOperation(), map[string]any{
		"changefeed_id": "cf-1",
		"cluster_type":  "CHANGEFEED",
		"cluster_id":    "42",
	})
	require.ErrorContains(t, err, `unknown param "cluster_id"`)
	require.ErrorContains(t, err, "changefeed_id")
}

func TestBuildParamsRejectsConstParam(t *testing.T) {
	_, err := BuildParams(testOperation(), map[string]any{
		"changefeed_id": "cf-1",
		"cluster_type":  "CHANGEFEED",
		"oncall":        "someone-else",
	})
	require.ErrorContains(t, err, "fixed by the server")
}

func TestBuildParamsReportsMissingRequired(t *testing.T) {
	_, err := BuildParams(testOperation(), map[string]any{"changefeed_id": "cf-1"})
	require.ErrorContains(t, err, "missing required params")
	require.ErrorContains(t, err, "cluster_type")
}

func TestBuildParamsChecksEnum(t *testing.T) {
	_, err := BuildParams(testOperation(), map[string]any{
		"changefeed_id": "cf-1",
		"cluster_type":  "CHANGEFEED_ESSENTIAL",
	})
	require.ErrorContains(t, err, "must be one of [CHANGEFEED, CHANGEFEED_NG]")
}

func TestBuildParamsConvertsValues(t *testing.T) {
	params, err := BuildParams(testOperation(), map[string]any{
		"changefeed_id": "cf-1",
		"cluster_type":  "CHANGEFEED",
		"replicas":      float64(3),
		"labels":        map[string]any{"tier": "gold"},
	})
	require.NoError(t, err)
	require.Equal(t, "3", params["replicas"])
	require.JSONEq(t, `{"tier":"gold"}`, params["labels"])
}

func TestBuildParamsRejectsFractionalInt(t *testing.T) {
	_, err := BuildParams(testOperation(), map[string]any{
		"changefeed_id": "cf-1",
		"cluster_type":  "CHANGEFEED",
		"replicas":      1.5,
	})
	require.ErrorContains(t, err, `param "replicas" must be an integer`)
}

func TestBuildParamsChecksMultiEnum(t *testing.T) {
	op := &Operation{
		Name:   "Restart",
		Params: map[string]ParamDefine{"components": {Type: ParamTypeString, MultiEnum: []string{"tidb", "tikv"}}},
	}

	params, err := BuildParams(op, map[string]any{"components": "tidb,tikv"})
	require.NoError(t, err)
	require.Equal(t, "tidb,tikv", params["components"])

	_, err = BuildParams(op, map[string]any{"components": "tidb,pd"})
	require.ErrorContains(t, err, "comma-separated subset of [tidb, tikv]")
}

func TestBuildActionsUsesImmediateByDefault(t *testing.T) {
	actions, err := BuildActions(testOperation(), "", nil)
	require.NoError(t, err)
	require.Len(t, actions, 1, "optional actions are not submitted unless asked for")
	require.Equal(t, "RestartChangefeed", actions[0].Name)
	require.Equal(t, StartPolicyImmediate, actions[0].StartPolicy)
}

func TestBuildActionsRequiresScheduleTime(t *testing.T) {
	_, err := BuildActions(testOperation(), StartPolicySchedule, nil)
	require.ErrorContains(t, err, "start_schedule_at is required")

	at := int64(1_700_000_000)
	actions, err := BuildActions(testOperation(), StartPolicySchedule, &at)
	require.NoError(t, err)
	require.Equal(t, &at, actions[0].StartScheduleAt)
}

func TestBuildActionsRejectsUnknownPolicy(t *testing.T) {
	_, err := BuildActions(testOperation(), "whenever", nil)
	require.ErrorContains(t, err, `unknown start_policy "whenever"`)
}

func TestResolveActionsKeepsDefinitionOrder(t *testing.T) {
	actions, err := ResolveActions(testOperation(), []CreateInstanceAction{
		{Name: "NotifyOwner"},
		{Name: "RestartChangefeed", StartPolicy: StartPolicyManual},
	})
	require.NoError(t, err)
	require.Equal(t, []string{"RestartChangefeed", "NotifyOwner"}, []string{actions[0].Name, actions[1].Name})
	require.Equal(t, StartPolicyManual, actions[0].StartPolicy)
	require.Equal(t, StartPolicyImmediate, actions[1].StartPolicy)
}

func TestResolveActionsRejectsUnknownAction(t *testing.T) {
	_, err := ResolveActions(testOperation(), []CreateInstanceAction{{Name: "DropCluster"}})
	require.ErrorContains(t, err, `unknown action "DropCluster"`)
}
