package adapterimpl

import (
	"context"
	"crypto/rand"
	"encoding/base64"

	"github.com/tidbcloud/pkgv2/secret/encrypt"
	secretprovider "github.com/tidbcloud/pkgv2/secret/provider"
	"go.opentelemetry.io/otel"

	"github.com/tidbcloud/tidb-management-service/serverless-global-service/domain/adapter"
	"github.com/tidbcloud/tidb-management-service/serverless-global-service/pkg/config"
	"github.com/tidbcloud/tidb-management-service/utilities/bizerr"
)

const (
	// SaltLength is the length of salt
	SaltLength = 8
)

var (
	tracer = otel.Tracer("encryptor")
)

type encryptorImp struct {
	enc encrypt.Encryptor
}

func NewEncryptor() adapter.EncryptorService {
	cmkArn := config.Global().InfraConfig.CMKArn
	enc, err := secretprovider.NewSecretEncryptor(cmkArn)
	if err != nil {
		panic("initialize encryptor failed")
	}
	return encryptorImp{
		enc: enc,
	}
}

func (e encryptorImp) EncryptBase64(ctx context.Context, text string) (string, error) {
	_, span := tracer.Start(ctx, "EncryptorFacade.EncryptBase64")
	defer span.End()

	secretTxt, err := encrypt.EncryptBase64(e.enc, encrypt.Secret(text))
	if err != nil {
		return "", bizerr.WrapError(err, "encrypt failed")
	}
	return secretTxt, nil
}

func (e encryptorImp) DecryptBase64(ctx context.Context, encryptedEncoded string) (string, error) {
	_, span := tracer.Start(ctx, "EncryptorFacade.DecryptBase64")
	defer span.End()

	plainText, err := encrypt.DecryptBase64(e.enc, encryptedEncoded)
	if err != nil {
		return "", bizerr.WrapError(err, "decrypt failed")
	}
	return string(plainText), nil
}

func (e encryptorImp) GenerateSalt(ctx context.Context) ([]byte, error) {
	_, span := tracer.Start(ctx, "EncryptorFacade.GenerateSalt")
	defer span.End()

	salt, err := GenerateSalt()
	if err != nil {
		return nil, bizerr.WrapError(err, "generate salt failed")
	}
	return salt, nil
}

func GenerateSalt() ([]byte, error) {
	salt := make([]byte, SaltLength)
	if _, e := rand.Read(salt); e != nil {
		return nil, e
	}
	return salt, nil
}

type fakeEncryptor struct{}

func NewFakeEncryptor() adapter.EncryptorService {
	return fakeEncryptor{}
}

func (f fakeEncryptor) EncryptBase64(ctx context.Context, text string) (string, error) {
	result := base64.StdEncoding.EncodeToString([]byte(text))
	return result, nil
}

func (f fakeEncryptor) DecryptBase64(ctx context.Context, encryptedEncoded string) (string, error) {
	data, err := base64.StdEncoding.DecodeString(encryptedEncoded)
	return string(data), err
}

func (f fakeEncryptor) GenerateSalt(ctx context.Context) ([]byte, error) {
	salt, err := GenerateSalt()
	if err != nil {
		return nil, bizerr.WrapError(err, "generate salt failed")
	}
	return salt, nil
}
