package matrix

import (
	"context"
	"errors"
	"fmt"
	"net/url"
	"os"
	"path/filepath"
	"strings"
	"time"

	"maunium.net/go/mautrix"
	"maunium.net/go/mautrix/id"
)

// MatrixClientInitConfig parametriza la inicializacion del cliente Matrix.
type MatrixClientInitConfig struct {
	// HomeserverURL es la URL base del servidor Matrix (Synapse/Dendrite/etc.).
	// Ejemplo: "https://matrix-af2f3d.organic-machine.com"
	HomeserverURL string

	// UserID es el MXID del usuario. Formato "@local:servidor".
	// Ejemplo: "@egutierrez:matrix-af2f3d.organic-machine.com"
	UserID string

	// AccessToken es el Bearer token obtenido del flow OIDC (mas_oidc_loopback).
	// No puede estar vacio.
	AccessToken string

	// DeviceID del cliente Matrix. Si vacio, se descubre via /whoami al inicializar.
	// Recomendado guardarlo en keyring tras el primer uso para evitar la llamada extra.
	DeviceID string

	// StoreDir es el directorio donde se persiste el estado de sync (next_batch, filter_id).
	// Se crea con permisos 0700 si no existe. Puede ser relativo (se convierte a absoluto).
	// Ejemplo: "~/.matrix_client_pc/egutierrez/" (no expandido automaticamente — usar os.UserHomeDir).
	StoreDir string

	// EnableCrypto activa el crypto store SQLite para Olm/Megolm (E2EE).
	// En v0.1.0 devuelve error — la implementacion completa esta en issue 0150.
	EnableCrypto bool
}

// MatrixClientInitResult contiene el cliente listo y los paths de persistencia.
type MatrixClientInitResult struct {
	// Client es el *mautrix.Client listo para Sync/SendMessage.
	// UserID, AccessToken y DeviceID ya estan configurados.
	Client *mautrix.Client

	// StorePath es la ruta al directorio de persistencia de sync state.
	StorePath string

	// CryptoPath es la ruta calculada para el crypto store SQLite.
	// Vacio si EnableCrypto=false. En v0.1.0 siempre vacio (no implementado).
	CryptoPath string
}

// MatrixClientInit construye un *mautrix.Client listo para hacer Sync,
// sin manejar el login (que ya hizo el flow OIDC via mas_oidc_loopback).
//
// Pasos:
//  1. Valida inputs (HomeserverURL parseable, UserID formato "@x:server", AccessToken no vacio).
//  2. Crea StoreDir con permisos 0700.
//  3. Llama mautrix.NewClient con las credenciales.
//  4. Si DeviceID esta vacio, hace Whoami para descubrirlo (sum latency ~100ms).
//  5. Si EnableCrypto=true, devuelve error (issue 0150 lo implementa).
//  6. Devuelve MatrixClientInitResult con el cliente configurado.
func MatrixClientInit(cfg MatrixClientInitConfig) (*MatrixClientInitResult, error) {
	// 1. Validar HomeserverURL
	if cfg.HomeserverURL == "" {
		return nil, fmt.Errorf("matrix_client_init: HomeserverURL no puede estar vacio")
	}
	if _, err := url.ParseRequestURI(cfg.HomeserverURL); err != nil {
		return nil, fmt.Errorf("matrix_client_init: HomeserverURL invalido %q: %w", cfg.HomeserverURL, err)
	}
	if !strings.HasPrefix(cfg.HomeserverURL, "http://") && !strings.HasPrefix(cfg.HomeserverURL, "https://") {
		return nil, fmt.Errorf("matrix_client_init: HomeserverURL debe empezar con http:// o https:// (got %q)", cfg.HomeserverURL)
	}

	// Validar UserID: debe ser "@local:servidor"
	if cfg.UserID == "" {
		return nil, fmt.Errorf("matrix_client_init: UserID no puede estar vacio")
	}
	if !strings.HasPrefix(cfg.UserID, "@") || !strings.Contains(cfg.UserID, ":") {
		return nil, fmt.Errorf("matrix_client_init: UserID invalido %q — formato esperado @local:servidor", cfg.UserID)
	}

	// Validar AccessToken
	if cfg.AccessToken == "" {
		return nil, fmt.Errorf("matrix_client_init: AccessToken no puede estar vacio")
	}

	// Validar StoreDir
	if cfg.StoreDir == "" {
		return nil, fmt.Errorf("matrix_client_init: StoreDir no puede estar vacio")
	}

	// En v0.1.0 crypto no esta implementado
	if cfg.EnableCrypto {
		return nil, fmt.Errorf("matrix_client_init: crypto not implemented in v0.1.0, see issue 0150")
	}

	// Convertir StoreDir a absoluto si es relativo
	storeDir := cfg.StoreDir
	if !filepath.IsAbs(storeDir) {
		abs, err := filepath.Abs(storeDir)
		if err != nil {
			return nil, fmt.Errorf("matrix_client_init: no se pudo resolver StoreDir %q: %w", storeDir, err)
		}
		storeDir = abs
	}

	// 2. Crear StoreDir con permisos 0700 (datos sensibles)
	if err := os.MkdirAll(storeDir, 0700); err != nil {
		return nil, fmt.Errorf("matrix_client_init: no se pudo crear StoreDir %q: %w", storeDir, err)
	}

	// 3. Construir cliente mautrix
	client, err := mautrix.NewClient(cfg.HomeserverURL, id.UserID(cfg.UserID), cfg.AccessToken)
	if err != nil {
		return nil, fmt.Errorf("matrix_client_init: mautrix.NewClient failed: %w", err)
	}

	// 4. DeviceID: usar el proporcionado o descubrir via Whoami
	if cfg.DeviceID != "" {
		client.DeviceID = id.DeviceID(cfg.DeviceID)
	} else {
		ctx, cancel := context.WithTimeout(context.Background(), 15*time.Second)
		defer cancel()

		whoami, err := client.Whoami(ctx)
		if err != nil {
			// Distinguir token invalido (M_UNKNOWN_TOKEN) de error de red
			if errors.Is(err, mautrix.MUnknownToken) {
				return nil, fmt.Errorf("matrix_client_init: access token invalido o expirado (M_UNKNOWN_TOKEN) — refrescar via OIDC: %w", err)
			}
			return nil, fmt.Errorf("matrix_client_init: Whoami failed (servidor caido o token invalido): %w", err)
		}
		client.DeviceID = whoami.DeviceID
	}

	// Calcular CryptoPath (aunque no se use en v0.1.0)
	cryptoPath := ""
	// CryptoPath calculado pero no inicializado en v0.1.0
	_ = filepath.Join(storeDir, "crypto.db") // reservado para matrix_crypto_init_go_infra (issue 0150)

	return &MatrixClientInitResult{
		Client:     client,
		StorePath:  storeDir,
		CryptoPath: cryptoPath,
	}, nil
}
