package impl

import (
	"context"
	"strings"
	"testing"

	"github.com/stretchr/testify/mock"
	account "github.com/tidbcloud/account/idl/pbgen/proto/account"
	core "github.com/tidbcloud/core-svc/idl/pbgen/proto/core"
	"github.com/tidbcloud/dataflow-service/app/models/domain"
	"github.com/tidbcloud/dataflow-service/app/repo/entity"
	grpcli_mock "github.com/tidbcloud/dataflow-service/pkg/grpc/grpcli/mock"
	"github.com/tidbcloud/pkgv2/bizerr"
	dedicatedv1 "github.com/tidbcloud/tidb-management-service/api/spec/global/dedicated/v1"
	mgmt "github.com/tidbcloud/tidb-management-service/api/spec/tidb_mgmt_service/v1"
)

// NOTE: these tests assert the *new* expectation that mgmt-service lookups
// are used instead of core-cli cluster queries. They are intentionally written
// to fail against the current implementation to codify the migration intent.

func TestPiTRestore_restoreClusterVersionBuilder_UsesMgmtCluster(t *testing.T) {
	coreCli := grpcli_mock.NewCoreGrpcClient(t)
	mgmtCli := grpcli_mock.NewMgmtSvcGrpcClient(t)

	// Expectation for new design: mgmt svc should be called.
	mgmtCli.On("GetAnyDedicatedClusterBasicByID", mock.Anything, uint64(123)).
		Return(&mgmt.Cluster{
			ClusterInfo: &mgmt.Cluster_DedicatedCluster{
				DedicatedCluster: &dedicatedv1.Cluster{Version: "mgmt-version"},
			},
		}, nil).Once()

	// Legacy core lookup must not be used; do not stub GetAnyClusterByID.

	p := newTestPiTRestore(nil)
	p.coreCli = coreCli
	p.mgmtSvcCli = mgmtCli

	req := &createRestoreParam{
		createRestoreReq: &domain.CreateRestoreReqV2{ClusterID: 123},
	}

	if err := p.restoreClusterVersionBuilder(context.Background(), req); err != nil {
		t.Fatalf("unexpected error: %v", err)
	}

	// The migration expects the version to come from mgmt service.
	if req.createClusterVersion != "mgmt-version" {
		t.Fatalf("expected createClusterVersion from mgmt service, got %q", req.createClusterVersion)
	}
	// Ensure legacy core lookup was not used.
	coreCli.AssertNotCalled(t, "GetAnyClusterByID", mock.Anything, uint64(123))
}

func TestRestoreRouter_validate(t *testing.T) {
	tests := map[string]struct {
		clusterConfig       domain.ClusterConfig
		setupCIDRs          func(cli *grpcli_mock.DedicatedGlobalGrpcClient)
		expectCIDRNotCalled bool
		wantErrCode         bizerr.ErrCode
		wantErrContains     []string
	}{
		"success - uses mgmt lookup and dedicated-global cidr query": {
			clusterConfig: domain.ClusterConfig{
				Region: "us-west-1",
				Name:   "n",
			},
			setupCIDRs: func(cli *grpcli_mock.DedicatedGlobalGrpcClient) {
				cli.On("QueryCidrs", mock.Anything, mock.MatchedBy(func(req *dedicatedv1.QueryCidrsRequest) bool {
					return req != nil &&
						req.Filter != nil &&
						len(req.Filter.ProjectIds) == 1 &&
						req.Filter.ProjectIds[0] == 11 &&
						req.Filter.CloudProviderName == "aws" &&
						req.GetGlobalMetaOnly()
				})).Return(&dedicatedv1.QueryCidrsResponse{
					Items: []*dedicatedv1.Cidr{
						{RegionName: "us-west-1"},
					},
				}, nil).Once()
			},
		},
		"error - cidr for target region not found": {
			clusterConfig: domain.ClusterConfig{
				Region: "us-west-1",
				Name:   "n",
			},
			setupCIDRs: func(cli *grpcli_mock.DedicatedGlobalGrpcClient) {
				cli.On("QueryCidrs", mock.Anything, mock.Anything).
					Return(&dedicatedv1.QueryCidrsResponse{
						Items: []*dedicatedv1.Cidr{
							{RegionName: "ap-southeast-1"},
						},
					}, nil).Once()
			},
			wantErrCode:     bizerr.ErrCodeRecordNotFound,
			wantErrContains: []string{"us-west-1", "11"},
		},
		"success - existing target cluster skips cidr query": {
			clusterConfig: domain.ClusterConfig{
				ClusterID: 999,
				Region:    "us-west-1",
				Name:      "existing-cluster",
			},
			expectCIDRNotCalled: true,
		},
	}

	for name, tc := range tests {
		t.Run(name, func(t *testing.T) {
			coreCli := grpcli_mock.NewCoreGrpcClient(t)
			mgmtCli := grpcli_mock.NewMgmtSvcGrpcClient(t)
			accountCli := grpcli_mock.NewAccountSvcGrpcClient(t)
			dedicatedGlobalCli := grpcli_mock.NewDedicatedGlobalGrpcClient(t)

			mgmtCli.On("GetAnyDedicatedClusterBasicByID", mock.Anything, uint64(321)).
				Return(&mgmt.Cluster{
					ClusterId:      321,
					ProjectId:      11,
					OrganizationId: 10,
					CloudProvider:  "aws",
					ClusterInfo: &mgmt.Cluster_DedicatedCluster{
						DedicatedCluster: &dedicatedv1.Cluster{},
					},
				}, nil).Once()
			accountCli.On("GetProjectByID", mock.Anything, &account.GetProjectByIDRequest{Id: 11}).
				Return(&account.GetProjectByIDResponse{Project: &account.Project{TenantId: 10}}, nil).Once()

			if tc.setupCIDRs != nil {
				tc.setupCIDRs(dedicatedGlobalCli)
			}

			r := &restoreModelRouter{
				baseRestoreModel:     baseRestoreModel{coreCli: coreCli, mgmtSvcCli: mgmtCli, dedicatedGlobalClient: dedicatedGlobalCli},
				accountSvcGrpcClient: accountCli,
			}
			req := &domain.CreateRestoreReqV2{
				TenantID:      10,
				ProjectID:     11,
				Mode:          domain.RestoreModePITR,
				ClusterID:     321,
				ClusterConfig: tc.clusterConfig,
			}

			err := r.validate(context.Background(), req)
			if tc.wantErrCode == 0 {
				if err != nil {
					t.Fatalf("unexpected error: %v", err)
				}
			} else {
				if err == nil {
					t.Fatalf("expected error with code %v, got nil", tc.wantErrCode)
				}
				if err.ErrCode() != tc.wantErrCode {
					t.Fatalf("expected error code %v, got %v", tc.wantErrCode, err.ErrCode())
				}
				for _, phrase := range tc.wantErrContains {
					if !strings.Contains(err.Error(), phrase) {
						t.Fatalf("expected error %q to contain %q", err.Error(), phrase)
					}
				}
			}

			if tc.expectCIDRNotCalled {
				dedicatedGlobalCli.AssertNotCalled(t, "QueryCidrs", mock.Anything, mock.Anything)
			}
			coreCli.AssertNotCalled(t, "GetAnyClusterByID", mock.Anything, uint64(321))
			coreCli.AssertNotCalled(t, "GetProjectRegionCIDR", mock.Anything, uint64(11), "aws", "us-west-1")
		})
	}
}

func TestBaseRestoreModel_restoreClusterParamBuilder_UsesMgmtClusterLookup(t *testing.T) {
	coreCli := grpcli_mock.NewCoreGrpcClient(t)
	mgmtCli := grpcli_mock.NewMgmtSvcGrpcClient(t)
	dedicatedGlobalCli := grpcli_mock.NewDedicatedGlobalGrpcClient(t)

	// core.GetClusterSnapshot is still expected to be used.
	coreCli.On("GetClusterSnapshot", mock.Anything, uint64(222), uint64(2)).
		Return(&core.ClusterSnapshot{}, nil).Once()

	// New design: backup cluster metadata should be fetched from mgmt service.
	// Provide mgmt cluster with meaningful metadata that should be mapped into
	// the create request.
	mgmtCli.On("GetAnyDedicatedClusterBasicByID", mock.Anything, uint64(222)).
		Return(&mgmt.Cluster{
			ClusterId:      222,
			ProjectId:      77,
			OrganizationId: 99,
			CloudProvider:  "aws",
			ClusterInfo: &mgmt.Cluster_DedicatedCluster{
				DedicatedCluster: &dedicatedv1.Cluster{},
			},
		}, nil).Once()

	// Legacy core lookup must not be used; do not stub GetAnyClusterByID.

	// The create flow does a mgmt dry-run (ValidateOnly) which calls RestoreDedicatedCluster.
	// Provide a permissive mock return so the test focuses on lookup behavior instead of mgmt validation.
	mgmtCli.On("RestoreDedicatedCluster", mock.Anything, mock.Anything).
		Return(&mgmt.Cluster{}, nil).Once()

	b := &baseRestoreModel{
		coreCli:               coreCli,
		mgmtSvcCli:            mgmtCli,
		dedicatedGlobalClient: dedicatedGlobalCli,
	}

	req := &createRestoreParam{
		backup: &entity.Backup{ClusterID: entity.ID(222), ClusterSnapshotID: entity.ID(2), BucketSettingSnapshot: nil},
		createRestoreReq: &domain.CreateRestoreReqV2{
			ProjectID: 77,
			ClusterConfig: domain.ClusterConfig{
				Region: "r",
				Name:   "n",
				Components: &domain.ClusterComponents{
					Tidb: &domain.TiDBComponent{NodeSize: "s", NodeQuantity: 1},
					Tikv: &domain.TiKVComponent{NodeSize: "s", NodeQuantity: 1, StorageSizeGib: 1, RaftIops: 100},
				},
			},
		},
	}

	if err := b.restoreClusterParamBuilder(context.Background(), req); err != nil {
		t.Fatalf("unexpected error: %v", err)
	}

	// New design expects createClusterReqV2 to be built using mgmt cluster metadata.
	if req.createClusterReqV2 == nil {
		t.Fatalf("expected createClusterReqV2 to be set")
	}

	// Ensure fields are mapped from mgmt response (not just non-nil). In the
	// current implementation these will come from core, so the assertions below
	// will fail until the migration is implemented.
	if req.createClusterReqV2.CloudProvider != "aws" {
		t.Fatalf("expected CloudProvider from mgmt to be mapped, got %q", req.createClusterReqV2.CloudProvider)
	}
	if req.createClusterReqV2.OrganizationId != 99 {
		t.Fatalf("expected OrganizationId from mgmt to be mapped, got %d", req.createClusterReqV2.OrganizationId)
	}
	if req.createClusterReqV2.ProjectId != 77 {
		t.Fatalf("expected ProjectId from input to be mapped, got %d", req.createClusterReqV2.ProjectId)
	}
	// Ensure legacy core lookup was not used.
	coreCli.AssertNotCalled(t, "GetAnyClusterByID", mock.Anything, uint64(222))
}
