package httpgateway

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

// SessionCipher using AES-256-GCM to encrypt/decrypt session tokens.
type SessionCipher struct {
	secret string
	gcm    cipher.AEAD
}

// NewSessionCipher builds a cipher using the provided secret. The secret is hashed to 32 bytes.
func NewSessionCipher(secret string) (*SessionCipher, error) {
	key := sha256.Sum256([]byte(secret))
	block, err := aes.NewCipher(key[:])
	if err != nil {
		return nil, err
	}
	gcm, err := cipher.NewGCM(block)
	if err != nil {
		return nil, err
	}
	return &SessionCipher{secret: secret, gcm: gcm}, nil
}

// EncryptCredential encrypts the credential tuple and returns a base64 token.
func (c *SessionCipher) EncryptCredential(cred *Credential) (string, error) {
	plaintext := strings.Join([]string{cred.Username, cred.Password, cred.Database}, "\x00")
	nonce := make([]byte, c.gcm.NonceSize())
	if _, err := io.ReadFull(rand.Reader, nonce); err != nil {
		return "", err
	}
	ciphertext := c.gcm.Seal(nil, nonce, []byte(plaintext), nil)
	payload := append(nonce, ciphertext...)
	return base64.RawURLEncoding.EncodeToString(payload), nil
}

// DecryptCredential reverses EncryptCredential.
func (c *SessionCipher) DecryptCredential(token string) (*Credential, error) {
	raw, err := base64.RawURLEncoding.DecodeString(token)
	if err != nil {
		return nil, err
	}
	nonceSize := c.gcm.NonceSize()
	if len(raw) < nonceSize {
		return nil, errors.New("invalid session payload")
	}
	nonce := raw[:nonceSize]
	ciphertext := raw[nonceSize:]
	plaintext, err := c.gcm.Open(nil, nonce, ciphertext, nil)
	if err != nil {
		return nil, err
	}
	parts := strings.Split(string(plaintext), "\x00")
	if len(parts) != 3 {
		return nil, errors.New("invalid session payload")
	}
	return &Credential{Username: parts[0], Password: parts[1], Database: parts[2]}, nil
}
