package dm_svc

import (
	"testing"

	"github.com/tidbcloud/pkgv2/bizerr"
	"github.com/tidbcloud/serverless-service/internal/domain/constant"
	"github.com/tidbcloud/serverless-service/internal/domain/entity"
	"github.com/tidbcloud/serverless-service/internal/domain/vo"
	regionalserverlessv1 "github.com/tidbcloud/tidb-management-service/api/spec/regional/serverless/v1"
)

const samplePrecheckExecutionFailedErr = "precheck execution failed: [code=26001:class=task-check:scope=internal:level=medium], " +
	"Message: fail to initialize checker: same table name in case-insensitive " +
	"[same target table `ceo`.`t_user_deviceId_association` vs `ceo`.`t_user_deviceid_association`], " +
	"Workaround: Please check `target-table` config in task configuration file."

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

	tests := []struct {
		name           string
		err            error
		wantOK         bool
		wantReason     string
		wantWorkaround string
	}{
		{
			name:           "real precheck execution failed error",
			err:            bizerr.New(bizerr.ErrCodeUnknown, "%s", samplePrecheckExecutionFailedErr),
			wantOK:         true,
			wantReason:     "fail to initialize checker: same table name in case-insensitive [same target table `ceo`.`t_user_deviceId_association` vs `ceo`.`t_user_deviceid_association`]",
			wantWorkaround: "Please check `target-table` config in task configuration file.",
		},
		{
			name:   "plain error without prefix",
			err:    bizerr.New(bizerr.ErrCodeUnknown, "%s", "connection refused"),
			wantOK: false,
		},
		{
			name:           "prefix only fallback",
			err:            bizerr.New(bizerr.ErrCodeUnknown, "%s", "precheck execution failed: invalid configuration"),
			wantOK:         true,
			wantReason:     "invalid configuration",
			wantWorkaround: "",
		},
		{
			name: "workaround without message",
			err: bizerr.New(
				bizerr.ErrCodeUnknown,
				"%s",
				"precheck execution failed: invalid config, Workaround: fix it",
			),
			wantOK:         true,
			wantReason:     "invalid config",
			wantWorkaround: "fix it",
		},
	}

	for _, tt := range tests {
		t.Run(tt.name, func(t *testing.T) {
			t.Parallel()
			reason, workaround, ok := extractDMPrecheckUserError(tt.err)
			if ok != tt.wantOK {
				t.Fatalf("ok = %v, want %v", ok, tt.wantOK)
			}
			if reason != tt.wantReason {
				t.Fatalf("reason = %q, want %q", reason, tt.wantReason)
			}
			if workaround != tt.wantWorkaround {
				t.Fatalf("workaround = %q, want %q", workaround, tt.wantWorkaround)
			}
		})
	}
}

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

	got := buildSkippedDueToInitFailureReason("invalid route rules")
	want := "This check was not executed because precheck initialization failed: invalid route rules"
	if got != want {
		t.Fatalf("reason = %q, want %q", got, want)
	}
}

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

	reason := "fail to initialize checker: same table name in case-insensitive"
	workaround := "Please check `target-table` config in task configuration file."
	results := buildDMPrecheckFailureResults(
		regionalserverlessv1.DMPrecheck_PRECHECK_ITEM_DUMP_PRIVILEGE_CHECKING.String(),
		reason,
		workaround,
	)

	precheck := &entity.DMPrecheck{
		ID:    "dmp-test",
		Stage: constant.DMPrecheckStageFinished,
		DMPrecheckItems: []entity.DMPrecheckItem{
			{
				Description: regionalserverlessv1.DMPrecheck_PRECHECK_ITEM_DUMP_PRIVILEGE_CHECKING.String(),
				Stage:       constant.DMPrecheckStageFinished,
				Type:        regionalserverlessv1.DMPrecheck_PRECHECK_ITEM_DUMP_PRIVILEGE_CHECKING,
				Results:     results,
			},
		},
	}

	resp := vo.DMPrecheckToV1beta1PbPrecheck(precheck)
	if resp == nil {
		t.Fatal("expected non-nil response")
	}
	if len(resp.Items) != 1 {
		t.Fatalf("items len = %d, want 1", len(resp.Items))
	}
	item := resp.Items[0]
	if item.Reason != reason {
		t.Fatalf("reason = %q, want %q", item.Reason, reason)
	}
	if item.Solution != workaround {
		t.Fatalf("solution = %q, want %q", item.Solution, workaround)
	}
	if item.GetStatus().String() != "FAILED" {
		t.Fatalf("status = %v, want FAILED", item.GetStatus())
	}
}

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

	reason := "visible failure"
	workaround := "fix your config"
	results := buildDMPrecheckFailureResults("PRECHECK_ITEM_DUMP_PRIVILEGE_CHECKING", reason, workaround)

	precheck := &entity.DMPrecheck{
		ID:    "dmp-test",
		Stage: constant.DMPrecheckStageFailed,
		DMPrecheckItems: []entity.DMPrecheckItem{
			{
				Description: "PRECHECK_ITEM_DUMP_PRIVILEGE_CHECKING",
				Stage:       constant.DMPrecheckStageFailed,
				Type:        regionalserverlessv1.DMPrecheck_PRECHECK_ITEM_DUMP_PRIVILEGE_CHECKING,
				Results:     results,
			},
		},
	}

	resp := vo.DMPrecheckToV1beta1PbPrecheck(precheck)
	if len(resp.Items) != 1 {
		t.Fatalf("items len = %d, want 1", len(resp.Items))
	}
	if resp.Items[0].Reason != reason {
		t.Fatalf("reason = %q, want %q", resp.Items[0].Reason, reason)
	}
	if resp.Items[0].Solution != workaround {
		t.Fatalf("solution = %q, want %q", resp.Items[0].Solution, workaround)
	}
}

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

	reason := "fail to initialize checker"
	workaround := "fix config"
	items := make([]entity.DMPrecheckItem, 0, 3)
	for _, itemType := range []regionalserverlessv1.DMPrecheck_PrecheckItem{
		regionalserverlessv1.DMPrecheck_PRECHECK_ITEM_DUMP_PRIVILEGE_CHECKING,
		regionalserverlessv1.DMPrecheck_PRECHECK_ITEM_VERSION_CHECKING,
		regionalserverlessv1.DMPrecheck_PRECHECK_ITEM_BINLOG_ENABLE_CHECKING,
	} {
		name := itemType.String()
		items = append(items, entity.DMPrecheckItem{
			Description: name,
			Stage:       constant.DMPrecheckStageFinished,
			Type:        itemType,
			Results:     buildDMPrecheckFailureResults(name, reason, workaround),
		})
	}

	precheck := &entity.DMPrecheck{
		ID:              "dmp-test",
		Stage:           constant.DMPrecheckStageFinished,
		TotalItems:      int32(len(items)),
		DMPrecheckItems: items,
	}

	resp := vo.DMPrecheckToV1beta1PbPrecheck(precheck)
	if resp.GetTotal() != int32(len(items)) {
		t.Fatalf("total = %d, want %d", resp.GetTotal(), len(items))
	}
	if resp.GetFailedCnt() != int32(len(items)) {
		t.Fatalf("failed_cnt = %d, want %d", resp.GetFailedCnt(), len(items))
	}
	if int(resp.GetFailedCnt()+resp.GetWarnCnt()+resp.GetSuccessCnt()) != len(items) {
		t.Fatalf(
			"count sum = %d, want %d",
			resp.GetFailedCnt()+resp.GetWarnCnt()+resp.GetSuccessCnt(),
			len(items),
		)
	}
}
