//go:build goolm || libolm

package infra

import (
	"context"
	"fmt"
	"os"
	"path/filepath"

	"maunium.net/go/mautrix"
	"maunium.net/go/mautrix/crypto/cryptohelper"
)

// MatrixCryptoInitConfig parametriza la inicializacion del crypto store Olm/Megolm.
type MatrixCryptoInitConfig struct {
	// Client es el *mautrix.Client ya inicializado via MatrixClientInit.
	// Debe tener AccessToken, UserID y DeviceID poblados.
	Client *mautrix.Client

	// StorePath es la ruta absoluta al archivo SQLite del crypto store.
	// Debe ser separado del state store. El SDK gestiona el schema internamente.
	// Si el directorio padre no existe, se crea con permisos 0700.
	// Ejemplo: "/home/lucas/.config/matrix_client_pc/egutierrez/crypto.db"
	StorePath string

	// PickleKey son exactamente 32 bytes usados por cryptohelper para cifrar las
	// sesiones Olm en disco at-rest. DEBE persistir entre arranques (guardar en keyring).
	// Si se pierde, el store SQLite se vuelve inutilizable y hay que crear nuevo dispositivo.
	PickleKey []byte
}

// MatrixCryptoInitResult contiene el helper listo para usar.
type MatrixCryptoInitResult struct {
	// Helper es el *cryptohelper.CryptoHelper inicializado.
	// Ya esta asignado a client.Crypto — el Sync loop cifra/descifra automaticamente.
	Helper *cryptohelper.CryptoHelper

	// StorePath es la ruta al archivo SQLite del crypto store (igual que cfg.StorePath).
	StorePath string
}

// MatrixCryptoInit inicializa el crypto store Olm/Megolm para un cliente mautrix
// usando cryptohelper — el wrapper oficial que abstrae SQLite + Olm identity keys +
// one-time key upload + decrypt automatico via el Syncer.
//
// Pasos:
//  1. Valida inputs (Client no nil con AccessToken/UserID/DeviceID, StorePath
//     absoluto, PickleKey exactamente 32 bytes).
//  2. Crea el directorio padre de StorePath con permisos 0700 si no existe.
//  3. Construye el helper via cryptohelper.NewCryptoHelper(client, pickleKey, storePath).
//  4. Llama helper.Init(ctx) — crea tablas SQLite, carga cuenta Olm, sube one-time keys.
//  5. Asigna client.Crypto = helper para que SendMessageEvent cifre automaticamente.
//  6. Devuelve MatrixCryptoInitResult con el helper listo.
func MatrixCryptoInit(ctx context.Context, cfg MatrixCryptoInitConfig) (*MatrixCryptoInitResult, error) {
	// 1. Validar Client
	if cfg.Client == nil {
		return nil, fmt.Errorf("matrix_crypto_init: Client no puede ser nil")
	}
	if cfg.Client.AccessToken == "" {
		return nil, fmt.Errorf("matrix_crypto_init: Client.AccessToken no puede estar vacio")
	}
	if cfg.Client.UserID == "" {
		return nil, fmt.Errorf("matrix_crypto_init: Client.UserID no puede estar vacio")
	}
	if cfg.Client.DeviceID == "" {
		return nil, fmt.Errorf("matrix_crypto_init: Client.DeviceID no puede estar vacio — descubrirlo via MatrixClientInit o Whoami antes de llamar MatrixCryptoInit")
	}

	// Validar StorePath
	if cfg.StorePath == "" {
		return nil, fmt.Errorf("matrix_crypto_init: StorePath no puede estar vacio")
	}
	if !filepath.IsAbs(cfg.StorePath) {
		return nil, fmt.Errorf("matrix_crypto_init: StorePath debe ser una ruta absoluta (got %q)", cfg.StorePath)
	}

	// Validar PickleKey: exactamente 32 bytes
	if len(cfg.PickleKey) != 32 {
		return nil, fmt.Errorf("matrix_crypto_init: PickleKey debe tener exactamente 32 bytes (got %d)", len(cfg.PickleKey))
	}

	// 2. Crear directorio padre con permisos 0700 (datos sensibles)
	storeDir := filepath.Dir(cfg.StorePath)
	if err := os.MkdirAll(storeDir, 0700); err != nil {
		return nil, fmt.Errorf("matrix_crypto_init: no se pudo crear directorio del store %q: %w", storeDir, err)
	}

	// 3. Construir CryptoHelper — acepta string como path SQLite directamente (v0.28 API)
	helper, err := cryptohelper.NewCryptoHelper(cfg.Client, cfg.PickleKey, cfg.StorePath)
	if err != nil {
		return nil, fmt.Errorf("matrix_crypto_init: NewCryptoHelper failed: %w", err)
	}

	// 4. Init: crea tablas SQLite, carga cuenta Olm, sube one-time keys al servidor
	if err := helper.Init(ctx); err != nil {
		return nil, fmt.Errorf("matrix_crypto_init: helper.Init failed (comprueba conectividad con Synapse y validez del token): %w", err)
	}

	// 5. Asignar client.Crypto para que SendMessageEvent cifre automaticamente
	cfg.Client.Crypto = helper

	return &MatrixCryptoInitResult{
		Helper:    helper,
		StorePath: cfg.StorePath,
	}, nil
}
