package main

import (
	"context"
	"fmt"
	"os"
	"os/signal"
	"strings"
	"syscall"
	"time"

	"gitlab.com/mbugroup/lti-api.git/internal/apikeys"
	"gitlab.com/mbugroup/lti-api.git/internal/cache"
	"gitlab.com/mbugroup/lti-api.git/internal/config"
	"gitlab.com/mbugroup/lti-api.git/internal/database"
	"gitlab.com/mbugroup/lti-api.git/internal/middleware"
	"gitlab.com/mbugroup/lti-api.git/internal/modules/sso/session"
	sso "gitlab.com/mbugroup/lti-api.git/internal/modules/sso/verifier"
	"gitlab.com/mbugroup/lti-api.git/internal/readapi"
	"gitlab.com/mbugroup/lti-api.git/internal/route"
	"gitlab.com/mbugroup/lti-api.git/internal/utils"

	"github.com/gofiber/fiber/v2"
	"github.com/gofiber/fiber/v2/middleware/compress"
	"github.com/gofiber/fiber/v2/middleware/cors"
	"github.com/gofiber/fiber/v2/middleware/helmet"
	"github.com/redis/go-redis/v9"

	"gorm.io/gorm"
)

func main() {
	ctx, cancel := context.WithCancel(context.Background())
	defer cancel()

	app := setupFiberApp()
	db := setupDatabase()
	defer closeDatabase(db)
	rdb := setupRedis()
	defer rdb.Close()
	setupSSO(ctx, rdb)
	setupRoutes(app, db, rdb)

	address := fmt.Sprintf("%s:%d", config.AppHost, config.AppPort)

	// Start server and handle graceful shutdown
	serverErrors := make(chan error, 1)
	go startServer(app, address, serverErrors)
	handleGracefulShutdown(ctx, app, serverErrors)
}

func setupRedis() *redis.Client {
	opt, err := redis.ParseURL(config.RedisURL)
	if err != nil {
		utils.Log.Fatalf("Redis URL parse error: %v", err)
	}
	rdb := redis.NewClient(opt)
	if err := rdb.Ping(context.Background()).Err(); err != nil {
		utils.Log.Fatalf("Redis ping failed: %v", err)
	}
	cache.SetRedis(rdb)
	utils.Log.Infof("Redis connected: %s", config.RedisURL)
	return rdb
}

func setupSSO(ctx context.Context, rdb *redis.Client) {
	const (
		maxAttempts = 12
		retryDelay  = 5 * time.Second
	)

	var lastErr error
	for attempt := 1; attempt <= maxAttempts; attempt++ {
		if err := sso.Init(ctx, config.SSOJWKSURL, config.SSOIssuer, config.SSOAllowedAudiences); err != nil {
			lastErr = err
			utils.Log.WithError(err).Warnf("SSO initialization attempt %d/%d failed", attempt, maxAttempts)
			select {
			case <-ctx.Done():
				utils.Log.Fatalf("SSO initialization aborted: %v", ctx.Err())
			case <-time.After(retryDelay):
			}
			continue
		}
		lastErr = nil
		if attempt > 1 {
			utils.Log.Infof("SSO initialization succeeded after %d attempts", attempt)
		}
		break
	}

	if lastErr != nil {
		utils.Log.Fatalf("SSO initialization failed: %v", lastErr)
	}

	if rdb != nil {
		session.SetRevocationStore(session.NewRevocationStore(rdb, config.SSOTokenBlacklistPrefix))
	} else {
		session.SetRevocationStore(nil)
	}
}

func setupFiberApp() *fiber.App {
	app := fiber.New(config.FiberConfig())

	// Middleware setup
	app.Use(middleware.LoggerConfig())
	app.Use(helmet.New())
	app.Use(compress.New())
	app.Use(middleware.RecoverConfig())

	origins := "*"
	if len(config.CORSAllowOrigins) > 0 {
		origins = strings.Join(config.CORSAllowOrigins, ",")
	}
	if config.CORSAllowCredentials && (origins == "" || origins == "*") {
		origins = "http://localhost:3000"
	}
	app.Use(cors.New(cors.Config{
		AllowOrigins:     origins,
		AllowMethods:     strings.Join(config.CORSAllowMethods, ","),
		AllowHeaders:     strings.Join(config.CORSAllowHeaders, ","),
		ExposeHeaders:    strings.Join(config.CORSExposeHeaders, ","),
		AllowCredentials: config.CORSAllowCredentials,
		MaxAge:           config.CORSMaxAge,
	}))

	return app
}

func setupDatabase() *gorm.DB {
	db := database.Connect(config.DBHost, config.DBName)
	return db
}

func setupRoutes(app *fiber.App, db *gorm.DB, rdb *redis.Client) {
	middleware.SetAPIKeyAuthenticator(apikeys.NewService(db))

	// route.Routes(app, db)
	// app.Use(utils.NotFoundHandler)
	app.Get("/healthz", func(c *fiber.Ctx) error {
		return c.Status(fiber.StatusOK).JSON(fiber.Map{
			"status":  "ok",
			"service": "api",
			"version": config.Version,
		})
	})

	app.Get("/readyz", func(c *fiber.Ctx) error {
		sqlDB, err := db.DB()
		if err != nil {
			return c.Status(fiber.StatusInternalServerError).JSON(fiber.Map{
				"status": "error", "db": "unavailable", "redis": "unknown",
			})
		}
		ctx, cancel := context.WithTimeout(c.Context(), 2*time.Second)
		defer cancel()

		dbOK := sqlDB.PingContext(ctx) == nil
		redisOK := rdb.Ping(ctx).Err() == nil

		status := fiber.StatusOK
		statusText := "ok"
		if !dbOK || !redisOK {
			status = fiber.StatusServiceUnavailable
			statusText = "degraded"
		}
		body := fiber.Map{
			"status": statusText,
			"db":     map[bool]string{true: "up", false: "down"}[dbOK],
			"redis":  map[bool]string{true: "up", false: "down"}[redisOK],
		}
		return c.Status(status).JSON(body)
	})

	readAPIRoutes := app.Group("/api")
	readapi.RegisterRoutes(readAPIRoutes)
	route.Routes(app, db)
	app.Use(utils.NotFoundHandler)
}

func startServer(app *fiber.App, address string, errs chan<- error) {
	if err := app.Listen(address); err != nil {
		errs <- fmt.Errorf("error starting server: %w", err)
	}
}

func closeDatabase(db *gorm.DB) {
	sqlDB, errDB := db.DB()
	if errDB != nil {
		utils.Log.Errorf("Error getting database instance: %v", errDB)
		return
	}

	if err := sqlDB.Close(); err != nil {
		utils.Log.Errorf("Error closing database connection: %v", err)
	} else {
		utils.Log.Info("Database connection closed successfully")
	}
}

func handleGracefulShutdown(ctx context.Context, app *fiber.App, serverErrors <-chan error) {
	quit := make(chan os.Signal, 1)
	signal.Notify(quit, os.Interrupt, syscall.SIGTERM)

	select {
	case err := <-serverErrors:
		utils.Log.Fatalf("Server error: %v", err)
	case <-quit:
		utils.Log.Info("Shutting down server...")
		if err := app.Shutdown(); err != nil {
			utils.Log.Fatalf("Error during server shutdown: %v", err)
		}
	case <-ctx.Done():
		utils.Log.Info("Server exiting due to context cancellation")
	}

	utils.Log.Info("Server exited")
}
