package main

import (
	"context"
	"fmt"
	"log"
	"net/http"
	"os"

	"github.com/gin-gonic/gin"
	"github.com/jackc/pgx/v5/pgxpool"
	"github.com/joho/godotenv"

	"github.com/ren/econ/backend/internal/config"
	"github.com/ren/econ/backend/internal/handlers"
	"github.com/ren/econ/backend/internal/middleware"
	"github.com/ren/econ/backend/internal/repository"
	"github.com/ren/econ/backend/internal/services"
)

func main() {
	// Load .env file if exists
	_ = godotenv.Load()

	cfg := config.Load()

	// Connect to PostgreSQL
	connStr := fmt.Sprintf(
		"postgres://%s:%s@%s:%d/%s?sslmode=disable",
		cfg.DBUser, cfg.DBPassword, cfg.DBHost, cfg.DBPort, cfg.DBName,
	)

	ctx := context.Background()
	dbPool, err := pgxpool.New(ctx, connStr)
	if err != nil {
		log.Fatalf("Error connecting to database: %v", err)
	}
	defer dbPool.Close()

	// Test connection
	if err := dbPool.Ping(ctx); err != nil {
		log.Fatalf("Error pinging database: %v", err)
	}
	log.Println("Connected to PostgreSQL")

	// Run migrations
	runMigrations(ctx, dbPool)

	// Initialize repositories
	userRepo := repository.NewUserRepository(dbPool)
	progresoRepo := repository.NewProgresoRepository(dbPool)
	contenidoRepo := repository.NewContenidoRepository(dbPool)

	// Initialize services
	authService := services.NewAuthService(cfg, userRepo)

	// Initialize handlers
	authHandler := handlers.NewAuthHandler(authService)
	usersHandler := handlers.NewUsersHandler(userRepo, progresoRepo, authService)
	progresoHandler := handlers.NewProgresoHandler(progresoRepo)
	contenidoHandler := handlers.NewContenidoHandler(contenidoRepo)

	// Setup Gin router
	router := gin.Default()

	// CORS middleware
	router.Use(func(c *gin.Context) {
		c.Writer.Header().Set("Access-Control-Allow-Origin", "*")
		c.Writer.Header().Set("Access-Control-Allow-Methods", "GET, POST, PUT, DELETE, OPTIONS")
		c.Writer.Header().Set("Access-Control-Allow-Headers", "Content-Type, Authorization")

		if c.Request.Method == "OPTIONS" {
			c.AbortWithStatus(http.StatusNoContent)
			return
		}

		c.Next()
	})

	// Health check
	router.GET("/health", func(c *gin.Context) {
		c.JSON(http.StatusOK, gin.H{"status": "ok"})
	})

	// API routes
	api := router.Group("/api")
	{
		// Auth routes (public)
		auth := api.Group("/auth")
		{
			auth.POST("/login", authHandler.Login)
			auth.POST("/refresh", authHandler.RefreshToken)
		}

		// Protected routes
		protected := api.Group("")
		protected.Use(middleware.AuthMiddleware(authService))
		{
			// Auth logout
			auth.POST("/auth/logout", authHandler.Logout)

			// Progreso routes
			progreso := protected.Group("/progreso")
			{
				progreso.GET("", progresoHandler.GetProgreso)
				progreso.POST("", progresoHandler.SaveProgreso)
				progreso.GET("/resumen", progresoHandler.GetResumen)
			}

			// Contenido routes
			contenido := protected.Group("/contenido")
			{
				contenido.GET("/modulos", contenidoHandler.GetModulos)
				contenido.GET("/modulos/:numero", contenidoHandler.GetModulo)
			}

			// Admin routes (requires admin role)
			admin := protected.Group("/admin")
			admin.Use(middleware.AdminMiddleware())
			{
				admin.GET("/usuarios", usersHandler.ListUsers)
				admin.POST("/usuarios", usersHandler.CreateUser)
				admin.GET("/usuarios/:id", usersHandler.GetUser)
				admin.PUT("/usuarios/:id", usersHandler.UpdateUser)
				admin.DELETE("/usuarios/:id", usersHandler.DeleteUser)
				admin.GET("/usuarios/:id/progreso", usersHandler.GetUserProgreso)
			}
		}
	}

	// Start server
	port := getEnv("PORT", "8080")
	log.Printf("Server starting on port %s", port)
	if err := router.Run(":" + port); err != nil {
		log.Fatalf("Error starting server: %v", err)
	}
}

func runMigrations(ctx context.Context, dbPool *pgxpool.Pool) {
	// Inline migrations
	migrations := []string{
		`CREATE TABLE IF NOT EXISTS usuarios (
			id UUID PRIMARY KEY DEFAULT gen_random_uuid(),
			email VARCHAR(255) UNIQUE NOT NULL,
			username VARCHAR(100) UNIQUE NOT NULL,
			password_hash VARCHAR(255) NOT NULL,
			nombre VARCHAR(100) NOT NULL,
			rol VARCHAR(20) DEFAULT 'estudiante' CHECK (rol IN ('admin', 'estudiante')),
			creado_en TIMESTAMP DEFAULT NOW(),
			ultimo_login TIMESTAMP,
			activo BOOLEAN DEFAULT TRUE
		)`,
		`CREATE TABLE IF NOT EXISTS progreso_usuario (
			id UUID PRIMARY KEY DEFAULT gen_random_uuid(),
			usuario_id UUID REFERENCES usuarios(id) ON DELETE CASCADE,
			modulo_numero INTEGER NOT NULL CHECK (modulo_numero BETWEEN 1 AND 4),
			ejercicio_id VARCHAR(50) NOT NULL,
			completado BOOLEAN DEFAULT FALSE,
			puntuacion INTEGER DEFAULT 0,
			intentos INTEGER DEFAULT 0,
			ultima_vez TIMESTAMP DEFAULT NOW(),
			respuesta_json JSONB,
			UNIQUE(usuario_id, modulo_numero, ejercicio_id)
		)`,
		`CREATE TABLE IF NOT EXISTS ejercicios (
			id VARCHAR(50) PRIMARY KEY,
			modulo_numero INTEGER NOT NULL,
			titulo VARCHAR(200) NOT NULL,
			tipo VARCHAR(50) NOT NULL,
			contenido JSONB NOT NULL,
			orden INTEGER DEFAULT 0
		)`,
		`CREATE TABLE IF NOT EXISTS refresh_tokens (
			id UUID PRIMARY KEY DEFAULT gen_random_uuid(),
			usuario_id UUID REFERENCES usuarios(id) ON DELETE CASCADE,
			token VARCHAR(255) UNIQUE NOT NULL,
			expira TIMESTAMP NOT NULL,
			creado_en TIMESTAMP DEFAULT NOW()
		)`,
	}

	for _, m := range migrations {
		if _, err := dbPool.Exec(ctx, m); err != nil {
			log.Printf("Migration warning: %v", err)
		}
	}

	// Seed ejercicios if empty
	var count int
	dbPool.QueryRow(ctx, "SELECT COUNT(*) FROM ejercicios").Scan(&count)
	if count == 0 {
		seedEjercicios(ctx, dbPool)
	}

	// Create admin user if not exists
	var adminCount int
	dbPool.QueryRow(ctx, "SELECT COUNT(*) FROM usuarios WHERE rol = 'admin'").Scan(&adminCount)
	if adminCount == 0 {
		// Default admin: renato97 / wlillidan1 (bcrypt hash)
		_, err := dbPool.Exec(ctx, `
			INSERT INTO usuarios (email, username, password_hash, nombre, rol)
			VALUES ('renato97@econ.local', 'renato97', '$2a$10$N9qo8uLOickgx2ZMRZoMyeIjZRGdjGj/n3.rsW4WzOFbMB3dHI.Bu', 'Renato', 'admin')
		`)
		if err != nil {
			log.Printf("Warning: could not create admin user: %v", err)
		} else {
			log.Println("Admin user created: renato97 / wlillidan1")
		}
	}

	log.Println("Migrations completed successfully")
}

func seedEjercicios(ctx context.Context, pool *pgxpool.Pool) {
	ejercicios := []struct {
		ID           string
		ModuloNumero int
		Titulo       string
		Tipo         string
		Contenido    string
		Orden        int
	}{
		// Módulo 1
		{"m1e1", 1, "Simulador de Disyuntivas", "interactivo", `{"tipo":"slider","descripcion":"Elige cuanto producir de cada bien"}`, 1},
		{"m1e2", 1, "Clasificación de Bienes", "quiz", `{"preguntas":[{"id":"q1","pregunta":"La leche es un bien...","opciones":["normal","inferior","de lujo"],"respuesta":"normal"}]}`, 2},
		{"m1e3", 1, "Flujo Circular", "dragdrop", `{"agentes":["familias","empresas"]}`, 3},
		// Módulo 2
		{"m2e1", 2, "Constructor de Curvas", "interactivo", `{"curvas":["oferta","demanda"]}`, 1},
		{"m2e2", 2, "Precios Máximos y Mínimos", "simulador", `{"tipo_precio":"seleccionar"}`, 2},
		{"m2e3", 2, "Shocks de Mercado", "quiz", `{"escenarios":[{"id":"s1","evento":"Sequía","pregunta":"¿Qué curva se ve afectada?","respuesta":"oferta"}]}`, 3},
		// Módulo 3
		{"m3e1", 3, "Calculadora de Elasticidad", "calculadora", `{"formula":"elasticidad"}`, 1},
		{"m3e2", 3, "Clasificador de Bienes", "quiz", `{"bienes":[{"nombre":"Pan","elasticidad":"inelastico"}]}`, 2},
		{"m3e3", 3, "Ejercicios tipo Examen", "quiz", `{"preguntas":[]}`, 3},
		// Módulo 4
		{"m4e1", 4, "Simulador de Producción", "simulador", `{"max_produccion":100}`, 1},
		{"m4e2", 4, "Calculadora de Costos", "calculadora", `{"tipos":["fijo","variable"]}`, 2},
		{"m4e3", 4, "Excedentes", "interactivo", `{"excedentes":["productor","consumidor"]}`, 3},
	}

	for _, e := range ejercicios {
		_, err := pool.Exec(ctx, `
			INSERT INTO ejercicios (id, modulo_numero, titulo, tipo, contenido, orden)
			VALUES ($1, $2, $3, $4, $5, $6)
		`, e.ID, e.ModuloNumero, e.Titulo, e.Tipo, e.Contenido, e.Orden)
		if err != nil {
			log.Printf("Warning: could not seed ejercicio %s: %v", e.ID, err)
		}
	}
	log.Println("Ejercicios seeded")
}

func getEnv(key, defaultValue string) string {
	if value, exists := os.LookupEnv(key); exists {
		return value
	}
	return defaultValue
}
