package branch_svc

import (
	"context"
	"fmt"
	"time"

	"github.com/tidbcloud/pkgv2/bizerr"
	"github.com/tidbcloud/pkgv2/clusterproxy"
	"github.com/tidbcloud/pkgv2/password"
	"github.com/tidbcloud/pkgv2/tracing"
	"github.com/tidbcloud/serverless-service/idl/pbgen/proto/v1beta"
	"github.com/tidbcloud/serverless-service/internal/domain/constant"
	"github.com/tidbcloud/serverless-service/internal/domain/facade"
	"github.com/tidbcloud/serverless-service/internal/domain/repo"
	"github.com/tidbcloud/serverless-service/internal/domain/service"
	"github.com/tidbcloud/serverless-service/internal/domain/vo"
	"github.com/tidbcloud/serverless-service/internal/errorcode"
	"github.com/tidbcloud/serverless-service/internal/utils"
	"go.opentelemetry.io/otel"
	"go.opentelemetry.io/otel/attribute"
	"google.golang.org/protobuf/types/known/emptypb"
)

var v1betaTracer = otel.Tracer("branch.v1beta")

type branchV1betaService struct {
	bs                         service.BranchService
	ur                         *service.UserRoleService
	accountFacade              facade.AccountFacade
	infraFacade                facade.InfraFacade
	encryptorFacade            facade.EncryptorFacade
	clusterProxyProviderFacade facade.ClusterProxyProviderFacade
	branchRepo                 repo.BranchRepo
	billingService             service.BillingService
	enableProject              bool
	v1beta.UnimplementedBranchServiceServer
	v1beta.UnimplementedUserServiceServer
}

func NewBranchV1betaService(
	bs service.BranchService,
	ur *service.UserRoleService,
	accountFacade facade.AccountFacade,
	infraFacade facade.InfraFacade,
	encryptorFacade facade.EncryptorFacade,
	clusterProxyProviderFacade facade.ClusterProxyProviderFacade,
	branchRepo repo.BranchRepo,
	billingService service.BillingService,
) v1beta.BranchServiceServer {
	return &branchV1betaService{
		bs:                         bs,
		ur:                         ur,
		accountFacade:              accountFacade,
		infraFacade:                infraFacade,
		encryptorFacade:            encryptorFacade,
		clusterProxyProviderFacade: clusterProxyProviderFacade,
		branchRepo:                 branchRepo,
		billingService:             billingService,
		enableProject:              true,
	}
}

// NewBranchV1betaUserService delete this after sql user is officially supported
func NewBranchV1betaUserService(
	bs service.BranchService,
	ur *service.UserRoleService,
	accountFacade facade.AccountFacade,
	infraFacade facade.InfraFacade,
	encryptorFacade facade.EncryptorFacade,
	clusterProxyProviderFacade facade.ClusterProxyProviderFacade,
	branchRepo repo.BranchRepo,
	billingService service.BillingService,
) v1beta.UserServiceServer {
	return &branchV1betaService{
		bs:                         bs,
		ur:                         ur,
		accountFacade:              accountFacade,
		infraFacade:                infraFacade,
		encryptorFacade:            encryptorFacade,
		clusterProxyProviderFacade: clusterProxyProviderFacade,
		branchRepo:                 branchRepo,
		billingService:             billingService,
		enableProject:              true,
	}
}

func (s *branchV1betaService) ListBranches(
	ctx context.Context,
	req *v1beta.ListBranchesRequest,
) (resp *v1beta.ListBranchesResp, err error) {
	start := time.Now()
	ctx, span := v1betaTracer.Start(ctx, "branchV1betaService.ListBranches")
	defer func() {
		tracing.EndSpan(span, err)
		CollectBranchMetrics(MetricTypeV1beta, MetricMethodListBranches, start, err)
		err = errorcode.PrepareBranchErrorMessage(ctx, err)
	}()
	span.SetAttributes(
		attribute.String("main_branch_id", req.ClusterId),
	)

	pager := utils.NewPagerNumberPager()
	err = pager.Normalize(req)
	if err != nil {
		return nil, err
	}

	span.AddEvent("call branch_svc.ListBranches")
	opt := vo.ListBranchesOpt{
		Page:       pager.Page(),
		PageSize:   pager.PageSize(),
		SortByTime: false,
		FullBranch: false,
	}
	branches, count, err := s.bs.ListBranchesInPage(ctx, req.ClusterId, opt)
	if err != nil {
		return nil, err
	}

	brs := make([]*v1beta.BasicBranch, 0, len(branches))
	for _, b := range branches {
		model, err := b.ConvertToV1betaProtoBasic()
		if err != nil {
			return nil, err
		}

		brs = append(brs, model)
	}

	return &v1beta.ListBranchesResp{
		Branches: brs,
		Total:    uint32(count),
	}, nil
}

func (s *branchV1betaService) CreateBranch(
	ctx context.Context,
	req *v1beta.CreateBranchRequest,
) (resp *v1beta.CreateBranchResp, err error) {
	start := time.Now()
	ctx, span := v1betaTracer.Start(ctx, "branchV1betaService.CreateBranch")
	defer func() {
		tracing.EndSpan(span, err)
		CollectBranchMetrics(MetricTypeV1beta, MetricMethodCreateBranch, start, err)
		err = errorcode.PrepareBranchErrorMessage(ctx, err)
	}()
	span.SetAttributes(
		attribute.String("parent_id", req.ClusterId),
		attribute.String("branch_name", req.DisplayName),
	)

	user, err := utils.GetUserFromContext(ctx)
	if err != nil {
		return nil, bizerr.New(bizerr.ErrCodeUnknown, "user not found")
	}

	span.AddEvent("call branch_svc.CreateBranch")
	b, err := s.bs.CreateBranch(ctx, req.ClusterId, req.DisplayName, req.ClusterId, nil, "", user.ID, nil)
	if err != nil {
		return nil, err
	}

	return &v1beta.CreateBranchResp{
		Id: b.ID,
	}, nil
}

func (s *branchV1betaService) GetBranch(
	ctx context.Context,
	req *v1beta.GetBranchRequest,
) (resp *v1beta.Branch, err error) {
	start := time.Now()
	ctx, span := v1betaTracer.Start(ctx, "branchV1betaService.GetBranch")
	defer func() {
		tracing.EndSpan(span, err)
		CollectBranchMetrics(MetricTypeV1beta, MetricMethodGetBranch, start, err)
		err = errorcode.PrepareBranchErrorMessage(ctx, err)
	}()
	span.SetAttributes(
		attribute.String("main_branch_id", req.ClusterId),
		attribute.String("branch_id", req.BranchId),
	)

	span.AddEvent("call branch_svc.GetBranch")
	b, err := s.bs.GetBranchById(ctx, req.ClusterId, req.BranchId)
	if err != nil {
		return nil, err
	}

	resp, err = b.ConvertToV1betaProtoFull()
	if err != nil {
		return nil, err
	}
	return resp, nil
}

func (s *branchV1betaService) DeleteBranch(
	ctx context.Context,
	req *v1beta.DeleteBranchRequest,
) (_ *emptypb.Empty, err error) {
	start := time.Now()
	ctx, span := v1betaTracer.Start(ctx, "branchV1betaService.DeleteBranch")
	defer func() {
		tracing.EndSpan(span, err)
		CollectBranchMetrics(MetricTypeV1beta, MetricMethodDeleteBranch, start, err)
		err = errorcode.PrepareBranchErrorMessage(ctx, err)
	}()
	span.SetAttributes(
		attribute.String("main_branch_id", req.ClusterId),
		attribute.String("branch_id", req.BranchId),
	)

	user, err := utils.GetUserFromContext(ctx)
	if err != nil {
		return nil, bizerr.New(bizerr.ErrCodeUnknown, "user not found")
	}

	span.AddEvent("call branch_svc.DeleteBranch")
	_, err = s.bs.DeleteBranchById(ctx, req.ClusterId, req.BranchId, user.ID)
	if err != nil {
		return nil, err
	}

	return &emptypb.Empty{}, nil
}

func (s *branchV1betaService) CreateUser(
	ctx context.Context,
	req *v1beta.CreateUserReq,
) (resp *v1beta.CreateUserResp, err error) {
	ctx, span := v1betaTracer.Start(ctx, "branchV1betaService.CreateUser")
	defer func() { tracing.EndSpan(span, err) }()
	span.SetAttributes(
		attribute.String("main_branch_id", fmt.Sprint(req.ClusterId)),
		attribute.String("branch_name", req.BranchName),
	)

	span.AddEvent("load branch entity")
	branch, err := s.branchRepo.GetBranchByNameAndMainBranchID(ctx, req.BranchName, fmt.Sprint(req.ClusterId))
	if err != nil {
		return nil, err
	}
	if branch.Status != constant.BranchStatusRunning {
		return nil, bizerr.New(errorcode.ErrCodeBranchNotRunning, "cannot create users on a not running branch")
	}

	span.AddEvent("load branch cr")
	branchCr, err := s.infraFacade.GetBranch(ctx, branch.ProjectID, branch.ID)
	if err != nil {
		return nil, err
	}
	status, err := branchCr.AwsFreeTierClusterStatusFromBranch()
	if err != nil {
		return nil, err
	}

	// TODO: Use delegate user of API Key to create users.
	cloudAdminPwd, err := s.encryptorFacade.DecryptBase64(branch.EncryptedCloudAdminPwd)
	if err != nil {
		return nil, err
	}
	operator := &clusterproxy.ClusterOperator{
		Username:   constant.CloudAdminUsername,
		AuthMethod: clusterproxy.AuthMethodPassword,
		Credential: cloudAdminPwd,
	}
	proxy, err := s.clusterProxyProviderFacade.GetBranchProxy(ctx, branchCr, operator)
	if err != nil {
		return nil, err
	}
	username := status.KeyspaceToken + "." + utils.RandomString(8)
	pwd := password.Decorate(utils.RandomString(32))
	sqls := []string{
		fmt.Sprintf(`CREATE USER IF NOT EXISTS '%s' IDENTIFIED BY '%s';`, username, pwd),
		fmt.Sprintf(`INSERT INTO mysql.role_edges VALUES('%%','role_admin','%%','%s','N');`, username),
		fmt.Sprintf(`SET DEFAULT ROLE ALL TO '%s';`, username),
	}
	err = proxy.ExecuteMultiSQL(ctx, sqls)
	if err != nil {
		return nil, err
	}
	return &v1beta.CreateUserResp{Username: username, Password: pwd}, nil
}

func (s *branchV1betaService) DeleteUser(ctx context.Context, req *v1beta.DeleteUserReq) (_ *emptypb.Empty, err error) {
	ctx, span := v1betaTracer.Start(ctx, "branchV1betaService.DeleteUser")
	defer func() { tracing.EndSpan(span, err) }()
	span.SetAttributes(
		attribute.String("main_branch_id", fmt.Sprint(req.ClusterId)),
		attribute.String("branch_name", req.BranchName),
		attribute.String("username", req.Username),
	)

	span.AddEvent("load branch entity")
	branch, err := s.branchRepo.GetBranchByNameAndMainBranchID(ctx, req.BranchName, fmt.Sprint(req.ClusterId))
	if err != nil {
		return nil, err
	}
	if branch.Status != constant.BranchStatusRunning {
		return nil, bizerr.New(errorcode.ErrCodeBranchNotRunning, "cannot delete users on a not running branch")
	}

	// TODO: Use delegate user of API Key to delete users.
	cloudAdminPwd, err := s.encryptorFacade.DecryptBase64(branch.EncryptedCloudAdminPwd)
	if err != nil {
		return nil, err
	}
	operator := &service.SqlOperator{
		Username:   constant.CloudAdminUsername,
		AuthMethod: constant.AuthMethodPassword,
		Credential: cloudAdminPwd,
	}
	userInfo := &service.UserInfo{
		Username: req.Username,
	}
	span.AddEvent("delete sql user")
	err = s.ur.DeleteUser(ctx, operator, branch, userInfo)
	if err != nil {
		return nil, err
	}

	return &emptypb.Empty{}, nil
}
