package branch_svc

import (
	"context"
	"time"

	"github.com/tidbcloud/pkgv2/bizerr"
	"github.com/tidbcloud/pkgv2/tracing"
	"github.com/tidbcloud/serverless-service/idl/pbgen/proto/v1beta1"
	"github.com/tidbcloud/serverless-service/internal/domain/facade"
	"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"
)

var v1beta1Tracer = otel.Tracer("branch.v1beta1")

type branchV1beta1Service struct {
	bs                         service.BranchService
	billingService             service.BillingService
	infraFacade                facade.InfraFacade
	clusterProxyProviderFacade facade.ClusterProxyProviderFacade
	encryptorFacade            facade.EncryptorFacade
	v1beta1.UnimplementedBranchServiceServer
}

func NewBranchV1beta1Service(
	bs service.BranchService,
	billingService service.BillingService,
	infraFacade facade.InfraFacade,
	clusterProxyProviderFacade facade.ClusterProxyProviderFacade,
	encryptorFacade facade.EncryptorFacade,
) v1beta1.BranchServiceServer {
	return &branchV1beta1Service{
		bs:                         bs,
		billingService:             billingService,
		infraFacade:                infraFacade,
		clusterProxyProviderFacade: clusterProxyProviderFacade,
		encryptorFacade:            encryptorFacade,
	}
}

func (s *branchV1beta1Service) CreateBranch(
	ctx context.Context,
	req *v1beta1.CreateBranchRequest,
) (_ *v1beta1.Branch, err error) {
	start := time.Now()
	ctx, span := v1beta1Tracer.Start(ctx, "branchV1beta1Service.CreateBranch")
	defer func() {
		tracing.EndSpan(span, err)
		CollectBranchMetrics(MetricTypeV1beta1, MetricMethodCreateBranch, start, err)
		err = errorcode.PrepareBranchErrorMessage(ctx, err)
	}()

	parentId, err := utils.GetBranchParentId(req.Branch.ParentId, req.ClusterId)
	if err != nil {
		return nil, bizerr.New(errorcode.ErrCodeInvalidBranchParentId, "invalid parent id")
	}

	span.SetAttributes(
		attribute.String("cluster_id", req.ClusterId),
		attribute.String("parent_id", parentId),
		attribute.String("branch_display_name", req.Branch.DisplayName),
	)

	parentTimestamp := utils.ConvertTimestamp(req.Branch.ParentTimestamp)
	if parentTimestamp != nil {
		span.SetAttributes(
			attribute.String("parent_timestamp", parentTimestamp.String()),
		)
	}

	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.Branch.DisplayName,
		parentId,
		parentTimestamp,
		req.Branch.RootPassword,
		user.ID,
		nil,
	)
	if err != nil {
		return nil, err
	}
	v1beta1Branch, err := b.ConvertToV1beta1ProtoBasic()
	if err != nil {
		return nil, err
	}
	return v1beta1Branch, nil
}

func (s *branchV1beta1Service) ListBranches(
	ctx context.Context,
	req *v1beta1.ListBranchesRequest,
) (_ *v1beta1.ListBranchesResponse, err error) {
	start := time.Now()
	ctx, span := v1beta1Tracer.Start(ctx, "branchV1beta1Service.ListBranches")
	defer func() {
		tracing.EndSpan(span, err)
		CollectBranchMetrics(MetricTypeV1beta1, MetricMethodListBranches, start, err)
		err = errorcode.PrepareBranchErrorMessage(ctx, err)
	}()
	span.SetAttributes(
		attribute.String("main_branch_id", req.ClusterId),
	)

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

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

	brs := make([]*v1beta1.Branch, 0, len(branches))
	for _, b := range branches {
		v1beta1Branch, err := b.ConvertToV1beta1ProtoBasic()
		if err != nil {
			return nil, err
		}
		brs = append(brs, v1beta1Branch)
	}

	resp := &v1beta1.ListBranchesResponse{
		Branches:  brs,
		TotalSize: uint32(count),
	}
	resp.NextPageToken = pager.NextPageToken(uint32(count))

	return resp, nil
}

func (s *branchV1beta1Service) GetBranch(
	ctx context.Context,
	req *v1beta1.GetBranchRequest,
) (_ *v1beta1.Branch, err error) {
	start := time.Now()
	ctx, span := v1beta1Tracer.Start(ctx, "branchV1beta1Service.GetBranch")
	defer func() {
		tracing.EndSpan(span, err)
		CollectBranchMetrics(MetricTypeV1beta1, 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
	}

	var v1beta1Branch *v1beta1.Branch

	if req.View != nil && *req.View == v1beta1.BranchView_BASIC {
		v1beta1Branch, err = b.ConvertToV1beta1ProtoBasic()
		if err != nil {
			return nil, err
		}
	} else {
		v1beta1Branch, err = b.ConvertToV1beta1ProtoFull()
		if err != nil {
			return nil, err
		}
	}

	return v1beta1Branch, nil
}

func (s *branchV1beta1Service) DeleteBranch(
	ctx context.Context,
	req *v1beta1.DeleteBranchRequest,
) (_ *v1beta1.Branch, err error) {
	start := time.Now()
	ctx, span := v1beta1Tracer.Start(ctx, "branchV1beta1Service.DeleteBranch")
	defer func() {
		tracing.EndSpan(span, err)
		CollectBranchMetrics(MetricTypeV1beta1, MetricMethodDeleteBranch, 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.DeleteBranch")
	user, err := utils.GetUserFromContext(ctx)
	if err != nil {
		return nil, bizerr.New(bizerr.ErrCodeUnknown, "user not found")
	}
	b, err := s.bs.DeleteBranchById(ctx, req.ClusterId, req.BranchId, user.ID)
	if err != nil {
		return nil, err
	}

	v1beta1Branch, err := b.ConvertToV1beta1ProtoBasic()
	if err != nil {
		return nil, err
	}
	return v1beta1Branch, nil
}

func (s *branchV1beta1Service) ResetBranch(
	ctx context.Context,
	req *v1beta1.ResetBranchRequest,
) (_ *v1beta1.Branch, err error) {
	start := time.Now()
	ctx, span := v1beta1Tracer.Start(ctx, "branchV1beta1Service.ResetBranch")
	defer func() {
		tracing.EndSpan(span, err)
		CollectBranchMetrics(MetricTypeV1beta1, MetricMethodResetBranch, 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.ResetBranch")
	b, err := s.bs.ResetBranchById(ctx, req.ClusterId, req.BranchId)
	if err != nil {
		return nil, err
	}

	v1beta1Branch, err := b.ConvertToV1beta1ProtoBasic()
	if err != nil {
		return nil, err
	}
	return v1beta1Branch, nil
}

func (s *branchV1beta1Service) UpdateSqlUser(
	ctx context.Context,
	req *v1beta1.UpdateSqlUserRequest,
) (_ *v1beta1.SqlUser, err error) {
	start := time.Now()
	ctx, span := v1beta1Tracer.Start(ctx, "branchV1beta1Service.UpdateSqlUser")
	defer func() {
		tracing.EndSpan(span, err)
		CollectBranchMetrics(MetricTypeV1beta1, MetricMethodUpdateSqlUser, start, err)
		err = errorcode.PrepareBranchErrorMessage(ctx, err)
	}()

	span.SetAttributes(
		attribute.String("cluster_id", req.ClusterId),
		attribute.String("branch_id", req.BranchId),
		attribute.String("username", req.Username),
	)

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

	if req.Password == "" {
		return nil, bizerr.New(bizerr.ErrCodeParams, "password is required")
	}

	span.AddEvent("call branch_svc.UpdateSqlUser")
	if err := s.bs.UpdateSqlUser(ctx, req.ClusterId, req.BranchId, req.Username, req.Password); err != nil {
		return nil, err
	}

	return &v1beta1.SqlUser{
		Username: req.Username,
	}, nil
}
