package impl

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

	"github.com/tidbcloud/dataflow-service/app/models/domain"
	"github.com/tidbcloud/dataflow-service/app/repo/entity"
)

// replayAuditLogURLStrategy normalizes one source audit-log address. The caller
// wraps errors with replay-level or task-level context.
type replayAuditLogURLStrategy interface {
	normalizeSourceAuditLogURL(*entity.AuditLogAddr) error
}

type aliyunReplayAuditLogURLStrategy struct {
	region string
}

type passthroughReplayAuditLogURLStrategy struct{}

// normalizeSourceAuditLogURL is a no-op for providers whose audit-log URL format
// is already consumed by infra-provider.
func (passthroughReplayAuditLogURLStrategy) normalizeSourceAuditLogURL(_ *entity.AuditLogAddr) error {
	return nil
}

// newReplayAuditLogURLStrategy maps the source provider to the required
// audit-log URL normalization strategy.
func newReplayAuditLogURLStrategy(
	region string,
	provider string,
) replayAuditLogURLStrategy {
	switch provider {
	case string(domain.InfraProviderTypeACK):
		return aliyunReplayAuditLogURLStrategy{
			region: region,
		}
	default:
		return passthroughReplayAuditLogURLStrategy{}
	}
}

// normalizeSourceAuditLogURL rewrites keyed Aliyun OSS input into the
// S3-compatible URL format expected by infra-provider. URL-only input is left
// unchanged by normalizeAliyunAuditLogURL.
func (s aliyunReplayAuditLogURLStrategy) normalizeSourceAuditLogURL(sourceAuditLogAddr *entity.AuditLogAddr) error {
	if sourceAuditLogAddr == nil || sourceAuditLogAddr.Aliyun == nil {
		return nil
	}
	return normalizeAliyunAuditLogURL(sourceAuditLogAddr.Aliyun, s.region)
}

// normalizeAliyunAuditLogURL converts keyed Aliyun OSS input into the
// S3-compatible URL format consumed by infra-provider. URL-only input is passed
// through unchanged.
// Examples:
//   - oss://bucket/path + keys ->
//     s3://bucket/path?access-key=...&endpoint=...&secret-access-key=...
//   - s3://bucket/path + keys -> error because keyed input must be oss://.
func normalizeAliyunAuditLogURL(aliyun *entity.AliyunAuditLog, region string) error {
	if aliyun == nil {
		return fmt.Errorf("aliyun audit credential is nil")
	}
	hasAccessKeyID := strings.TrimSpace(aliyun.AccessKeyID) != ""
	hasSecretAccessKey := strings.TrimSpace(aliyun.SecretAccessKey) != ""
	if hasAccessKeyID != hasSecretAccessKey {
		return fmt.Errorf("access_key_id and secret_access_key must be provided together")
	}
	if !hasAccessKeyID {
		return nil
	}
	bucketURL := strings.TrimSpace(aliyun.BucketURL)
	if !strings.HasPrefix(bucketURL, "oss://") {
		return fmt.Errorf("bucket_url must start with oss:// when access keys are provided")
	}
	trimmedRegion := strings.TrimSpace(region)
	if trimmedRegion == "" {
		return fmt.Errorf("region is required to build aliyun oss endpoint")
	}
	s3URL, err := buildAliyunReplayS3URL(bucketURL, trimmedRegion, aliyun.AccessKeyID, aliyun.SecretAccessKey)
	if err != nil {
		return err
	}
	aliyun.BucketURL = s3URL
	return nil
}

// buildAliyunReplayS3URL rewrites an OSS URL into infra-provider's S3-compatible
// replay URL and appends the Aliyun OSS endpoint and access keys.
// Examples:
//   - oss://bucket/prefix/tidb-* ->
//     s3://bucket/prefix/tidb-?access-key=...&endpoint=...&secret-access-key=...
//   - oss://bucket/prefix/tidb-%2A ->
//     s3://bucket/prefix/tidb-?access-key=...&endpoint=...&secret-access-key=...
func buildAliyunReplayS3URL(bucketURL, region, accessKeyID, secretAccessKey string) (string, error) {
	trimmed := strings.TrimPrefix(bucketURL, "oss://")
	if trimmed == bucketURL {
		return "", fmt.Errorf("bucket_url must start with oss://")
	}
	trimmed = strings.TrimPrefix(trimmed, "/")
	if trimmed == "" {
		return "", fmt.Errorf("bucket_url does not contain bucket name")
	}
	parts := strings.SplitN(trimmed, "/", 2)
	bucket := parts[0]
	path := ""
	if len(parts) == 2 {
		path = parts[1]
	}
	path = strings.ReplaceAll(path, "%2A", "*")
	path = strings.ReplaceAll(path, "%2a", "*")
	path = strings.TrimSuffix(path, "*")

	result := fmt.Sprintf("s3://%s", bucket)
	if path != "" {
		result = fmt.Sprintf("%s/%s", result, path)
	}

	query := url.Values{}
	query.Set("endpoint", fmt.Sprintf("http://oss-%s.aliyuncs.com", region))
	query.Set("access-key", accessKeyID)
	query.Set("secret-access-key", secretAccessKey)
	return result + "?" + query.Encode(), nil
}
