package grpc

import (
	"encoding/json"
	"errors"
	"strings"
	"testing"

	"github.com/BurntSushi/toml"
	dmconfig "github.com/pingcap/tiflow/dm/config"
	dmdbconfig "github.com/pingcap/tiflow/dm/config/dbconfig"
	pchecker "github.com/pingcap/tiflow/dm/pkg/checker"
	"github.com/stretchr/testify/require"
	agent "github.com/tidbcloud/dataflow-service/dataflow-agent-ng/idl/pbgen/proto/ng_agent"
	"google.golang.org/grpc/codes"
	"google.golang.org/grpc/status"
)

func encodePrecheckTaskConfig(t *testing.T, cfg *dmconfig.SubTaskConfig) string {
	t.Helper()

	var builder strings.Builder
	require.NoError(t, toml.NewEncoder(&builder).Encode(cfg))
	return builder.String()
}

func newMinimalPrecheckTaskConfig(sourceHost, sourceUser, targetHost string) *dmconfig.SubTaskConfig {
	return &dmconfig.SubTaskConfig{
		Name:           "precheck-task",
		Mode:           "all",
		SourceID:       "test-precheck",
		LoaderConfig:   dmconfig.DefaultLoaderConfig(),
		SyncerConfig:   dmconfig.DefaultSyncerConfig(),
		MydumperConfig: dmconfig.DefaultMydumperConfig(),
		From:           dmdbconfig.DBConfig{Host: sourceHost, User: sourceUser},
		To:             dmdbconfig.DBConfig{Host: targetHost, User: "target-user"},
	}
}

func TestBuildPrecheckConfigsUsesIndexedSourcePasswords(t *testing.T) {
	req := &agent.PrecheckDMJobReq{
		TaskConfig: []string{
			encodePrecheckTaskConfig(t, newMinimalPrecheckTaskConfig("source-1", "user-1", "")),
			encodePrecheckTaskConfig(t, newMinimalPrecheckTaskConfig("source-2", "user-2", "existing-target")),
		},
		SourcePasswords: []string{"pwd-1", "pwd-2"},
		ClusterPassword: "cluster-pwd",
	}

	configs, err := buildPrecheckConfigs(req, "resolved-target")
	require.NoError(t, err)
	require.Len(t, configs, 2)

	require.Equal(t, "pwd-1", configs[0].From.Password)
	require.Equal(t, "cluster-pwd", configs[0].To.Password)
	require.Equal(t, "resolved-target", configs[0].To.Host)

	require.Equal(t, "pwd-2", configs[1].From.Password)
	require.Equal(t, "cluster-pwd", configs[1].To.Password)
	require.Equal(t, "existing-target", configs[1].To.Host)
}

func TestBuildPrecheckConfigsRejectsMismatchedSourcePasswords(t *testing.T) {
	req := &agent.PrecheckDMJobReq{
		TaskConfig: []string{
			encodePrecheckTaskConfig(t, newMinimalPrecheckTaskConfig("source-1", "user-1", "")),
			encodePrecheckTaskConfig(t, newMinimalPrecheckTaskConfig("source-2", "user-2", "")),
		},
		SourcePasswords: []string{"pwd-1"},
	}

	_, err := buildPrecheckConfigs(req, "resolved-target")
	require.Error(t, err)
	require.Contains(t, err.Error(), "source_passwords")
}

func TestValidateExtraCheckingItems(t *testing.T) {
	require.NoError(t, validateExtraCheckingItems(nil))
	require.NoError(t, validateExtraCheckingItems([]string{dmconfig.PrimaryKeyChecking}))
	require.Error(t, validateExtraCheckingItems([]string{"unknown_checking_item"}))
}

func TestPrecheckDMJobClientErrorReturnsCleanInvalidArgument(t *testing.T) {
	err := precheckDMJobClientError(errors.New("fail to initialize checker: invalid config"))

	st, ok := status.FromError(err)
	require.True(t, ok)
	require.Equal(t, codes.InvalidArgument, st.Code())
	require.Equal(t, "fail to initialize checker: invalid config", st.Message())
}

func TestPrecheckDMJobFailureRespReturnsFailedCheckResults(t *testing.T) {
	resp, err := precheckDMJobFailureResp(errors.New("fail to initialize checker: invalid config"))
	require.NoError(t, err)
	require.NotNil(t, resp)

	var results pchecker.Results
	require.NoError(t, json.Unmarshal(resp.CheckResults, &results))
	require.Len(t, results.Results, 1)
	require.Equal(t, dmPrecheckInitializationDesc, results.Results[0].Desc)
	require.Equal(t, pchecker.StateFailure, results.Results[0].State)
	require.Equal(t, "fail to initialize checker: invalid config", results.Results[0].Errors[0].ShortErr)
	require.NotNil(t, results.Summary)
	require.Equal(t, int64(1), results.Summary.Failed)
}
