package cloudstorage

import (
	"fmt"
	"net/url"
	"regexp"
	"strings"

	globalcommonv1 "github.com/tidbcloud/tidb-management-service/api/spec/global/common/v1"
	globalngv1 "github.com/tidbcloud/tidb-management-service/api/spec/global/nextgen/v1"
	"github.com/tidbcloud/tidb-management-service/utilities/bizerr"
)

var AzureStorageAccountRe = regexp.MustCompile(`^([a-z0-9]+)\.blob\.core\.windows\.net$`)

const InvalidAzureUriErrMsg = "Invalid Azure Blob URI. Expected format: (azure|https)://<account>.blob.core.windows.net/<container>/<path>/"

// Validate validates CloudStorage. When cloudProvider is non-nil, also enforces
// provider restrictions: S3 role_arn only for AWS, OSS role_arn only for ALICLOUD.
func Validate(cs *globalngv1.CloudStorage, cloudProvider globalcommonv1.CloudProvider_Name) bizerr.BizError {
	if cs == nil {
		return bizerr.New(bizerr.ErrCodeParams, "cloud_storage is required")
	}
	// check params
	switch cs.Type {
	case globalngv1.CloudStorageType_S3:
		s3 := cs.GetS3()
		if s3 == nil {
			return bizerr.New(bizerr.ErrCodeParams, "cloud_storage.s3 is required when type is S3")
		}
		if s3.GetUri() == "" {
			return bizerr.New(bizerr.ErrCodeParams, "cloud_storage.s3.uri is required")
		}

		switch s3.AuthType {
		case globalngv1.S3CloudStorage_ACCESS_KEY:
			ak := s3.GetAccessKey()
			if ak == nil || ak.GetId() == "" || ak.GetSecret() == "" {
				return bizerr.New(bizerr.ErrCodeParams, "cloud_storage.s3.access_key.id and secret are required when auth_type is ACCESS_KEY")
			}
		case globalngv1.S3CloudStorage_ROLE_ARN:
			roleArn := s3.GetRoleArn()
			if roleArn == "" {
				return bizerr.New(bizerr.ErrCodeParams, "cloud_storage.s3.role_arn.arn are required when auth_type is ROLE_ARN")
			}
		default:
			return bizerr.New(bizerr.ErrCodeParams, "unsupported cloud_storage.s3.auth_type")
		}
	case globalngv1.CloudStorageType_GCS:
		gcs := cs.GetGcs()
		if gcs == nil {
			return bizerr.New(bizerr.ErrCodeParams, "cloud_storage.gcs is required when type is GCS")
		}
		if gcs.GetUri() == "" {
			return bizerr.New(bizerr.ErrCodeParams, "cloud_storage.gcs.uri is required")
		}
		switch gcs.AuthType {
		case globalngv1.GCSCloudStorage_SERVICE_ACCOUNT_KEY:
			if gcs.GetServiceAccountKey() == "" {
				return bizerr.New(bizerr.ErrCodeParams, "cloud_storage.gcs.service_account_key is required when auth_type is SERVICE_ACCOUNT_KEY")
			}
		default:
			return bizerr.New(bizerr.ErrCodeParams, "unsupported cloud_storage.gcs.auth_type")
		}
	case globalngv1.CloudStorageType_AZURE_BLOB:
		az := cs.GetAzureBlob()
		if az == nil {
			return bizerr.New(bizerr.ErrCodeParams, "cloud_storage.azure_blob is required when type is AZURE_BLOB")
		}
		if az.GetUri() == "" {
			return bizerr.New(bizerr.ErrCodeParams, "cloud_storage.azure_blob.uri is required")
		}
		switch az.AuthType {
		case globalngv1.AzureBlobCloudStorage_SAS_TOKEN:
			if az.GetSasToken() == "" {
				return bizerr.New(bizerr.ErrCodeParams, "cloud_storage.azure_blob.sas_token is required when auth_type is SAS_TOKEN")
			}
		default:
			return bizerr.New(bizerr.ErrCodeParams, "unsupported cloud_storage.azure_blob.auth_type")
		}
	case globalngv1.CloudStorageType_OSS:
		oss := cs.GetOss()
		if oss == nil {
			return bizerr.New(bizerr.ErrCodeParams, "cloud_storage.oss is required when type is OSS")
		}
		if oss.GetUri() == "" {
			return bizerr.New(bizerr.ErrCodeParams, "cloud_storage.oss.uri is required")
		}
		switch oss.AuthType {
		case globalngv1.OSSCloudStorage_ACCESS_KEY:
			ak := oss.GetAccessKey()
			if ak == nil || ak.GetId() == "" || ak.GetSecret() == "" {
				return bizerr.New(bizerr.ErrCodeParams, "cloud_storage.oss.access_key.id and secret are required when auth_type is ACCESS_KEY")
			}
		case globalngv1.OSSCloudStorage_ROLE_ARN:
			if oss.GetRoleArn() == "" {
				return bizerr.New(bizerr.ErrCodeParams, "cloud_storage.oss.role_arn is required when auth_type is ROLE_ARN")
			}
		default:
			return bizerr.New(bizerr.ErrCodeParams, "unsupported cloud_storage.oss.auth_type")
		}
	default:
		return bizerr.New(bizerr.ErrCodeParams, "unsupported cloud_storage.type")
	}

	// validate URI format
	switch cs.Type {
	case globalngv1.CloudStorageType_S3:
		uri := cs.GetS3().GetUri()
		if !strings.HasPrefix(uri, "s3://") {
			return bizerr.New(bizerr.ErrCodeParams, "s3 uri must start with 's3://'")
		}
		if !strings.HasSuffix(uri, "/") {
			return bizerr.New(bizerr.ErrCodeParams, "uri must end with '/'")
		}
		if cloudProvider != globalcommonv1.CloudProvider_AWS && cs.GetS3().GetRoleArn() != "" {
			return bizerr.New(bizerr.ErrCodeParams, "s3 role arn is only supported for aws cluster")
		}
	case globalngv1.CloudStorageType_OSS:
		uri := cs.GetOss().GetUri()
		if !strings.HasPrefix(uri, "oss://") {
			return bizerr.New(bizerr.ErrCodeParams, "oss uri must start with 'oss://'")
		}
		if !strings.HasSuffix(uri, "/") {
			return bizerr.New(bizerr.ErrCodeParams, "uri must end with '/'")
		}
		if cloudProvider != globalcommonv1.CloudProvider_ALICLOUD && cs.GetOss().GetRoleArn() != "" {
			return bizerr.New(bizerr.ErrCodeParams, "oss role arn is only supported for alicloud cluster")
		}
	case globalngv1.CloudStorageType_GCS:
		uri := cs.GetGcs().GetUri()
		if !strings.HasPrefix(uri, "gcs://") && !strings.HasPrefix(uri, "gs://") {
			return bizerr.New(bizerr.ErrCodeParams, "gcs uri must start with 'gcs://' or 'gs://'")
		}
		if !strings.HasSuffix(uri, "/") {
			return bizerr.New(bizerr.ErrCodeParams, "uri must end with '/'")
		}
	case globalngv1.CloudStorageType_AZURE_BLOB:
		if _, _, err := ParseAzureURI(cs.GetAzureBlob().GetUri()); err != nil {
			return err
		}
	}
	return nil
}

func ParseAzureURI(uri string) (accountName string, containerURI *url.URL, bizErr bizerr.BizError) {
	if !strings.HasPrefix(uri, "azure://") && !strings.HasPrefix(uri, "https://") {
		return "", nil, bizerr.New(bizerr.ErrCodeParams, InvalidAzureUriErrMsg)
	}
	if !strings.HasSuffix(uri, "/") {
		return "", nil, bizerr.New(bizerr.ErrCodeParams, "uri must end with '/'")
	}
	parsedURL, err := url.Parse(uri)
	if err != nil {
		return "", nil, bizerr.New(bizerr.ErrCodeParams, InvalidAzureUriErrMsg)
	}
	sourceURL, err := url.Parse(fmt.Sprintf("azure:/%s", parsedURL.EscapedPath()))
	if err != nil {
		return "", nil, bizerr.New(bizerr.ErrCodeParams, InvalidAzureUriErrMsg)
	}
	subMatch := AzureStorageAccountRe.FindStringSubmatch(parsedURL.Host)
	if len(subMatch) != 2 {
		return "", nil, bizerr.New(bizerr.ErrCodeParams, InvalidAzureUriErrMsg)
	}
	return subMatch[1], sourceURL, nil
}
