package compact_test

import (
	"context"
	"testing"

	dagentity "github.com/shiningrush/fastflow/pkg/entity"
	"github.com/stretchr/testify/mock"
	"github.com/stretchr/testify/require"
	"github.com/stretchr/testify/suite"
	cluster_job_mock "github.com/tidbcloud/dataflow-service/app/internal/cluster_job/mock"
	"github.com/tidbcloud/dataflow-service/app/models/backup/impl"
	"github.com/tidbcloud/dataflow-service/app/models/domain"
	"github.com/tidbcloud/dataflow-service/app/repo"
	"github.com/tidbcloud/dataflow-service/app/repo/entity"
	repo_mock "github.com/tidbcloud/dataflow-service/app/repo/mock"
	"github.com/tidbcloud/dataflow-service/app/workflow"
	workflow_mock "github.com/tidbcloud/dataflow-service/app/workflow/mock"
	grpcli_mock "github.com/tidbcloud/dataflow-service/pkg/grpc/grpcli/mock"
	"github.com/tidbcloud/pkgv2/bizerr"
	"github.com/tidbcloud/pkgv2/db"
	mgmt "github.com/tidbcloud/tidb-management-service/api/spec/tidb_mgmt_service/v1"
	"gorm.io/gorm"
)

const (
	// Test constants - reuse from existing test file
	TestModelTenantID    = uint64(1)
	TestModelProjectID   = uint64(2)
	TestModelClusterID   = uint64(300)
	TestModelLogBackupID = uint64(200)
)

type compactBackupModelTestSuite struct {
	suite.Suite

	model *impl.CompactBackupModel

	mockLogBackupRepo     *repo_mock.LogBackupRepo
	mockCompactBackupRepo *repo_mock.CompactBackupRepo
	mockClusterJob        *cluster_job_mock.Model
	mockWorkflow          *workflow_mock.Workflow
	mockCoreGrpcClient    *grpcli_mock.CoreGrpcClient
	mockMgmtGrpcClient    *grpcli_mock.MgmtSvcGrpcClient

	db            *gorm.DB
	transProvider db.TransProvider
}

func TestCompactBackupModelTestSuite(t *testing.T) {
	suite.Run(t, new(compactBackupModelTestSuite))
}

func (s *compactBackupModelTestSuite) SetupSuite() {
	testDB, err := repo.GetFakeDB()
	require.NoError(s.T(), err)
	s.db = testDB
	s.transProvider = db.NewProvider(testDB)
}

func (s *compactBackupModelTestSuite) SetupTest() {
	// Create fresh mocks for each test to ensure isolation
	s.mockLogBackupRepo = repo_mock.NewLogBackupRepo(s.T())
	s.mockCompactBackupRepo = repo_mock.NewCompactBackupRepo(s.T())
	s.mockClusterJob = cluster_job_mock.NewModel(s.T())
	s.mockWorkflow = workflow_mock.NewWorkflow(s.T())
	s.mockCoreGrpcClient = grpcli_mock.NewCoreGrpcClient(s.T())
	s.mockMgmtGrpcClient = grpcli_mock.NewMgmtSvcGrpcClient(s.T())

	// Create model instance with fresh mocks
	s.model = impl.NewCompactBackupModel(
		s.transProvider,
		s.mockCompactBackupRepo,
		s.mockLogBackupRepo,
		s.mockClusterJob,
		s.mockWorkflow,
		s.mockCoreGrpcClient,
		s.mockMgmtGrpcClient,
		nil, // bucketRepo - not needed for these tests
		nil, // encryptor - not needed for these tests
	)
}

// Test AsyncCreate method with various scenarios
func (s *compactBackupModelTestSuite) TestAsyncCreate() {
	testCases := []struct {
		name             string
		req              *domain.AsyncCreateCompactBackupReq
		mockLogBackup    *entity.LogBackup
		mockLogBackupErr bizerr.BizError
		mockSaveErr      bizerr.BizError
		mockWorkflowResp *dagentity.DagInstance
		mockWorkflowErr  error
		expectError      bool
		expectNilResp    bool // For DryRun case
		description      string
	}{
		{
			name: "success_complete_flow",
			req: &domain.AsyncCreateCompactBackupReq{
				ClusterID:   TestModelClusterID,
				LogBackupID: TestModelLogBackupID,
				UserID:      TestModelTenantID,
				ProjectID:   TestModelProjectID,
				IsInternal:  true,
			},
			mockLogBackup: &entity.LogBackup{
				Updatable: entity.Updatable{
					Base: entity.Base{ID: entity.ID(TestModelLogBackupID)},
				},
				ClusterID: entity.ID(TestModelClusterID),
				TenantID:  entity.ID(TestModelTenantID),
				Status:    entity.LogBackupStatusRunning,
				Storage:   "s3://test-bucket/log-backup",
			},
			mockLogBackupErr: nil,
			mockSaveErr:      nil,
			mockWorkflowResp: &dagentity.DagInstance{
				BaseInfo: dagentity.BaseInfo{ID: "test-workflow-123"},
			},
			mockWorkflowErr: nil,
			expectError:     false,
			description:     "Complete successful flow: preCheck→createDB→startWorkflow",
		},
		{
			name: "failure_log_backup_not_found",
			req: &domain.AsyncCreateCompactBackupReq{
				ClusterID:   TestModelClusterID,
				LogBackupID: TestModelLogBackupID,
				UserID:      TestModelTenantID,
				ProjectID:   TestModelProjectID,
				IsInternal:  true,
			},
			mockLogBackupErr: bizerr.NewWithStack(bizerr.ErrCodeUnknown, "log backup not found"),
			expectError:      true,
			description:      "PreCheck fails when LogBackup does not exist",
		},
		{
			name: "failure_log_backup_invalid_status",
			req: &domain.AsyncCreateCompactBackupReq{
				ClusterID:   TestModelClusterID,
				LogBackupID: TestModelLogBackupID,
				UserID:      TestModelTenantID,
				ProjectID:   TestModelProjectID,
				IsInternal:  true,
			},
			mockLogBackup: &entity.LogBackup{
				Updatable: entity.Updatable{
					Base: entity.Base{ID: entity.ID(TestModelLogBackupID)},
				},
				ClusterID: entity.ID(TestModelClusterID),
				TenantID:  entity.ID(TestModelTenantID),
				Status:    entity.LogBackupStatusScheduled, // Invalid status (not yet running)
				Storage:   "s3://test-bucket/log-backup",
			},
			mockLogBackupErr: nil,
			expectError:      true,
			description:      "PreCheck fails when LogBackup status is Scheduled or Pending",
		},
		{
			name: "failure_tenant_mismatch",
			req: &domain.AsyncCreateCompactBackupReq{
				ClusterID:   TestModelClusterID,
				LogBackupID: TestModelLogBackupID,
				UserID:      TestModelTenantID,
				ProjectID:   TestModelProjectID,
				IsInternal:  true,
			},
			mockLogBackup: &entity.LogBackup{
				Updatable: entity.Updatable{
					Base: entity.Base{ID: entity.ID(TestModelLogBackupID)},
				},
				ClusterID: entity.ID(TestModelClusterID),
				TenantID:  entity.ID(999), // Different tenant
				Status:    entity.LogBackupStatusRunning,
				Storage:   "s3://test-bucket/log-backup",
			},
			mockLogBackupErr: nil,
			expectError:      true,
			description:      "PreCheck fails when LogBackup belongs to a different tenant than the cluster",
		},
		{
			name: "failure_db_save_error",
			req: &domain.AsyncCreateCompactBackupReq{
				ClusterID:   TestModelClusterID,
				LogBackupID: TestModelLogBackupID,
				UserID:      TestModelTenantID,
				ProjectID:   TestModelProjectID,
				IsInternal:  true,
			},
			mockLogBackup: &entity.LogBackup{
				Updatable: entity.Updatable{
					Base: entity.Base{ID: entity.ID(TestModelLogBackupID)},
				},
				ClusterID: entity.ID(TestModelClusterID),
				TenantID:  entity.ID(TestModelTenantID),
				Status:    entity.LogBackupStatusRunning,
				Storage:   "s3://test-bucket/log-backup",
			},
			mockLogBackupErr: nil,
			mockSaveErr:      bizerr.NewWithStack(bizerr.ErrCodeUnknown, "database save failed"),
			expectError:      true,
			description:      "DB entity creation fails due to Save error",
		},
		{
			name: "failure_workflow_start_error",
			req: &domain.AsyncCreateCompactBackupReq{
				ClusterID:   TestModelClusterID,
				LogBackupID: TestModelLogBackupID,
				UserID:      TestModelTenantID,
				ProjectID:   TestModelProjectID,
				IsInternal:  true,
			},
			mockLogBackup: &entity.LogBackup{
				Updatable: entity.Updatable{
					Base: entity.Base{ID: entity.ID(TestModelLogBackupID)},
				},
				ClusterID: entity.ID(TestModelClusterID),
				TenantID:  entity.ID(TestModelTenantID),
				Status:    entity.LogBackupStatusRunning,
				Storage:   "s3://test-bucket/log-backup",
			},
			mockLogBackupErr: nil,
			mockSaveErr:      nil,
			mockWorkflowErr:  bizerr.NewWithStack(bizerr.ErrCodeUnknown, "workflow execution failed"),
			expectError:      true,
			description:      "Workflow DAG creation fails",
		},
		{
			name: "success_dry_run_mode",
			req: &domain.AsyncCreateCompactBackupReq{
				ClusterID:   TestModelClusterID,
				LogBackupID: TestModelLogBackupID,
				UserID:      TestModelTenantID,
				ProjectID:   TestModelProjectID,
				IsInternal:  true,
				DryRun:      true, // DryRun mode
			},
			mockLogBackup: &entity.LogBackup{
				Updatable: entity.Updatable{
					Base: entity.Base{ID: entity.ID(TestModelLogBackupID)},
				},
				ClusterID: entity.ID(TestModelClusterID),
				TenantID:  entity.ID(TestModelTenantID),
				Status:    entity.LogBackupStatusRunning,
				Storage:   "s3://test-bucket/log-backup",
			},
			mockLogBackupErr: nil,
			expectNilResp:    true, // DryRun returns nil
			description:      "DryRun mode should skip actual execution",
		},
		{
			name: "failure_invalid_request",
			req: &domain.AsyncCreateCompactBackupReq{
				ClusterID:   0, // Invalid ClusterID
				LogBackupID: TestModelLogBackupID,
				UserID:      TestModelTenantID,
				ProjectID:   TestModelProjectID,
				IsInternal:  true,
			},
			expectError: true,
			description: "Request validation should fail for invalid ClusterID",
		},
	}

	for _, tc := range testCases {
		testCase := tc
		s.Run(testCase.name, func() {
			s.SetupTest()
			s.runAsyncCreateTestCase(testCase)
		})
	}
}

func (s *compactBackupModelTestSuite) runAsyncCreateTestCase(tc struct {
	name             string
	req              *domain.AsyncCreateCompactBackupReq
	mockLogBackup    *entity.LogBackup
	mockLogBackupErr bizerr.BizError
	mockSaveErr      bizerr.BizError
	mockWorkflowResp *dagentity.DagInstance
	mockWorkflowErr  error
	expectError      bool
	expectNilResp    bool
	description      string
}) {
	ctx := context.Background()

	// Setup mocks based on test case requirements
	if tc.mockLogBackup != nil || tc.mockLogBackupErr != nil {
		s.mockLogBackupRepo.On("Load", mock.Anything, tc.req.LogBackupID).
			Return(tc.mockLogBackup, tc.mockLogBackupErr).Maybe()
	}

	// Mock mgmt lookup when log backup loads successfully
	if tc.mockLogBackup != nil && tc.mockLogBackupErr == nil {
		s.mockMgmtGrpcClient.On("GetAnyDedicatedClusterBasicByID", mock.Anything, tc.req.ClusterID).
			Return(&mgmt.Cluster{ClusterId: tc.req.ClusterID, ProjectId: TestModelProjectID, OrganizationId: TestModelTenantID}, nil).Maybe()
	}

	// Mock Save operation if we expect it to be called
	if !tc.expectError || tc.mockSaveErr != nil || tc.mockWorkflowErr != nil {
		if tc.mockSaveErr != nil {
			s.mockCompactBackupRepo.On("Save", mock.Anything, mock.AnythingOfType("*entity.CompactBackup")).
				Return(tc.mockSaveErr).Maybe()
		} else {
			s.mockCompactBackupRepo.On("Save", mock.Anything, mock.AnythingOfType("*entity.CompactBackup")).
				Run(func(args mock.Arguments) {
					// Simulate GORM setting the ID field after save
					backup := args.Get(1).(*entity.CompactBackup)
					backup.ID = entity.ID(999) // Set mock ID
				}).Return(nil).Maybe()
		}
	}

	// Mock Workflow operations for successful cases
	if !tc.expectError || tc.mockWorkflowErr != nil {
		if tc.mockWorkflowErr != nil {
			s.mockWorkflow.On("RunDagWithTags", workflow.DagCreateCompactBackup, mock.Anything, mock.Anything).
				Return(nil, tc.mockWorkflowErr).Maybe()
		} else {
			s.mockWorkflow.On("RunDagWithTags", workflow.DagCreateCompactBackup, mock.Anything, mock.Anything).
				Return(tc.mockWorkflowResp, nil).Maybe()
		}
	}

	// Execute AsyncCreate
	resp, err := s.model.AsyncCreate(ctx, tc.req)

	// Verify results
	if tc.expectError {
		s.Error(err, "Should return error for test case: %s", tc.name)
		s.Nil(resp, "Response should be nil when error occurs")
	} else if tc.expectNilResp {
		s.NoError(err, "DryRun should not return error for test case: %s", tc.name)
		s.Nil(resp, "DryRun should return nil response")
	} else {
		s.NoError(err, "Should not return error for test case: %s", tc.name)
		s.NotNil(resp, "Response should not be nil for successful case")
		s.NotZero(resp.CompactBackupID, "CompactBackupID should be set")
		s.Equal("test-workflow-123", resp.WorkflowID, "WorkflowID should match mock")
	}
}

// Test PreCheck method separately for focused validation
func (s *compactBackupModelTestSuite) TestPreCheck() {
	testCases := []struct {
		name             string
		req              *domain.AsyncCreateCompactBackupReq
		mockLogBackup    *entity.LogBackup
		mockLogBackupErr bizerr.BizError
		expectError      bool
		description      string
	}{
		{
			name: "success_log_backup_running",
			req: &domain.AsyncCreateCompactBackupReq{
				ClusterID:   TestModelClusterID,
				LogBackupID: TestModelLogBackupID,
			},
			mockLogBackup: &entity.LogBackup{
				Updatable: entity.Updatable{
					Base: entity.Base{ID: entity.ID(TestModelLogBackupID)},
				},
				ClusterID: entity.ID(TestModelClusterID),
				TenantID:  entity.ID(TestModelTenantID),
				Status:    entity.LogBackupStatusRunning,
			},
			expectError: false,
			description: "Valid LogBackup with Running status should pass",
		},
		{
			name: "success_log_backup_paused",
			req: &domain.AsyncCreateCompactBackupReq{
				ClusterID:   TestModelClusterID,
				LogBackupID: TestModelLogBackupID,
			},
			mockLogBackup: &entity.LogBackup{
				Updatable: entity.Updatable{
					Base: entity.Base{ID: entity.ID(TestModelLogBackupID)},
				},
				ClusterID: entity.ID(TestModelClusterID),
				TenantID:  entity.ID(TestModelTenantID),
				Status:    entity.LogBackupStatusPaused,
			},
			expectError: false,
			description: "Valid LogBackup with Paused status should pass",
		},
		{
			name: "failure_log_backup_not_found",
			req: &domain.AsyncCreateCompactBackupReq{
				ClusterID:   TestModelClusterID,
				LogBackupID: TestModelLogBackupID,
			},
			mockLogBackupErr: bizerr.NewWithStack(bizerr.ErrCodeUnknown, "log backup not found"),
			expectError:      true,
			description:      "Should fail when LogBackup does not exist",
		},
		{
			name: "failure_tenant_mismatch",
			req: &domain.AsyncCreateCompactBackupReq{
				ClusterID:   TestModelClusterID,
				LogBackupID: TestModelLogBackupID,
			},
			mockLogBackup: &entity.LogBackup{
				Updatable: entity.Updatable{
					Base: entity.Base{ID: entity.ID(TestModelLogBackupID)},
				},
				ClusterID: entity.ID(TestModelClusterID),
				TenantID:  entity.ID(999), // Different tenant
				Status:    entity.LogBackupStatusRunning,
			},
			expectError: true,
			description: "Should fail when LogBackup belongs to a different tenant than the cluster",
		},
		{
			name: "failure_invalid_log_backup_status",
			req: &domain.AsyncCreateCompactBackupReq{
				ClusterID:   TestModelClusterID,
				LogBackupID: TestModelLogBackupID,
			},
			mockLogBackup: &entity.LogBackup{
				Updatable: entity.Updatable{
					Base: entity.Base{ID: entity.ID(TestModelLogBackupID)},
				},
				ClusterID: entity.ID(TestModelClusterID),
				TenantID:  entity.ID(TestModelTenantID),
				Status:    entity.LogBackupStatusScheduled, // Invalid status (not yet running)
			},
			expectError: true,
			description: "Should fail when LogBackup status is Scheduled or Pending",
		},
	}

	for _, tc := range testCases {
		testCase := tc
		s.Run(testCase.name, func() {
			s.SetupTest()
			s.runPreCheckTestCase(testCase)
		})
	}
}

func (s *compactBackupModelTestSuite) runPreCheckTestCase(tc struct {
	name             string
	req              *domain.AsyncCreateCompactBackupReq
	mockLogBackup    *entity.LogBackup
	mockLogBackupErr bizerr.BizError
	expectError      bool
	description      string
}) {
	ctx := context.Background()

	// Setup LogBackupRepo mock
	s.mockLogBackupRepo.On("Load", mock.Anything, tc.req.LogBackupID).
		Return(tc.mockLogBackup, tc.mockLogBackupErr).Once()

	// Mock mgmt lookup when log backup loads successfully
	if tc.mockLogBackup != nil && tc.mockLogBackupErr == nil {
		s.mockMgmtGrpcClient.On("GetAnyDedicatedClusterBasicByID", mock.Anything, tc.req.ClusterID).
			Return(&mgmt.Cluster{ClusterId: tc.req.ClusterID, ProjectId: TestModelProjectID, OrganizationId: TestModelTenantID}, nil).Maybe()
	}

	// For successful preCheck cases, also mock the Save and Workflow operations
	if !tc.expectError {
		// First Save: GORM assigns the auto-increment ID
		s.mockCompactBackupRepo.On("Save", mock.Anything, mock.AnythingOfType("*entity.CompactBackup")).
			Run(func(args mock.Arguments) {
				// Simulate GORM setting the ID field after save
				backup := args.Get(1).(*entity.CompactBackup)
				backup.ID = entity.ID(998) // Set mock ID
			}).Return(nil).Once()
		// Second Save: persists DownstreamCompactID generated from the ID above
		s.mockCompactBackupRepo.On("Save", mock.Anything, mock.AnythingOfType("*entity.CompactBackup")).
			Return(nil).Once()
		s.mockWorkflow.On("RunDagWithTags", workflow.DagCreateCompactBackup, mock.Anything, mock.Anything).
			Return(&dagentity.DagInstance{BaseInfo: dagentity.BaseInfo{ID: "test-workflow"}}, nil).Once()
	}

	// Call AsyncCreate to test preCheck indirectly
	_, err := s.model.AsyncCreate(ctx, tc.req)

	// Verify results
	if tc.expectError {
		s.Error(err, "Should return error for test case: %s", tc.name)
	} else {
		s.NoError(err, "Should not return error for test case: %s", tc.name)
	}
}
