package grpc_service

import (
	"context"
	"log/slog"

	"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) CreateClusterConfigurationFile(ctx context.Context, req *pb.CreateClusterConfigurationFileRequest) (*pb.ConfigurationFile, error) {
	return service.CreateClusterConfigurationFile(c.DB, req)
}

func (c ConfigCenterService) BatchCreateClusterConfigurationFiles(ctx context.Context, req *pb.BatchCreateClusterConfigurationFilesRequest) (*pb.BatchCreateClusterConfigurationFilesResponse, error) {
	tx, err := c.DB.Begin()
	if err != nil {
		return nil, err
	}
	defer tx.Rollback()

	if !req.GetValidateOnly() {
		for _, r := range req.GetRequests() {
			if r.GetValidateOnly() {
				return nil, status.Error(codes.InvalidArgument, "validate_only should be set at BatchCreateClusterConfigurationFilesRequest")
			}
		}
	}

	resp := pb.BatchCreateClusterConfigurationFilesResponse{
		Files: make([]*pb.ConfigurationFile, 0, len(req.GetRequests())),
	}
	for _, r := range req.GetRequests() {
		if req.GetValidateOnly() {
			r.ValidateOnly = true
		}
		f, err := service.CreateClusterConfigurationFile(tx, r)
		if err != nil {
			return nil, err
		}
		resp.Files = append(resp.Files, f)
	}

	return &resp, tx.Commit()
}

func (c ConfigCenterService) AutoCreateClusterConfigurationFile(ctx context.Context, req *pb.AutoCreateClusterConfigurationFileRequest) (*pb.ConfigurationFile, error) {
	return service.AutoCreateClusterConfigurationFile(c.DB, req)
}

func (c ConfigCenterService) BatchAutoCreateClusterConfigurationFiles(ctx context.Context, req *pb.BatchAutoCreateClusterConfigurationFilesRequest) (*pb.BatchAutoCreateClusterConfigurationFilesResponse, error) {
	tx, err := c.DB.Begin()
	if err != nil {
		return nil, err
	}

	resp := pb.BatchAutoCreateClusterConfigurationFilesResponse{
		Files: make([]*pb.ConfigurationFile, 0, len(req.GetRequests())),
	}
	for _, req := range req.GetRequests() {
		r, err := service.AutoCreateClusterConfigurationFile(tx, req)
		if err != nil {
			tx.Rollback()
			return nil, err
		}
		resp.Files = append(resp.Files, r)
	}

	return &resp, tx.Commit()
}

func (c ConfigCenterService) GetClusterConfigurationFile(ctx context.Context, req *pb.GetClusterConfigurationFileRequest) (*pb.ConfigurationFile, error) {
	return service.GetClusterConfigurationFile(c.DB, req)
}

func (c ConfigCenterService) ListClusterConfigurationFiles(ctx context.Context, req *pb.ListClusterConfigurationFilesRequest) (*pb.ListClusterConfigurationFilesResponse, error) {
	return service.ListClusterConfigurationFiles(c.DB, req)
}

func (c ConfigCenterService) UpsertClusterCustomParams(ctx context.Context, req *pb.UpsertClusterCustomParamsRequest) (*pb.ConfigurationFile, error) {
	return service.UpsertClusterCustomParams(c.DB, req)
}

func (c ConfigCenterService) BatchUpsertClusterCustomParams(ctx context.Context, req *pb.BatchUpsertClusterCustomParamsRequest) (*pb.BatchUpsertClusterCustomParamsResponse, error) {
	if len(req.GetRequests()) == 0 {
		return &pb.BatchUpsertClusterCustomParamsResponse{}, status.Error(codes.InvalidArgument, "requests length should not be 0")
	}
	clusterType := req.GetRequests()[0].GetClusterType()
	clusterID := req.GetRequests()[0].GetClusterId()
	for _, r := range req.GetRequests() {
		if r.GetClusterType() != clusterType {
			return &pb.BatchUpsertClusterCustomParamsResponse{}, status.Error(codes.InvalidArgument, "all cluster type should be the same")
		}
		if r.GetClusterId() != clusterID {
			return &pb.BatchUpsertClusterCustomParamsResponse{}, status.Error(codes.InvalidArgument, "all clusterID should be the same")
		}
	}
	tx, err := c.DB.Begin()
	if err != nil {
		return &pb.BatchUpsertClusterCustomParamsResponse{}, status.Error(codes.Internal, err.Error())
	}
	defer tx.Rollback()

	resp, err := service.BatchUpsertClusterCustomParams(tx, req)
	if err != nil {
		return &pb.BatchUpsertClusterCustomParamsResponse{}, err
	}
	err = WriteAuditLog(tx, "BatchUpsertClusterCustomParams", clusterType.String(), clusterID, req, resp)
	if err != nil {
		return &pb.BatchUpsertClusterCustomParamsResponse{}, status.Error(codes.Internal, "failed to write audit log")
	}
	return resp, tx.Commit()
}

func (c ConfigCenterService) UpdateClusterConfigurationFile(ctx context.Context, req *pb.UpdateClusterConfigurationFileRequest) (*pb.ConfigurationFile, error) {
	if err := c.validateUpdateClusterConfigurationFileRequest(req); err != nil {
		return nil, err
	}

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

	resp, err := service.UpdateClusterConfigurationFile(tx, req)
	if err != nil {
		return &pb.ConfigurationFile{}, err
	}

	err = WriteAuditLog(tx, "UpdateClusterConfigurationFile", req.GetClusterType().String(), req.GetClusterId(), req, resp)
	if err != nil {
		return &pb.ConfigurationFile{}, status.Error(codes.Internal, "failed to write audit log")
	}

	return resp, tx.Commit()
}

func (c ConfigCenterService) BatchUpdateClusterConfigurationFiles(ctx context.Context, req *pb.BatchUpdateClusterConfigurationFilesRequest) (*pb.BatchUpdateClusterConfigurationFilesResponse, error) {
	if len(req.GetRequests()) == 0 {
		return &pb.BatchUpdateClusterConfigurationFilesResponse{}, status.Error(codes.InvalidArgument, "requests length should not be 0")
	}
	clusterType := req.GetRequests()[0].GetClusterType()
	clusterID := req.GetRequests()[0].GetClusterId()
	for _, r := range req.GetRequests() {
		if r.GetClusterType() != clusterType {
			return &pb.BatchUpdateClusterConfigurationFilesResponse{}, status.Error(codes.InvalidArgument, "all cluster type should be the same")
		}
		if r.GetClusterId() != clusterID {
			return &pb.BatchUpdateClusterConfigurationFilesResponse{}, status.Error(codes.InvalidArgument, "all clusterID should be the same")
		}
		if r.GetValidateOnly() && !req.GetValidateOnly() {
			return nil, status.Error(codes.InvalidArgument, "validate_only should be set at BatchUpdateClusterConfigurationFilesRequest")
		}
		if err := c.validateUpdateClusterConfigurationFileRequest(r); err != nil {
			return nil, err
		}
	}

	tx, err := c.DB.Begin()
	if err != nil {
		return &pb.BatchUpdateClusterConfigurationFilesResponse{}, status.Error(codes.Internal, err.Error())
	}
	resp, err := service.BatchUpdateClusterConfigurationFiles(tx, req)
	if err != nil {
		tx.Rollback()
		return &pb.BatchUpdateClusterConfigurationFilesResponse{}, err
	}

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

	return resp, tx.Commit()
}

func (c ConfigCenterService) AutoUpdateClusterConfigurationFile(ctx context.Context, req *pb.AutoUpdateClusterConfigurationFileRequest) (*pb.ConfigurationFile, error) {
	tx, err := c.DB.Begin()
	if err != nil {
		return &pb.ConfigurationFile{}, status.Error(codes.Internal, err.Error())
	}
	resp, err := service.AutoUpdateClusterConfigurationFile(tx, req)
	if err != nil {
		tx.Rollback()
		return &pb.ConfigurationFile{}, err
	}

	err = WriteAuditLog(tx, "AutoUpdateClusterConfigurationFile", req.GetClusterType().String(), req.GetClusterId(), req, resp)
	if err != nil {
		tx.Rollback()
		return &pb.ConfigurationFile{}, status.Error(codes.Internal, "failed to write audit log")
	}

	return resp, tx.Commit()
}

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

	tx, err := c.DB.Begin()
	if err != nil {
		return &pb.BatchAutoUpdateClusterConfigurationFilesResponse{}, status.Error(codes.Internal, err.Error())
	}
	resp, err := service.BatchAutoUpdateClusterConfigurationFiles(tx, req)
	if err != nil {
		tx.Rollback()
		return &pb.BatchAutoUpdateClusterConfigurationFilesResponse{}, err
	}

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

	return resp, tx.Commit()
}

func (c ConfigCenterService) DeleteClusterConfigurationFile(ctx context.Context, req *pb.DeleteClusterConfigurationFileRequest) (*emptypb.Empty, error) {
	return &emptypb.Empty{}, service.DeleteClusterConfigurationFile(c.DB, req.GetClusterType().String(), req.GetClusterId(), req.GetConfigurationFilename())
}

func (c ConfigCenterService) BatchDeleteClusterConfigurationFile(ctx context.Context, req *pb.BatchDeleteClusterConfigurationFilesRequest) (*emptypb.Empty, error) {
	tx, err := c.DB.Begin()
	if err != nil {
		return nil, err
	}

	for _, req := range req.GetRequests() {
		err = service.DeleteClusterConfigurationFile(tx, req.GetClusterType().String(), req.GetClusterId(), req.GetConfigurationFilename())
		if err != nil {
			tx.Rollback()
			return nil, err
		}
	}

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

func (c ConfigCenterService) GetTenantInitialCustomParams(ctx context.Context, req *pb.GetTenantInitialCustomParamsRequest) (*pb.TenantInitialCustomPatams, error) {
	return service.GetTenantInitialCustomParams(c.DB, req)
}
func (c ConfigCenterService) ListTenantInitialCustomParams(ctx context.Context, req *pb.ListTenantInitialCustomParamsRequest) (*pb.ListTenantInitialCustomParamsResponse, error) {
	tx, err := c.DB.Begin()
	if err != nil {
		return nil, err
	}

	types, err := tx.ListTenantInitialConfigTypes(req.GetClusterType().String(), req.GetTenantId())
	if err != nil {
		tx.Rollback()
		return nil, err
	}

	var resp pb.ListTenantInitialCustomParamsResponse
	for _, t := range types {
		params, err := tx.ListTenantInitialCustomParams(req.GetClusterType().String(), t, req.GetTenantId())
		if err != nil {
			tx.Rollback()
			return nil, err
		}

		initialParams := make(map[string]*pb.ParamItem)
		for _, param := range params {
			v, err := pb.NewValue(param.Value.Raw)
			if err != nil {
				tx.Rollback()
				return nil, err
			}
			initialParams[param.Key] = &pb.ParamItem{
				Value:   v,
				Comment: param.Comment,
			}
		}

		resp.TenantInitialCustomParams = append(resp.TenantInitialCustomParams,
			&pb.TenantInitialCustomPatams{
				ClusterType:       req.GetClusterType(),
				ConfigurationType: t,
				InitialParams:     initialParams,
			},
		)
	}

	return &resp, tx.Commit()
}

func (c ConfigCenterService) UpdateTenantInitialCustomParams(ctx context.Context, req *pb.UpdateTenantInitialCustomParamsRequest) (*pb.TenantInitialCustomPatams, error) {
	return service.UpdateTenantInitialCustomParams(c.DB, req)
}

func (c ConfigCenterService) BatchUpdateTenantInitialCustomParams(ctx context.Context, req *pb.BatchUpdateTenantInitialCustomParamsRequest) (*pb.BatchUpdateTenantInitialCustomParamsResponse, error) {
	tx, err := c.DB.Begin()
	if err != nil {
		return nil, err
	}
	defer tx.Rollback()

	var resp pb.BatchUpdateTenantInitialCustomParamsResponse
	for _, req := range req.GetRequests() {
		r, err := service.UpdateTenantInitialCustomParams(tx, req)
		if err != nil {
			return nil, err
		}
		resp.TenantInitialCustomParams = append(resp.TenantInitialCustomParams, r)
	}

	if len(req.GetRequests()) > 0 {
		for region, client := range c.UnifiedService.GetRegionalClients(req.GetRequests()[0].GetClusterType()) {
			_, err := client.BatchUpdateTenantInitialCustomParams(ctx, req)
			if err != nil {
				slog.Error("failed to finish calling regional BatchUpdateTenantInitialCustomParams", "region", region, "error", err)
			}
		}
	}

	return &resp, tx.Commit()
}

func (c ConfigCenterService) validateUpdateClusterConfigurationFileRequest(req *pb.UpdateClusterConfigurationFileRequest) error {
	if req.GetClusterType() == pb.ClusterType_CLUSTER_TYPE_UNSPECIFIED {
		return status.Error(codes.InvalidArgument, "cluster_type is required")
	}
	if req.GetClusterId() == "" {
		return status.Error(codes.InvalidArgument, "cluster_id is required")
	}
	if req.GetConfigurationFilename() == "" {
		return status.Error(codes.InvalidArgument, "configuration_filename is required")
	}
	if req.GetClearCustomParams() && len(req.GetCustomParams()) > 0 {
		return status.Error(codes.InvalidArgument, "clear_custom_params and custom_params cannot be set at the same time")
	}
	return nil
}
