package region

import (
	"context"

	"google.golang.org/protobuf/types/known/emptypb"

	globalcommonv1 "github.com/tidbcloud/tidb-management-service/api/spec/global/common/v1"
	globaldedicatedv1 "github.com/tidbcloud/tidb-management-service/api/spec/global/dedicated/v1"
	globalngv1 "github.com/tidbcloud/tidb-management-service/api/spec/global/nextgen/v1"
	globalserverlessv1 "github.com/tidbcloud/tidb-management-service/api/spec/global/serverless/v1"
	tidbmgmtservicev1 "github.com/tidbcloud/tidb-management-service/api/spec/tidb_mgmt_service/v1"
	"github.com/tidbcloud/tidb-management-service/utilities/bizerr"
	"github.com/tidbcloud/tidb-management-service/utilities/enum"
)

var allServicePlans = []globalcommonv1.ServicePlan{
	globalcommonv1.ServicePlan_Starter,
	globalcommonv1.ServicePlan_Essential,
	globalcommonv1.ServicePlan_Premium,
	globalcommonv1.ServicePlan_BYOC,
	globalcommonv1.ServicePlan_Essential_V2,
	globalcommonv1.ServicePlan_Premium_Reserved,
}

func (c serverImpl) RegisterCloudProvider(
	ctx context.Context,
	req *tidbmgmtservicev1.RegisterCloudProviderRequest,
) (*tidbmgmtservicev1.CloudProvider, error) {
	if req.GetCloudProvider() == nil {
		return nil, bizerr.New(bizerr.ErrCodeParams, "cloud provider is invalid")
	}

	switch req.GetCloudProvider().GetDeployType() {
	case tidbmgmtservicev1.DeployType_DEPLOY_TYPE_SERVERLESS:
		p, err := c.serverlessCli.RegisterCloudProvider(ctx, &globalserverlessv1.CloudProviderServiceRegisterRequest{
			CloudProvider: req.GetCloudProvider().GetCloudProvider(),
		})
		if err != nil {
			return nil, err
		}
		return &tidbmgmtservicev1.CloudProvider{
			DeployType:    tidbmgmtservicev1.DeployType_DEPLOY_TYPE_SERVERLESS,
			CloudProvider: p,
		}, nil

	case tidbmgmtservicev1.DeployType_DEPLOY_TYPE_DEDICATED:
		p, err := c.dedicatedCli.RegisterCloudProvider(ctx, &globaldedicatedv1.RegisterCloudProviderRequest{
			CloudProvider: req.GetCloudProvider().GetCloudProvider(),
		})
		if err != nil {
			return nil, err
		}
		return &tidbmgmtservicev1.CloudProvider{
			DeployType:    tidbmgmtservicev1.DeployType_DEPLOY_TYPE_DEDICATED,
			CloudProvider: p,
		}, nil

	case tidbmgmtservicev1.DeployType_DEPLOY_TYPE_NEXTGEN:
		p, err := c.nextgenCli.RegisterCloudProvider(ctx, &globalngv1.RegisterCloudProviderRequest{
			CloudProvider: req.GetCloudProvider().GetCloudProvider(),
		})
		if err != nil {
			return nil, err
		}
		return &tidbmgmtservicev1.CloudProvider{
			DeployType:    tidbmgmtservicev1.DeployType_DEPLOY_TYPE_NEXTGEN,
			CloudProvider: p,
		}, nil
	default:
		return nil, bizerr.New(bizerr.ErrCodeParams, "deploy type is invalid")
	}
}

func (c serverImpl) ListCloudProviders(
	ctx context.Context,
	req *tidbmgmtservicev1.ListCloudProvidersRequest,
) (*tidbmgmtservicev1.ListCloudProvidersResponse, error) {
	switch req.GetDeployType() {
	case tidbmgmtservicev1.DeployType_DEPLOY_TYPE_SERVERLESS:
		ps, err := c.serverlessCli.ListCloudProviders(ctx, &globalserverlessv1.CloudProviderServiceListRequest{
			OrganizationId: 0,
		})
		if err != nil {
			return nil, err
		}
		var res []*tidbmgmtservicev1.CloudProvider
		for _, p := range ps.GetCloudProviders() {
			for _, region := range p.GetRegions() {
				normalizeRegionForResponse(region)
			}
			res = append(res, &tidbmgmtservicev1.CloudProvider{
				DeployType:    tidbmgmtservicev1.DeployType_DEPLOY_TYPE_SERVERLESS,
				CloudProvider: p,
			})
		}
		return &tidbmgmtservicev1.ListCloudProvidersResponse{
			CloudProviders: res,
		}, nil
	case tidbmgmtservicev1.DeployType_DEPLOY_TYPE_DEDICATED:
		ps, err := c.dedicatedCli.ListCloudProviders(ctx, &globaldedicatedv1.ListCloudProvidersRequest{})
		if err != nil {
			return nil, err
		}
		var res []*tidbmgmtservicev1.CloudProvider
		for _, p := range ps.GetCloudProviders() {
			for _, region := range p.GetRegions() {
				normalizeRegionForResponse(region)
			}
			res = append(res, &tidbmgmtservicev1.CloudProvider{
				DeployType:    tidbmgmtservicev1.DeployType_DEPLOY_TYPE_DEDICATED,
				CloudProvider: p,
			})
		}
		return &tidbmgmtservicev1.ListCloudProvidersResponse{
			CloudProviders: res,
		}, nil
	case tidbmgmtservicev1.DeployType_DEPLOY_TYPE_NEXTGEN:
		var res []*tidbmgmtservicev1.CloudProvider
		for _, plan := range allServicePlans {
			ps, err := c.nextgenCli.ListCloudProviders(ctx, &globalngv1.ListCloudProvidersRequest{
				ServicePlan: plan,
			})
			if err != nil {
				return nil, err
			}
			for _, p := range ps.GetCloudProviders() {
				for _, region := range p.GetRegions() {
					normalizeRegionForResponse(region)
				}
				res = append(res, &tidbmgmtservicev1.CloudProvider{
					DeployType:    tidbmgmtservicev1.DeployType_DEPLOY_TYPE_NEXTGEN,
					CloudProvider: p,
				})
			}
		}
		return &tidbmgmtservicev1.ListCloudProvidersResponse{
			CloudProviders: res,
		}, nil
	default:
		return nil, bizerr.New(bizerr.ErrCodeParams, "deploy type is invalid")
	}
}

func (c serverImpl) RegisterRegion(
	ctx context.Context,
	req *tidbmgmtservicev1.RegisterRegionRequest,
) (*tidbmgmtservicev1.Region, error) {
	if req.GetRegion() == nil {
		return nil, bizerr.New(bizerr.ErrCodeParams, "region is invalid")
	}

	switch req.GetRegion().GetDeployType() {
	case tidbmgmtservicev1.DeployType_DEPLOY_TYPE_SERVERLESS:
		normalizeRegionForLegacyServiceRequest(req.GetRegion().GetRegion())
		r, err := c.serverlessCli.RegisterRegion(ctx, &globalserverlessv1.RegionServiceRegisterRequest{
			Region: req.GetRegion().GetRegion(),
		})
		if err != nil {
			return nil, err
		}
		normalizeRegionForResponse(r)

		return &tidbmgmtservicev1.Region{
			DeployType: tidbmgmtservicev1.DeployType_DEPLOY_TYPE_SERVERLESS,
			Region:     r,
		}, nil
	case tidbmgmtservicev1.DeployType_DEPLOY_TYPE_DEDICATED:
		normalizeRegionForLegacyServiceRequest(req.GetRegion().GetRegion())
		r, err := c.dedicatedCli.RegisterRegion(ctx, &globaldedicatedv1.RegisterRegionRequest{
			Region: req.GetRegion().GetRegion(),
		})
		if err != nil {
			return nil, err
		}
		normalizeRegionForResponse(r)
		return &tidbmgmtservicev1.Region{
			DeployType: tidbmgmtservicev1.DeployType_DEPLOY_TYPE_DEDICATED,
			Region:     r,
		}, nil
	case tidbmgmtservicev1.DeployType_DEPLOY_TYPE_NEXTGEN:
		normalizeRegionForNextgenRequest(req.GetRegion().GetRegion())
		r, err := c.nextgenCli.RegisterRegion(ctx, &globalngv1.RegisterRegionRequest{
			Region:                   req.GetRegion().GetRegion(),
			RegionProviderIdentities: req.GetRegion().GetRegionProviderIdentities(),
		})
		if err != nil {
			return nil, err
		}
		normalizeRegionForResponse(r)
		return &tidbmgmtservicev1.Region{
			DeployType: tidbmgmtservicev1.DeployType_DEPLOY_TYPE_NEXTGEN,
			Region:     r,
		}, nil
	default:
		return nil, bizerr.New(bizerr.ErrCodeParams, "deploy type is invalid")
	}
}

func (c serverImpl) ListRegions(
	ctx context.Context,
	req *tidbmgmtservicev1.ListRegionsRequest,
) (*tidbmgmtservicev1.ListRegionsResponse, error) {
	switch req.GetDeployType() {
	case tidbmgmtservicev1.DeployType_DEPLOY_TYPE_SERVERLESS:
		rs, err := c.serverlessCli.ListRegions(ctx, &globalserverlessv1.RegionServiceListRequest{
			EngineType: req.GetEngineType(),
		})
		if err != nil {
			return nil, err
		}
		var res []*tidbmgmtservicev1.Region
		for _, r := range rs.GetRegions() {
			normalizeRegionForResponse(r)
			res = append(res, &tidbmgmtservicev1.Region{
				DeployType: tidbmgmtservicev1.DeployType_DEPLOY_TYPE_SERVERLESS,
				Region:     r,
			})
		}
		return &tidbmgmtservicev1.ListRegionsResponse{
			Regions: res,
		}, nil

	case tidbmgmtservicev1.DeployType_DEPLOY_TYPE_DEDICATED:
		rs, err := c.dedicatedCli.ListRegions(ctx, &globaldedicatedv1.ListRegionsRequest{})
		if err != nil {
			return nil, err
		}
		var res []*tidbmgmtservicev1.Region
		for _, r := range rs.GetRegions() {
			normalizeRegionForResponse(r)
			res = append(res, &tidbmgmtservicev1.Region{
				DeployType: tidbmgmtservicev1.DeployType_DEPLOY_TYPE_DEDICATED,
				Region:     r,
			})
		}
		return &tidbmgmtservicev1.ListRegionsResponse{
			Regions: res,
		}, nil
	case tidbmgmtservicev1.DeployType_DEPLOY_TYPE_NEXTGEN:
		var res []*tidbmgmtservicev1.Region

		rs, err := c.nextgenCli.ListRegions(ctx, &globalngv1.ListRegionsRequest{
			ServicePlan:    req.ServicePlan,
			OrganizationId: req.OrganizationId,
		})
		if err != nil {
			return nil, err
		}
		for _, r := range rs.GetRegions() {
			normalizeRegionForResponse(r)
			res = append(res, &tidbmgmtservicev1.Region{
				DeployType: tidbmgmtservicev1.DeployType_DEPLOY_TYPE_NEXTGEN,
				Region:     r,
			})
		}

		return &tidbmgmtservicev1.ListRegionsResponse{
			Regions: res,
		}, nil
	default:
		return nil, bizerr.New(bizerr.ErrCodeParams, "deploy type is invalid")
	}
}

func (c serverImpl) GetRegion(
	ctx context.Context,
	req *tidbmgmtservicev1.GetRegionRequest,
) (*tidbmgmtservicev1.Region, error) {
	switch req.GetDeployType() {
	case tidbmgmtservicev1.DeployType_DEPLOY_TYPE_SERVERLESS:

		regionId, err := enum.FromString[globalcommonv1.Region_Name](req.GetRegionLogicalName())
		if err != nil {
			return nil, bizerr.WrapErrorWithCode(err, bizerr.ErrCodeParams, "region logical name is invalid")
		}

		servicePlan := globalcommonv1.ServicePlan_Essential
		if req.GetServicePlan() != globalcommonv1.ServicePlan_SERVICE_PLAN_UNSPECIFIED {
			servicePlan = req.GetServicePlan()
		}
		r, err := c.serverlessCli.GetRegion(ctx, &globalserverlessv1.RegionServiceGetRequest{
			RegionId:        globalcommonv1.Region_Name(regionId),
			CloudProviderId: req.GetCloudProviderId(),
			ServicePlan:     servicePlan,
			EngineType:      req.GetEngineType(),
		})
		if err != nil {
			return nil, err
		}
		normalizeRegionForResponse(r)

		return &tidbmgmtservicev1.Region{
			DeployType: tidbmgmtservicev1.DeployType_DEPLOY_TYPE_SERVERLESS,
			Region:     r,
		}, nil
	case tidbmgmtservicev1.DeployType_DEPLOY_TYPE_DEDICATED:
		r, err := c.dedicatedCli.GetRegionByLogicalName(ctx, &globaldedicatedv1.GetRegionByLogicalNameRequest{
			RegionLogicalName: req.GetRegionLogicalName(),
			CloudProviderId:   req.GetCloudProviderId(),
		})
		if err != nil {
			return nil, err
		}
		normalizeRegionForResponse(r)

		return &tidbmgmtservicev1.Region{
			DeployType: tidbmgmtservicev1.DeployType_DEPLOY_TYPE_DEDICATED,
			Region:     r,
		}, nil
	case tidbmgmtservicev1.DeployType_DEPLOY_TYPE_NEXTGEN:
		r, err := c.nextgenCli.GetRegionByLogicalName(ctx, &globalngv1.GetRegionByLogicalNameRequest{
			RegionLogicalName: req.GetRegionLogicalName(),
		})

		if err != nil {
			return nil, err
		}

		identitiesResp, err := c.nextgenCli.ListRegionProviderIdentities(ctx, &globalngv1.ListRegionProviderIdentitiesRequest{
			RegionLogicalName: req.GetRegionLogicalName(),
		})
		if err != nil {
			return nil, err
		}
		normalizeRegionForResponse(r)

		return &tidbmgmtservicev1.Region{
			DeployType:                tidbmgmtservicev1.DeployType_DEPLOY_TYPE_NEXTGEN,
			Region:                    r,
			RegionProviderIdentities:  identitiesResp.GetRegionProviderIdentities(),
			DefaultProviderIdentities: identitiesResp.GetDefaultProviderIdentities(),
		}, nil
	default:
		return nil, bizerr.New(bizerr.ErrCodeParams, "deploy type is invalid")
	}
}

func (c serverImpl) SelectRegionByOrganizationId(
	ctx context.Context,
	req *tidbmgmtservicev1.SelectRegionByOrganizationIdRequest,
) (*tidbmgmtservicev1.Region, error) {
	switch req.GetDeployType() {
	case tidbmgmtservicev1.DeployType_DEPLOY_TYPE_SERVERLESS:
		// serverless region is not supported get by organization id yet
		// serverless region no logical region
		return nil, bizerr.New(bizerr.ErrCodeParams, "serverless region is not supported get by organization id yet")
	case tidbmgmtservicev1.DeployType_DEPLOY_TYPE_DEDICATED:
		r, err := c.dedicatedCli.SelectRegionByOrgId(ctx, &globaldedicatedv1.SelectRegionByOrgIdRequest{
			CloudProviderId: req.GetCloudProviderId(),
			RegionId:        req.GetRegionId(),
			OrganizationId:  req.GetOrganizationId(),
		})
		if err != nil {
			return nil, err
		}
		normalizeRegionForResponse(r)

		return &tidbmgmtservicev1.Region{
			DeployType: tidbmgmtservicev1.DeployType_DEPLOY_TYPE_DEDICATED,
			Region:     r,
		}, nil
	case tidbmgmtservicev1.DeployType_DEPLOY_TYPE_NEXTGEN:
		r, err := c.nextgenCli.SelectRegionByOrgId(ctx, &globalngv1.SelectRegionByOrgIdRequest{
			CloudProviderId: req.GetCloudProviderId(),
			OrganizationId:  req.GetOrganizationId(),
			RegionId:        req.GetRegionId(),
			ServicePlan:     req.GetServicePlan(),
		})
		if err != nil {
			return nil, err
		}
		normalizeRegionForResponse(r)

		return &tidbmgmtservicev1.Region{
			DeployType: tidbmgmtservicev1.DeployType_DEPLOY_TYPE_NEXTGEN,
			Region:     r,
		}, nil
	default:
		return nil, bizerr.New(bizerr.ErrCodeParams, "deploy type is invalid")
	}
}

func (c serverImpl) UpdateRegion(
	ctx context.Context,
	req *tidbmgmtservicev1.UpdateRegionRequest,
) (*tidbmgmtservicev1.Region, error) {
	if req.GetRegion() == nil {
		return nil, bizerr.New(bizerr.ErrCodeParams, "region is invalid")
	}

	switch req.GetRegion().GetDeployType() {
	case tidbmgmtservicev1.DeployType_DEPLOY_TYPE_SERVERLESS:
		normalizeRegionForLegacyServiceRequest(req.GetRegion().GetRegion())
		r, err := c.serverlessCli.UpdateRegion(ctx, &globalserverlessv1.RegionServiceUpdateRequest{
			Region: req.GetRegion().GetRegion(),
		})

		if err != nil {
			return nil, err
		}
		normalizeRegionForResponse(r)

		return &tidbmgmtservicev1.Region{
			DeployType: tidbmgmtservicev1.DeployType_DEPLOY_TYPE_SERVERLESS,
			Region:     r,
		}, nil

	case tidbmgmtservicev1.DeployType_DEPLOY_TYPE_DEDICATED:
		normalizeRegionForLegacyServiceRequest(req.GetRegion().GetRegion())
		r, err := c.dedicatedCli.UpdateRegion(ctx, &globaldedicatedv1.UpdateRegionRequest{
			Region: req.GetRegion().GetRegion(),
		})
		if err != nil {
			return nil, err
		}
		normalizeRegionForResponse(r)
		return &tidbmgmtservicev1.Region{
			DeployType: tidbmgmtservicev1.DeployType_DEPLOY_TYPE_DEDICATED,
			Region:     r,
		}, nil
	case tidbmgmtservicev1.DeployType_DEPLOY_TYPE_NEXTGEN:
		normalizeRegionForNextgenRequest(req.GetRegion().GetRegion())
		r, err := c.nextgenCli.UpdateRegion(ctx, &globalngv1.UpdateRegionRequest{
			Region:                    req.GetRegion().GetRegion(),
			DefaultProviderIdentities: req.GetRegion().GetDefaultProviderIdentities(),
		})
		if err != nil {
			return nil, err
		}
		normalizeRegionForResponse(r)

		identitiesResp, err := c.nextgenCli.ListRegionProviderIdentities(ctx, &globalngv1.ListRegionProviderIdentitiesRequest{
			RegionLogicalName: r.GetLogicalName(),
		})
		if err != nil {
			return nil, err
		}
		return &tidbmgmtservicev1.Region{
			DeployType:                tidbmgmtservicev1.DeployType_DEPLOY_TYPE_NEXTGEN,
			Region:                    r,
			RegionProviderIdentities:  identitiesResp.GetRegionProviderIdentities(),
			DefaultProviderIdentities: identitiesResp.GetDefaultProviderIdentities(),
		}, nil
	default:
		return nil, bizerr.New(bizerr.ErrCodeParams, "deploy type is invalid")
	}
}

func (c serverImpl) CheckRegionStatus(
	ctx context.Context,
	req *tidbmgmtservicev1.CheckRegionStatusRequest,
) (*emptypb.Empty, error) {
	switch req.GetDeployType() {
	case tidbmgmtservicev1.DeployType_DEPLOY_TYPE_SERVERLESS:
		return nil, bizerr.New(bizerr.ErrCodeParams, "serverless region is not supported list yet")
	case tidbmgmtservicev1.DeployType_DEPLOY_TYPE_DEDICATED:
		// only support dedicated region
		_, err := c.dedicatedCli.CheckRegionStatus(ctx, &globaldedicatedv1.CheckRegionStatusRequest{
			RegionLogicalName: req.GetRegionLogicalName(),
			CloudProviderId:   req.GetCloudProviderId(),
		})
		if err != nil {
			return nil, err
		}

		return &emptypb.Empty{}, nil
	case tidbmgmtservicev1.DeployType_DEPLOY_TYPE_NEXTGEN:
		return nil, bizerr.New(bizerr.ErrCodeParams, "nextgen does not support check region status")
	default:
		return nil, bizerr.New(bizerr.ErrCodeParams, "deploy type is invalid")
	}
}

func (c serverImpl) DeleteRegion(
	ctx context.Context,
	req *tidbmgmtservicev1.DeleteRegionRequest,
) (*emptypb.Empty, error) {
	switch req.GetDeployType() {
	case tidbmgmtservicev1.DeployType_DEPLOY_TYPE_SERVERLESS:
		regionId, err := enum.FromString[globalcommonv1.Region_Name](req.GetRegionLogicalName())
		if err != nil {
			return nil, bizerr.WrapErrorWithCode(err, bizerr.ErrCodeParams, "region logical name is invalid")
		}
		if req.GetServicePlan() == globalcommonv1.ServicePlan_SERVICE_PLAN_UNSPECIFIED {
			return nil, bizerr.WrapErrorWithCode(err, bizerr.ErrCodeParams, "invalid service plan")
		}

		_, err = c.serverlessCli.DeleteRegion(ctx, &globalserverlessv1.RegionServiceDeleteRequest{
			RegionId:        globalcommonv1.Region_Name(regionId),
			CloudProviderId: req.GetCloudProviderId(),
			ServicePlan:     req.GetServicePlan(),
			EngineType:      req.GetEngineType(),
		})
		if err != nil {
			return nil, err
		}

		return &emptypb.Empty{}, nil
	case tidbmgmtservicev1.DeployType_DEPLOY_TYPE_DEDICATED:
		_, err := c.dedicatedCli.DeleteRegion(ctx, &globaldedicatedv1.DeleteRegionRequest{
			RegionLogicalName: req.GetRegionLogicalName(),
			CloudProviderId:   req.GetCloudProviderId(),
		})
		if err != nil {
			return nil, err
		}
		return &emptypb.Empty{}, nil
	case tidbmgmtservicev1.DeployType_DEPLOY_TYPE_NEXTGEN:
		_, err := c.nextgenCli.DeleteRegion(ctx, &globalngv1.DeleteRegionRequest{
			RegionLogicalName: req.GetRegionLogicalName(),
			CloudProviderId:   req.GetCloudProviderId(),
		})
		if err != nil {
			return nil, err
		}
		return &emptypb.Empty{}, nil
	default:
		return nil, bizerr.New(bizerr.ErrCodeParams, "deploy type is invalid")
	}
}

func (c serverImpl) UpdateRegionOpsAuditLogSetting(
	ctx context.Context,
	request *tidbmgmtservicev1.UpdateRegionOpsAuditLogSettingRequest,
) (*globalngv1.UpdateRegionOpsAuditLogSettingResponse, error) {
	switch request.GetDeployType() {
	case tidbmgmtservicev1.DeployType_DEPLOY_TYPE_NEXTGEN:
		return c.nextgenCli.UpdateRegionOpsAuditLogSetting(ctx, &globalngv1.UpdateRegionOpsAuditLogSettingRequest{
			Region: &globalngv1.RegionIdentifier{
				RegionIdentifier: &globalngv1.RegionIdentifier_RegionLogicalName{
					RegionLogicalName: request.GetRegionLogicalName(),
				},
			},
			OpsAuditLogSetting: request.GetOpsAuditLogSetting(),
		})
	default:
		return nil, bizerr.New(bizerr.ErrCodeParams, "only nextgen deploy type is supported")
	}
}
