// Package mailstore handles local mailbox storage: per-mailbox encryption at rest
// (a random AES-256 key per mailbox, sealed with one server-held master key so a raw
// DB/backup theft alone can't decrypt mail — see MasterKey below), on-disk ciphertext
// layout, and quota accounting. Message retrieval/IMAP serving is a later milestone;
// this package is the storage engine underneath it.
package mailstore

import (
	"crypto/aes"
	"crypto/cipher"
	"crypto/rand"
	"errors"
	"fmt"
	"os"
	"path/filepath"
	"strings"

	"mailgoserver/internal/db"
)

// masterKeySize is 32 bytes (AES-256).
const masterKeySize = 32

// LoadOrCreateMasterKey reads the server's master encryption key from path, generating
// a fresh random one on first run if the file doesn't exist yet — mirrors
// tlsutil.GenerateSelfSignedCert's generate-if-missing pattern. This file must be
// backed up separately from the database: losing it makes every stored mailbox's mail
// permanently unrecoverable, even for admins.
func LoadOrCreateMasterKey(path string) ([]byte, error) {
	if b, err := os.ReadFile(path); err == nil {
		if len(b) != masterKeySize {
			return nil, fmt.Errorf("master key at %s is %d bytes, want %d", path, len(b), masterKeySize)
		}
		return b, nil
	} else if !os.IsNotExist(err) {
		return nil, err
	}

	if err := os.MkdirAll(filepath.Dir(path), 0o755); err != nil {
		return nil, err
	}
	key := make([]byte, masterKeySize)
	if _, err := rand.Read(key); err != nil {
		return nil, err
	}
	if err := os.WriteFile(path, key, 0o600); err != nil {
		return nil, err
	}
	return key, nil
}

// Store is the local mailbox storage engine: one per running server, shared across
// connections (analogous to smtpserver.Backend).
type Store struct {
	DB        *db.DB
	MasterKey []byte
	BasePath  string
}

func New(database *db.DB, masterKey []byte, basePath string) *Store {
	return &Store{DB: database, MasterKey: masterKey, BasePath: basePath}
}

// GenerateDEK returns a fresh random AES-256 data encryption key for one mailbox.
func GenerateDEK() []byte {
	dek := make([]byte, masterKeySize)
	rand.Read(dek)
	return dek
}

// WrapDEK seals dek with the server master key, returning the ciphertext and the
// nonce used for that one seal operation (both stored on the mailbox row).
func (s *Store) WrapDEK(dek []byte) (wrapped, nonce []byte, err error) {
	return sealAESGCM(s.MasterKey, dek)
}

// UnwrapDEK reverses WrapDEK.
func (s *Store) UnwrapDEK(wrapped, nonce []byte) ([]byte, error) {
	return openAESGCM(s.MasterKey, wrapped, nonce)
}

func sealAESGCM(key, plaintext []byte) (ciphertext, nonce []byte, err error) {
	block, err := aes.NewCipher(key)
	if err != nil {
		return nil, nil, err
	}
	gcm, err := cipher.NewGCM(block)
	if err != nil {
		return nil, nil, err
	}
	nonce = make([]byte, gcm.NonceSize())
	if _, err := rand.Read(nonce); err != nil {
		return nil, nil, err
	}
	return gcm.Seal(nil, nonce, plaintext, nil), nonce, nil
}

func openAESGCM(key, ciphertext, nonce []byte) ([]byte, error) {
	block, err := aes.NewCipher(key)
	if err != nil {
		return nil, err
	}
	gcm, err := cipher.NewGCM(block)
	if err != nil {
		return nil, err
	}
	if len(nonce) != gcm.NonceSize() {
		return nil, errors.New("mailstore: invalid nonce size")
	}
	return gcm.Open(nil, nonce, ciphertext, nil)
}

// sanitizePathSegment neuters filesystem-unsafe characters in a mailbox email address
// so it can be used directly as a directory name, mirroring
// smtpserver.sanitizePathSegment's spirit (that one only handles domains; this one
// also strips "@" and ":" since a full address is used here, not just a domain).
func sanitizePathSegment(s string) string {
	for _, c := range []string{"/", "\\", ":", "@"} {
		s = strings.ReplaceAll(s, c, "_")
	}
	return s
}
