package infra

import (
	"database/sql"
	"testing"
	"time"

	_ "github.com/mattn/go-sqlite3"
)

func openSessionTestDB(t *testing.T) *sql.DB {
	t.Helper()
	db, err := sql.Open("sqlite3", ":memory:")
	if err != nil {
		t.Fatalf("sql.Open: %v", err)
	}
	t.Cleanup(func() { db.Close() })
	return db
}

func TestSessionCreate_PersistsSession(t *testing.T) {
	db := openSessionTestDB(t)
	s, err := SessionCreate(db, "user-1", time.Hour, map[string]any{"role": "admin"})
	if err != nil {
		t.Fatalf("SessionCreate: %v", err)
	}
	if s.Token == "" || len(s.Token) != 64 {
		t.Errorf("token de largo incorrecto: len=%d", len(s.Token))
	}
	if s.UserID != "user-1" {
		t.Errorf("UserID = %q", s.UserID)
	}
	if s.ExpiresAt <= s.CreatedAt {
		t.Errorf("ExpiresAt %d <= CreatedAt %d", s.ExpiresAt, s.CreatedAt)
	}

	// Verificar persistencia
	var count int
	if err := db.QueryRow("SELECT COUNT(*) FROM sessions").Scan(&count); err != nil {
		t.Fatalf("query: %v", err)
	}
	if count != 1 {
		t.Errorf("esperaba 1 fila, got %d", count)
	}
}

func TestSessionCreate_GeneratesDistinctTokens(t *testing.T) {
	db := openSessionTestDB(t)
	s1, _ := SessionCreate(db, "u", time.Hour, nil)
	s2, _ := SessionCreate(db, "u", time.Hour, nil)
	if s1.Token == s2.Token {
		t.Fatal("dos sesiones no pueden tener el mismo token")
	}
}

func TestSessionCreate_RejectsEmptyUserID(t *testing.T) {
	db := openSessionTestDB(t)
	if _, err := SessionCreate(db, "", time.Hour, nil); err == nil {
		t.Fatal("esperaba error con user_id vacio")
	}
}

func TestSessionValidate_ValidSession(t *testing.T) {
	db := openSessionTestDB(t)
	s, _ := SessionCreate(db, "user-7", time.Hour, map[string]any{"k": "v"})

	got, err := SessionValidate(db, s.Token)
	if err != nil {
		t.Fatalf("SessionValidate: %v", err)
	}
	if got.UserID != "user-7" {
		t.Errorf("UserID = %q", got.UserID)
	}
	if got.Metadata["k"] != "v" {
		t.Errorf("metadata[k] = %v", got.Metadata["k"])
	}
}

func TestSessionValidate_MissingToken(t *testing.T) {
	db := openSessionTestDB(t)
	_ = sessionEnsureTable(db)
	if _, err := SessionValidate(db, "nope"); err == nil {
		t.Fatal("esperaba error con token inexistente")
	}
}

func TestSessionValidate_ExpiredSession(t *testing.T) {
	db := openSessionTestDB(t)
	_ = sessionEnsureTable(db)
	// Insertar manualmente una sesion ya expirada
	past := time.Now().Unix() - 60
	_, err := db.Exec(
		"INSERT INTO sessions (token, user_id, expires_at, created_at, metadata) VALUES (?, ?, ?, ?, ?)",
		"expired-tok", "u", past, past-100, "{}",
	)
	if err != nil {
		t.Fatalf("insert: %v", err)
	}
	if _, err := SessionValidate(db, "expired-tok"); err == nil {
		t.Fatal("esperaba error con sesion expirada")
	}
}

func TestSessionCleanup_RemovesOnlyExpired(t *testing.T) {
	db := openSessionTestDB(t)
	active, _ := SessionCreate(db, "active-user", time.Hour, nil)
	_ = sessionEnsureTable(db)
	past := time.Now().Unix() - 60
	_, _ = db.Exec(
		"INSERT INTO sessions (token, user_id, expires_at, created_at, metadata) VALUES (?, ?, ?, ?, ?)",
		"expired", "u", past, past, "{}",
	)

	n, err := SessionCleanup(db)
	if err != nil {
		t.Fatalf("SessionCleanup: %v", err)
	}
	if n != 1 {
		t.Errorf("esperaba 1 eliminada, got %d", n)
	}

	// La activa sigue ahi
	if _, err := SessionValidate(db, active.Token); err != nil {
		t.Errorf("sesion activa no deberia haberse borrado: %v", err)
	}
}
