package acmecert

import (
	"crypto"
	"encoding/json"
	"os"
	"path/filepath"

	"github.com/go-acme/lego/v4/certcrypto"
	"github.com/go-acme/lego/v4/registration"
)

// acmeUser implements registration.User. Its private key and (once registered)
// registration resource are persisted to disk under a data directory, mirroring the
// generate-if-missing file pattern already used by mailstore.LoadOrCreateMasterKey and
// tlsutil.GenerateSelfSignedCert — so the ACME account survives restarts and is never
// re-registered unnecessarily.
type acmeUser struct {
	Email        string
	Registration *registration.Resource
	key          crypto.PrivateKey
}

func (u *acmeUser) GetEmail() string                        { return u.Email }
func (u *acmeUser) GetRegistration() *registration.Resource { return u.Registration }
func (u *acmeUser) GetPrivateKey() crypto.PrivateKey        { return u.key }

func accountKeyPath(dataDir string) string { return filepath.Join(dataDir, "account.key") }
func accountRegPath(dataDir string) string { return filepath.Join(dataDir, "account.json") }

// loadOrCreateAccount loads the persisted ACME account key/registration from dataDir,
// generating a fresh key if none exists yet. A nil Registration means no account has
// been registered with the CA yet — the caller is responsible for registering and then
// calling saveRegistration.
func loadOrCreateAccount(dataDir, email string) (*acmeUser, error) {
	if err := os.MkdirAll(dataDir, 0o755); err != nil {
		return nil, err
	}

	key, err := loadOrCreateAccountKey(dataDir)
	if err != nil {
		return nil, err
	}

	user := &acmeUser{Email: email, key: key}
	if regBytes, err := os.ReadFile(accountRegPath(dataDir)); err == nil {
		var reg registration.Resource
		if err := json.Unmarshal(regBytes, &reg); err == nil {
			user.Registration = &reg
		}
	}
	return user, nil
}

func loadOrCreateAccountKey(dataDir string) (crypto.PrivateKey, error) {
	path := accountKeyPath(dataDir)
	if pemBytes, err := os.ReadFile(path); err == nil {
		return certcrypto.ParsePEMPrivateKey(pemBytes)
	}

	key, err := certcrypto.GeneratePrivateKey(certcrypto.EC256)
	if err != nil {
		return nil, err
	}
	if err := os.WriteFile(path, certcrypto.PEMEncode(key), 0o600); err != nil {
		return nil, err
	}
	return key, nil
}

// saveRegistration persists the account's registration resource so future runs don't
// re-register with the CA.
func saveRegistration(dataDir string, reg *registration.Resource) error {
	data, err := json.Marshal(reg)
	if err != nil {
		return err
	}
	return os.WriteFile(accountRegPath(dataDir), data, 0o600)
}
