package grpc_service

import (
	"context"
	"fmt"

	"github.com/tidbcloud/config-center/internal/db"
	"github.com/tidbcloud/config-center/internal/service"
	pb "github.com/tidbcloud/config-center/proto/config_center"
	"google.golang.org/grpc/codes"
	"google.golang.org/grpc/status"
	"google.golang.org/protobuf/types/known/emptypb"
)

func (c ConfigCenterService) CreateConfigurationTemplate(ctx context.Context, req *pb.CreateConfigurationTemplateRequest) (*pb.ConfigurationTemplate, error) {
	if c.ServiceMode == ModeRegional && req.TemplateId == nil {
		return nil, status.Error(codes.InvalidArgument, "template_id must be set for CreateConfigurationTemplate in regional mode")
	}

	return c.UnifiedService.CreateConfigurationTemplate(req)
}

func (c ConfigCenterService) BatchCreateConfigurationTemplates(ctx context.Context, req *pb.BatchCreateConfigurationTemplatesRequest) (*pb.BatchCreateConfigurationTemplatesResponse, error) {
	resp := pb.BatchCreateConfigurationTemplatesResponse{}
	for _, r := range req.Requests {
		t, err := c.UnifiedService.CreateConfigurationTemplate(r)
		if err != nil {
			return nil, status.Error(codes.Internal, err.Error())
		}
		resp.Templates = append(resp.Templates, t)
	}

	return &resp, nil
}

func (c ConfigCenterService) GetConfigurationTemplate(ctx context.Context, req *pb.GetConfigurationTemplateRequest) (*pb.ConfigurationTemplate, error) {
	return service.GetConfigurationTemplate(c.DB, req)
}

func (c ConfigCenterService) ListConfigurationTemplates(ctx context.Context, req *pb.ListConfigurationTemplatesRequest) (*pb.ListConfigurationTemplatesResponse, error) {
	return service.ListConfigurationTemplates(c.DB, req)
}

func (c ConfigCenterService) DeleteConfigurationTemplate(ctx context.Context, req *pb.DeleteConfigurationTemplateRequest) (*emptypb.Empty, error) {
	return service.DeleteConfigurationTemplate(c.DB, req)
}

func (c ConfigCenterService) SearchDefaultConfigurationTemplate(_ context.Context, req *pb.SearchDefaultConfigurationTemplateRequest) (*pb.ConfigurationTemplate, error) {
	templateID, err := c.UnifiedService.SearchDefaultConfigurationTemplateIdInTx(c.DB, req)
	switch err {
	case nil:
		// found
	case db.ErrNotFound:
		return nil, status.Errorf(codes.NotFound, "[%s]default template for config type %s on version %s and instance %s.%s not found", req.GetClusterType().String(), req.GetConfigurationType(), req.GetVersion(), req.GetCloudProvider(), req.GetNodeInstance())
	default:
		return nil, status.Errorf(codes.Internal, "failed to search default configuration template: %v", err)
	}

	return service.GetConfigurationTemplate(c.DB, &pb.GetConfigurationTemplateRequest{
		ClusterType:       req.GetClusterType(),
		ConfigurationType: req.GetConfigurationType(),
		TemplateId:        templateID,
	})
}

func (c ConfigCenterService) BatchSearchDefaultConfigurationTemplate(_ context.Context, req *pb.BatchSearchDefaultConfigurationTemplateRequest) (*pb.BatchSearchDefaultConfigurationTemplateResponse, error) {
	tx, err := c.DB.Begin()
	if err != nil {
		return nil, err
	}

	resp := pb.BatchSearchDefaultConfigurationTemplateResponse{
		Templates: make([]*pb.ConfigurationTemplate, 0, len(req.GetRequests())),
	}
	for _, req := range req.GetRequests() {
		template, err := service.SearchDefaultConfigurationTemplate(tx, req)
		if err != nil {
			tx.Rollback()
			return nil, err
		}
		resp.Templates = append(resp.Templates, template)
	}

	return &resp, tx.Commit()
}

func (c ConfigCenterService) ListConfigurationTemplateDefaultCaseBindings(_ context.Context, req *pb.ListConfigurationTemplateDefaultCaseBindingsRequest) (*pb.ListConfigurationTemplateDefaultCaseBindingsResponse, error) {
	resp := pb.ListConfigurationTemplateDefaultCaseBindingsResponse{}
	tx, err := c.DB.Begin()
	if err != nil {
		return &resp, status.Error(codes.Internal, err.Error())
	}
	defer tx.Rollback()

	pageSize := req.GetPageSize()
	if pageSize > 200 {
		pageSize = 200
	}
	pageNum := req.GetPageNum()
	if pageNum < 1 {
		pageNum = 1
	}

	total, err := tx.CountDefaultTemplateBindings(req.GetClusterType().String(), req.ConfigurationType, req.Version, req.CloudProvider, req.NodeInstance, req.TenantId, req.ExactMatch)
	if err != nil {
		return &resp, status.Error(codes.Internal, err.Error())
	}
	resp.Total = total

	defaultTemplateRows, err := tx.ListDefaultTemplateBindings(req.GetClusterType().String(), req.ConfigurationType, req.Version, req.CloudProvider, req.NodeInstance, req.TenantId, pageSize, pageNum, req.ExactMatch)
	if err != nil {
		return &resp, status.Error(codes.Internal, err.Error())
	}
	for _, row := range defaultTemplateRows {
		resp.TemplateDefaultCaseBindings = append(resp.TemplateDefaultCaseBindings, &pb.TemplateDefaultCaseBinding{
			ClusterType: req.GetClusterType(),
			TemplateId:  row.TemplateID,
			DefaultCase: &pb.DefaultCase{
				Version:           row.Version,
				CloudProvider:     row.CloudProvider,
				NodeInstance:      row.NodeInstance,
				ConfigurationType: row.ConfigType,
				Region:            row.Region,
				TenantId:          row.TenantID,
			},
		})
	}

	return &resp, tx.Commit()
}

func (c ConfigCenterService) UpdateDefaultConfigurationTemplate(ctx context.Context, req *pb.UpdateDefaultConfigurationTemplateRequest) (*pb.ConfigurationTemplate, error) {
	if req.GetCloudProvider() == "" && req.GetNodeInstance() != "" {
		return &pb.ConfigurationTemplate{}, status.Error(codes.InvalidArgument, "cloud_provider cannot be empty when node_instance is not empty")
	}

	tx, err := c.DB.Begin()
	if err != nil {
		return &pb.ConfigurationTemplate{}, status.Error(codes.Internal, err.Error())
	}
	defer tx.Rollback()

	resp, err := c.UnifiedService.UpdateDefaultConfigurationTemplateInTx(tx, req)
	if err != nil {
		return resp, err
	}
	err = WriteAuditLog(tx, "UpdateDefaultConfigurationTemplate", req.GetClusterType().String(), req.GetConfigurationType(), req, resp)
	if err != nil {
		return &pb.ConfigurationTemplate{}, status.Error(codes.Internal, "failed to write audit log")
	}

	return resp, tx.Commit()
}

func (c ConfigCenterService) DeleteDefaultConfigurationTemplate(ctx context.Context, req *pb.DeleteDefaultConfigurationTemplateRequest) (*emptypb.Empty, error) {
	if req.GetCloudProvider() == "" && req.GetNodeInstance() != "" {
		return &emptypb.Empty{}, status.Error(codes.InvalidArgument, "cloud_provider cannot be empty when node_instance is not empty")
	}

	tx, err := c.DB.Begin()
	if err != nil {
		return &emptypb.Empty{}, status.Error(codes.Internal, err.Error())
	}
	defer tx.Rollback()

	resp, err := c.UnifiedService.DeleteDefaultConfigurationTemplateInTx(tx, req)
	if err != nil {
		return &emptypb.Empty{}, err
	}
	err = WriteAuditLog(tx, "DeleteDefaultConfigurationTemplate", req.GetClusterType().String(), req.GetConfigurationType(), req, resp)
	if err != nil {
		return &emptypb.Empty{}, status.Error(codes.Internal, "failed to write audit log")
	}

	return &emptypb.Empty{}, tx.Commit()
}

func (c ConfigCenterService) BatchUpdateDefaultConfigurationTemplate(ctx context.Context, req *pb.BatchUpdateDefaultConfigurationTemplateRequest) (*pb.BatchUpdateDefaultConfigurationTemplateResponse, error) {
	if len(req.GetRequests()) == 0 {
		return &pb.BatchUpdateDefaultConfigurationTemplateResponse{}, status.Error(codes.InvalidArgument, "requests length should not be 0")
	}
	clusterType := req.GetRequests()[0].GetClusterType()
	for _, req := range req.GetRequests() {
		if req.GetClusterType() != clusterType {
			return &pb.BatchUpdateDefaultConfigurationTemplateResponse{}, status.Error(codes.InvalidArgument, "all cluster type should be the same")
		}
	}

	tx, err := c.DB.Begin()
	if err != nil {
		return &pb.BatchUpdateDefaultConfigurationTemplateResponse{}, err
	}
	defer tx.Rollback()

	resp := pb.BatchUpdateDefaultConfigurationTemplateResponse{
		Templates: make([]*pb.ConfigurationTemplate, 0, len(req.GetRequests())),
	}
	for _, req := range req.GetRequests() {
		template, err := c.UnifiedService.UpdateDefaultConfigurationTemplateInTx(tx, req)
		if err != nil {
			return nil, err
		}
		resp.Templates = append(resp.Templates, template)
	}

	err = WriteAuditLog(tx, "BatchUpdateDefaultConfigurationTemplate", clusterType.String(), "batch", req, resp)
	if err != nil {
		tx.Rollback()
		return &resp, status.Error(codes.Internal, "failed to write audit log")
	}

	return &resp, tx.Commit()
}

func (c ConfigCenterService) GetTemplateUsage(ctx context.Context, req *pb.GetTemplateUsageRequest) (*pb.TemplateUsage, error) {
	templateUsage := pb.TemplateUsage{
		ClusterIds:   make([]string, 0),
		DefaultCases: make([]*pb.DefaultCase, 0),
	}

	clusterConfigInfos, err := c.DB.ListClusterConfigInfoByTemplate(req.GetClusterType().String(), req.GetConfigurationType(), req.GetTemplateId())
	if err != nil {
		return nil, err
	}
	for _, clusterConfigInfo := range clusterConfigInfos {
		templateUsage.ClusterIds = append(templateUsage.ClusterIds, clusterConfigInfo.ClusterID)
	}

	if c.ServiceMode == ModeGlobal {
		for region, regionalClient := range c.UnifiedService.GetRegionalClients(req.GetClusterType()) {
			resp, err := regionalClient.GetTemplateUsage(ctx, req)
			if err != nil {
				return nil, fmt.Errorf("failed to get template usage from regional client %s: %v", region, err)
			}
			templateUsage.ClusterIds = append(templateUsage.ClusterIds, resp.ClusterIds...)
		}
	}

	defaultTemplates, err := c.DB.ListDefaultTemplateByTemplate(req.GetClusterType().String(), req.GetConfigurationType(), req.GetTemplateId())
	if err != nil {
		return nil, err
	}
	for _, defaultTemplate := range defaultTemplates {
		templateUsage.DefaultCases = append(templateUsage.DefaultCases, &pb.DefaultCase{
			Version:           defaultTemplate.Version,
			CloudProvider:     defaultTemplate.CloudProvider,
			NodeInstance:      defaultTemplate.NodeInstance,
			ConfigurationType: defaultTemplate.ConfigType,
			TenantId:          defaultTemplate.TenantID,
		})
	}

	return &templateUsage, nil
}
