package actors

import (
	"context"
	"testing"

	"github.com/go-logr/logr"
	"go.uber.org/mock/gomock"

	"github.com/tidbcloud/devops/entity"
	tntent "github.com/tidbcloud/devops/entity/tenant"
	"github.com/tidbcloud/devops/pkg/infra/config"
	apprsvcapi "github.com/tidbcloud/devops/pkg/service/approval/api"
	rmapi "github.com/tidbcloud/devops/pkg/service/remediationticket/api"
	rmrepo "github.com/tidbcloud/devops/pkg/service/remediationticket/repo"
)

func TestActorPendingSendApprovalSetsApprovedStatusForSucceedPhase(t *testing.T) {
	ctrl := gomock.NewController(t)
	repo := rmrepo.NewMockRemediationTicketRepo(ctrl)
	approvalSvc := apprsvcapi.NewMockApprovalServiceAPI(ctrl)
	phase := entity.RemediationTicketPhaseRegionalGM
	ticket := &entity.RemediationTicket{
		ID:                   1,
		Phase:                &phase,
		Status:               entity.RemediationTicketStatusPending,
		ApplicantEmail:       "xieyujie@pingcap.com",
		ExtraPercent:         "0",
		AssociatedIncidentId: 29,
	}
	var patched *entity.RemediationTicket
	repo.EXPECT().
		FunctionalPatchTicket(gomock.Any(), ticket, gomock.Any()).
		DoAndReturn(func(ctx context.Context, current *entity.RemediationTicket, patch ...rmrepo.PatchRemediationTicket) error {
			cloned := *current
			for _, p := range patch {
				if err := p(&cloned); err != nil {
					return err
				}
			}
			patched = &cloned
			return nil
		})

	actor := NewRemediationTicketActor(context.Background(), Components{
		Logger:                logr.Discard(),
		RTConfig:              config.RemediationTicketConfig{},
		RemediationTicketRepo: repo,
		ApprovalService:       approvalSvc,
	}, ticket)

	if err := actor.SendApproval(rmapi.TransferTicketParam{}); err != nil {
		t.Fatalf("SendApproval() error = %v", err)
	}
	if patched == nil {
		t.Fatalf("expected ticket to be patched")
	}
	if patched.Phase == nil || *patched.Phase != entity.RemediationTicketPhaseSucceed {
		t.Fatalf("expected phase to be succeed, got %v", patched.Phase)
	}
	if patched.Status != entity.RemediationTicketStatusApproved {
		t.Fatalf("expected status to be approved, got %s", patched.Status)
	}
	if patched.ApprID != 0 {
		t.Fatalf("expected appr_id to be reset to 0, got %d", patched.ApprID)
	}
}

func TestActorPendingSendApprovalCreatesSuccessApprovalCC(t *testing.T) {
	t.Setenv("APP_ENV", "dev")
	t.Setenv("APP_PARTITION", "global")

	ctrl := gomock.NewController(t)
	repo := rmrepo.NewMockRemediationTicketRepo(ctrl)
	approvalSvc := apprsvcapi.NewMockApprovalServiceAPI(ctrl)
	phase := entity.RemediationTicketPhaseRegionalGM
	region := tntent.TenantRegionAPAC
	ticket := &entity.RemediationTicket{
		ID:                   1,
		Phase:                &phase,
		Status:               entity.RemediationTicketStatusPending,
		ApplicantEmail:       "creator@pingcap.com",
		ExtraPercent:         "0",
		AssociatedIncidentId: 29,
		BizRegion:            &region,
	}
	apprID := uint64(1001)
	approvalSvc.EXPECT().
		CreateApproval(gomock.Any(), gomock.Any()).
		DoAndReturn(func(ctx context.Context, param apprsvcapi.CreateApprovalParam) (*uint64, error) {
			if param.Name != "Remediation Ticket Completed" {
				t.Fatalf("approval name = %q, want completion approval", param.Name)
			}
			if len(param.Nodes) != 0 {
				t.Fatalf("completion approval nodes = %#v, want empty", param.Nodes)
			}
			if param.CCTasks == nil || len(param.CCTasks.Emails) != 2 {
				t.Fatalf("completion approval cc = %#v, want configured emails", param.CCTasks)
			}
			return &apprID, nil
		})
	repo.EXPECT().
		SaveTicketPhaseApproval(gomock.Any(), gomock.Any()).
		DoAndReturn(func(ctx context.Context, approval *entity.RemediationTicketPhaseApproval) (*uint64, error) {
			if approval.TicketId != ticket.ID {
				t.Fatalf("ticket phase approval ticket id = %d, want %d", approval.TicketId, ticket.ID)
			}
			if approval.FromPhase != entity.RemediationTicketPhaseRegionalGM || approval.ToPhase != entity.RemediationTicketPhaseSucceed {
				t.Fatalf("ticket phase approval transition = %s -> %s, want regional_gm -> succeed", approval.FromPhase, approval.ToPhase)
			}
			if approval.ApprovalID != apprID {
				t.Fatalf("ticket phase approval id = %d, want %d", approval.ApprovalID, apprID)
			}
			id := uint64(10)
			return &id, nil
		})
	repo.EXPECT().
		FunctionalPatchTicket(gomock.Any(), ticket, gomock.Any()).
		DoAndReturn(func(ctx context.Context, current *entity.RemediationTicket, patch ...rmrepo.PatchRemediationTicket) error {
			cloned := *current
			for _, p := range patch {
				if err := p(&cloned); err != nil {
					return err
				}
			}
			if cloned.Phase == nil || *cloned.Phase != entity.RemediationTicketPhaseSucceed {
				t.Fatalf("patched phase = %v, want succeed", cloned.Phase)
			}
			return nil
		})

	actor := NewRemediationTicketActor(context.Background(), Components{
		Logger: logr.Discard(),
		RTConfig: config.RemediationTicketConfig{
			SuccessApprovalCCByBizRegion: map[string][]string{
				string(tntent.TenantRegionAPAC): {"cc1@pingcap.com", "cc2@pingcap.com"},
			},
		},
		RemediationTicketRepo: repo,
		ApprovalService:       approvalSvc,
	}, ticket)

	if err := actor.SendApproval(rmapi.TransferTicketParam{}); err != nil {
		t.Fatalf("SendApproval() error = %v", err)
	}
}

func TestActorPendingCancelSetsClosedPhaseAndStatus(t *testing.T) {
	ctrl := gomock.NewController(t)
	repo := rmrepo.NewMockRemediationTicketRepo(ctrl)
	approvalSvc := apprsvcapi.NewMockApprovalServiceAPI(ctrl)
	phase := entity.RemediationTicketPhaseRegionTSELeader
	ticket := &entity.RemediationTicket{
		ID:             2,
		Phase:          &phase,
		Status:         entity.RemediationTicketStatusPending,
		ApplicantEmail: "xieyujie@pingcap.com",
	}
	var patched *entity.RemediationTicket
	repo.EXPECT().
		FunctionalPatchTicket(gomock.Any(), ticket, gomock.Any()).
		DoAndReturn(func(ctx context.Context, current *entity.RemediationTicket, patch ...rmrepo.PatchRemediationTicket) error {
			cloned := *current
			for _, p := range patch {
				if err := p(&cloned); err != nil {
					return err
				}
			}
			patched = &cloned
			return nil
		})

	actor := NewRemediationTicketActor(context.Background(), Components{
		Logger:                logr.Discard(),
		RTConfig:              config.RemediationTicketConfig{},
		RemediationTicketRepo: repo,
		ApprovalService:       approvalSvc,
	}, ticket)

	if err := actor.Cancel(); err != nil {
		t.Fatalf("Cancel() error = %v", err)
	}
	if patched == nil {
		t.Fatalf("expected ticket to be patched")
	}
	if patched.Phase == nil || *patched.Phase != entity.RemediationTicketPhaseClosed {
		t.Fatalf("expected phase to be closed, got %v", patched.Phase)
	}
	if patched.Status != entity.RemediationTicketStatusClosed {
		t.Fatalf("expected status to be closed, got %s", patched.Status)
	}
}
