package utils

import (
	"crypto/aes"
	"crypto/cipher"
	"crypto/rand"
	"encoding/base64"
	"errors"
	"fmt"
	"io"
)

var (
	ErrNoActiveMasterKey   = errors.New("no active master key found")
	ErrInvalidOrgID        = errors.New("invalid organization ID")
	ErrCredentialNotFound  = errors.New("credential not found")
	ErrInvalidMasterKeyLen = errors.New("invalid master key length")
)

func randomBytes(n int) ([]byte, error) {
	b := make([]byte, n)
	if _, err := io.ReadFull(rand.Reader, b); err != nil {
		return nil, fmt.Errorf("rand: %w", err)
	}
	return b, nil
}

func encryptAESGCM(plaintext, key []byte) (cipherNoTag, iv, tag []byte, _ error) {
	block, err := aes.NewCipher(key)
	if err != nil {
		return nil, nil, nil, fmt.Errorf("cipher: %w", err)
	}

	gcm, err := cipher.NewGCM(block)
	if err != nil {
		return nil, nil, nil, fmt.Errorf("gcm: %w", err)
	}
	if gcm.NonceSize() != 12 {
		return nil, nil, nil, fmt.Errorf("unexpected nonce size: %d", gcm.NonceSize())
	}

	iv, err = randomBytes(gcm.NonceSize())
	if err != nil {
		return nil, nil, nil, err
	}

	// Go’s GCM returns ciphertext||tag, with 16-byte tag.
	cipherWithTag := gcm.Seal(nil, iv, plaintext, nil)
	if len(cipherWithTag) < 16 {
		return nil, nil, nil, errors.New("ciphertext too short")
	}
	tagLen := 16
	cipherNoTag = cipherWithTag[:len(cipherWithTag)-tagLen]
	tag = cipherWithTag[len(cipherWithTag)-tagLen:]
	return cipherNoTag, iv, tag, nil
}

func decryptAESGCM(cipherNoTag, key, iv, tag []byte) ([]byte, error) {
	block, err := aes.NewCipher(key)
	if err != nil {
		return nil, fmt.Errorf("cipher: %w", err)
	}
	gcm, err := cipher.NewGCM(block)
	if err != nil {
		return nil, fmt.Errorf("gcm: %w", err)
	}
	if gcm.NonceSize() != len(iv) {
		return nil, fmt.Errorf("bad nonce size: %d", len(iv))
	}
	// Reattach tag
	cipherWithTag := append(append([]byte{}, cipherNoTag...), tag...)
	plain, err := gcm.Open(nil, iv, cipherWithTag, nil)
	if err != nil {
		return nil, fmt.Errorf("gcm open: %w", err)
	}
	return plain, nil
}

func EncodeB64(b []byte) string {
	return base64.StdEncoding.EncodeToString(b)
}

func DecodeB64(s string) ([]byte, error) {
	return base64.StdEncoding.DecodeString(s)
}
