package server

import (
	"context"
	"fmt"
	"path"
	"strings"

	apiv1 "github.com/tidbcloud/cluster-service-ng/api/cluster-service-ng/v1beta"
	dplanemgrv1 "github.com/tidbcloud/cluster-service-ng/api/dataplane-manager/v1"
	"github.com/tidbcloud/cluster-service-ng/domain/entity"
	"github.com/tidbcloud/cluster-service-ng/domain/keyspacerole"
	"google.golang.org/grpc/codes"
	"google.golang.org/grpc/status"
)

type importRoleConfigParams struct {
	keyspaceRole    *keyspacerole.Params
	internalBucket  string
	cloudStorageURI string
}

func (s *serverImpl) GetImportRoleConfig(ctx context.Context, tidbClusterId string) (*entity.ImportRoleConfig, error) {
	if tidbClusterId == "" {
		return nil, status.Errorf(codes.InvalidArgument, "tidbCluster id is required")
	}

	tidbCluster, err := s.tidbClusterRepo.Load(ctx, tidbClusterId, false)
	if err != nil {
		if status.Code(err) != codes.NotFound {
			return nil, status.Errorf(codes.Internal, "failed to load tidbCluster: %s", err)
		}
		return nil, status.Errorf(codes.NotFound, "tidbCluster %s not found", tidbClusterId)
	}

	sharedPool, err := s.sharedPoolRepo.Load(ctx, tidbCluster.GetSharedPoolId(), false)
	if err != nil {
		if status.Code(err) != codes.NotFound {
			return nil, status.Errorf(codes.Internal, "failed to load sharedPool: %s", err)
		}
		return nil, status.Errorf(codes.NotFound, "sharedPool %s not found", tidbCluster.GetSharedPoolId())
	}

	if err := validateImportRoleSupported(sharedPool); err != nil {
		return nil, err
	}

	params, err := buildImportRoleConfigParams(sharedPool, tidbCluster)
	if err != nil {
		return nil, err
	}

	if s.dataplaneClient == nil {
		return nil, status.Errorf(codes.Internal, "dataplane manager client is not configured")
	}
	if err := s.checkImportRoleDataplaneReady(ctx, params.keyspaceRole.DataplaneID); err != nil {
		return nil, err
	}

	keyspaceRole, err := s.dataplaneClient.GetKeyspaceRole(ctx, &dplanemgrv1.GetKeyspaceRoleRequest{
		DataplaneId:   params.keyspaceRole.DataplaneID,
		KeyspaceName:  params.keyspaceRole.KeyspaceName,
		BucketId:      params.keyspaceRole.BucketID,
		AllowedPrefix: params.keyspaceRole.AllowedPrefix,
		ExternalId:    params.keyspaceRole.ExternalID,
	})
	if err != nil {
		if status.Code(err) == codes.NotFound {
			return nil, status.Errorf(codes.FailedPrecondition, "keyspace role for cluster %s is not ready: role not found", tidbClusterId)
		}
		return nil, status.Errorf(codes.Internal, "failed to get keyspace role: %s", err)
	}
	if err := validateKeyspaceRoleReady(tidbClusterId, keyspaceRole); err != nil {
		return nil, err
	}
	if err := validateKeyspaceRoleContract(tidbClusterId, keyspaceRole, params); err != nil {
		return nil, err
	}

	return &entity.ImportRoleConfig{
		RoleArn:         keyspaceRole.GetRoleArn(),
		ExternalId:      keyspaceRole.GetExternalId(),
		Bucket:          params.internalBucket,
		AllowedPrefix:   keyspaceRole.GetAllowedPrefix(),
		CloudStorageUri: params.cloudStorageURI,
	}, nil
}

func validateImportRoleSupported(sharedPool *entity.SharedPool) error {
	if keyspacerole.ShouldSkip(sharedPool) {
		return status.Errorf(codes.FailedPrecondition, "import role config is not supported for provider %s", sharedPool.GetProvider().GetType())
	}
	return nil
}

func buildImportRoleConfigParams(sharedPool *entity.SharedPool, tidbCluster *entity.TiDBCluster) (*importRoleConfigParams, error) {
	internalStorage := sharedPool.GetInternalStorage()
	if internalStorage == nil {
		return nil, status.Errorf(codes.FailedPrecondition, "sharedPool %s internal storage is not configured", sharedPool.GetId())
	}
	if internalStorage.GetBucket() == "" {
		return nil, status.Errorf(codes.FailedPrecondition, "sharedPool %s internal bucket is not ready", sharedPool.GetId())
	}
	if internalStorage.GetRegion() == "" {
		return nil, status.Errorf(codes.FailedPrecondition, "sharedPool %s internal storage region is not configured", sharedPool.GetId())
	}

	roleParams, err := keyspacerole.BuildParams(sharedPool, tidbCluster, "")
	if err != nil {
		return nil, status.Errorf(codes.FailedPrecondition, "failed to build keyspace role params: %s", err)
	}
	cloudStorageURI, err := buildImportRoleCloudStorageURI(sharedPool, internalStorage.GetBucket(), roleParams.AllowedPrefix)
	if err != nil {
		return nil, status.Errorf(codes.FailedPrecondition, "failed to build cloud storage uri: %s", err)
	}

	return &importRoleConfigParams{
		keyspaceRole:    roleParams,
		internalBucket:  internalStorage.GetBucket(),
		cloudStorageURI: cloudStorageURI,
	}, nil
}

func buildImportRoleCloudStorageURI(sharedPool *entity.SharedPool, bucket, allowedPrefix string) (string, error) {
	importRootPath := path.Join(bucket, keyspacerole.NormalizeStoragePrefix(allowedPrefix))
	switch sharedPool.GetProvider().GetType() {
	case apiv1.ProviderType_AWS:
		return fmt.Sprintf("s3://%s", importRootPath), nil
	case apiv1.ProviderType_ALICLOUD:
		return fmt.Sprintf("oss://%s", importRootPath), nil
	default:
		return "", fmt.Errorf("not supported provider type %s", sharedPool.GetProvider().GetType())
	}
}

func (s *serverImpl) checkImportRoleDataplaneReady(ctx context.Context, dataplaneId string) error {
	if dataplaneId == "" {
		return status.Errorf(codes.FailedPrecondition, "sharedPool dataplane name is empty")
	}

	dataplane, err := s.dataplaneClient.GetDataPlane(ctx, &dplanemgrv1.GetDataPlaneRequest{
		DataPlaneId: dataplaneId,
	})
	if err != nil {
		if status.Code(err) == codes.NotFound {
			return status.Errorf(codes.FailedPrecondition, "dataplane %s not found", dataplaneId)
		}
		return status.Errorf(codes.Internal, "failed to get dataplane: %s", err)
	}
	if dataplane.GetStatus() != dplanemgrv1.DataPlane_Active {
		return status.Errorf(codes.FailedPrecondition, "dataplane %s is not active: %s", dataplaneId, dataplane.GetStatus())
	}
	return nil
}

func validateKeyspaceRoleReady(tidbClusterId string, keyspaceRole *dplanemgrv1.KeyspaceRole) error {
	if keyspaceRole == nil {
		return status.Errorf(codes.FailedPrecondition, "keyspace role for cluster %s is not ready: empty response", tidbClusterId)
	}

	switch keyspaceRole.GetStatus() {
	case dplanemgrv1.KeyspaceRole_Ready:
		if keyspaceRole.GetRoleArn() == "" {
			return status.Errorf(codes.FailedPrecondition, "keyspace role for cluster %s is ready but role ARN is empty", tidbClusterId)
		}
		return nil
	case dplanemgrv1.KeyspaceRole_Failed:
		message := keyspaceRole.GetMessage()
		if message == "" {
			message = "keyspace role failed"
		}
		return status.Errorf(codes.FailedPrecondition, "keyspace role for cluster %s failed: %s", tidbClusterId, message)
	default:
		message := keyspaceRole.GetMessage()
		if message == "" {
			message = keyspaceRole.GetStatus().String()
		}
		return status.Errorf(codes.FailedPrecondition, "keyspace role for cluster %s is not ready: %s", tidbClusterId, message)
	}
}

func validateKeyspaceRoleContract(tidbClusterId string, keyspaceRole *dplanemgrv1.KeyspaceRole, params *importRoleConfigParams) error {
	var mismatches []string
	if keyspaceRole.GetExternalId() != params.keyspaceRole.ExternalID {
		mismatches = append(mismatches, "external_id")
	}
	if keyspaceRole.GetBucketId() != params.keyspaceRole.BucketID {
		mismatches = append(mismatches, "bucket_id")
	}
	if keyspaceRole.GetAllowedPrefix() != params.keyspaceRole.AllowedPrefix {
		mismatches = append(mismatches, "allowed_prefix")
	}
	if len(mismatches) > 0 {
		return status.Errorf(codes.FailedPrecondition, "keyspace role for cluster %s does not match import role config: %s", tidbClusterId, strings.Join(mismatches, ", "))
	}
	return nil
}
