package db

import (
	"context"
	"database/sql"
	"fmt"
	"nearle/models"
	"nearle/utils"

	"os"
	"time"

	"github.com/redis/go-redis/v9"
	"gorm.io/driver/postgres"
	"gorm.io/gorm"
	"gorm.io/gorm/logger"
)

var (
	DB *gorm.DB
)

// --------------------
// DATABASE CONNECTION
// --------------------

func Connect() {
	dsn := fmt.Sprintf(
		"host=%s user=%s password=%s dbname=%s port=%s sslmode=disable TimeZone=Asia/Kolkata",
		mustEnv("DB_HOST"),
		mustEnv("DB_USER"),
		mustEnv("DB_PASSWORD"),
		mustEnv("DB_NAME"),
		getEnv("DB_PORT", "5432"),
	)

	var err error
	maxRetries := 10
	backoff := 2 * time.Second

	for i := 1; i <= maxRetries; i++ {
		utils.Logger.Infow("Connecting to database", "attempt", i)

		DB, err = gorm.Open(postgres.Open(dsn), &gorm.Config{
			Logger: logger.Default.LogMode(logger.Error),
		})

		if err == nil {
			sqlDB, dbErr := DB.DB()
			if dbErr == nil {

				// 🔥 Ping with timeout (important)
				ctx, cancel := context.WithTimeout(context.Background(), 5*time.Second)
				defer cancel()

				if pingErr := sqlDB.PingContext(ctx); pingErr == nil {
					setupDB(DB)

					utils.Logger.Infow("✅ Database connected successfully")

					// Start background health check
					startDBHealthCheck(sqlDB)

					return
				} else {
					err = pingErr
				}
			} else {
				err = dbErr
			}
		}

		utils.Logger.Errorw("❌ DB connection failed", "attempt", i, "error", err)
		time.Sleep(backoff)
		backoff *= 2
	}

	utils.Logger.Fatalw("☢️ CRITICAL: DB connection failed after retries")
}

// --------------------
// CONNECTION POOLING
// --------------------

func setupDB(database *gorm.DB) {
	sqlDB, err := database.DB()
	if err != nil {
		utils.Logger.Errorw("Failed to get sql.DB", "error", err)
		return
	}

	// Auto-migrate the rider_substitutions table
	database.AutoMigrate(&models.RiderSubstitution{})

	// 🔥 K8s SAFE CONFIG
	sqlDB.SetMaxOpenConns(30)                 // total open connections
	sqlDB.SetMaxIdleConns(5)                  // idle connections
	sqlDB.SetConnMaxLifetime(5 * time.Minute) // avoid stale connections
	sqlDB.SetConnMaxIdleTime(2 * time.Minute)

	stats := sqlDB.Stats()
	utils.Logger.Infow("DB Pool configured",
		"MaxOpen", stats.MaxOpenConnections,
	)
}

// --------------------
// DB HEALTH CHECK
// --------------------

func startDBHealthCheck(sqlDB *sql.DB) {
	go func() {
		for {
			ctx, cancel := context.WithTimeout(context.Background(), 3*time.Second)

			err := sqlDB.PingContext(ctx)
			cancel()

			if err != nil {
				utils.Logger.Errorw("❌ DB connection lost", "error", err)
			}

			time.Sleep(30 * time.Second)
		}
	}()
}

// --------------------
// CLOSE DB
// --------------------

func CloseDB() {
	if DB == nil {
		return
	}

	sqlDB, err := DB.DB()
	if err != nil {
		utils.Logger.Errorw("Error retrieving sql.DB", "error", err)
		return
	}

	utils.Logger.Infow("Closing DB connection")
	sqlDB.Close()
}

// --------------------
// ENV HELPERS
// --------------------

func mustEnv(key string) string {
	val := os.Getenv(key)
	if val == "" {
		utils.Logger.Warnw("Missing env variable", "key", key)
	}
	return val
}

func getEnv(key, fallback string) string {
	if val := os.Getenv(key); val != "" {
		return val
	}
	return fallback
}

// --------------------
// REDIS CONNECTION
// --------------------

var Rdb *redis.Client
var Ctx = context.Background()

func InitRedis() {
	redisHost := getEnv("REDIS_HOST", "66.116.226.255") // ✅ FIXED IP
	redisPort := getEnv("REDIS_PORT", "6379")

	addr := fmt.Sprintf("%s:%s", redisHost, redisPort)

	Rdb = redis.NewClient(&redis.Options{
		Addr:     addr,
		Username: "default",
		Password: "Package@324969#",
		DB:       0,

		// ✅ TIMEOUTS (VERY IMPORTANT)
		DialTimeout:  10 * time.Second,
		ReadTimeout:  10 * time.Second,
		WriteTimeout: 10 * time.Second,

		// ✅ POOL
		PoolSize:     50,
		MinIdleConns: 10,

		// ✅ RETRIES
		MaxRetries:      3,
		MinRetryBackoff: 500 * time.Millisecond,
		MaxRetryBackoff: 2 * time.Second,
	})

	maxRetries := 5
	backoff := 1 * time.Second

	for i := 1; i <= maxRetries; i++ {
		ctx, cancel := context.WithTimeout(context.Background(), 5*time.Second)

		_, err := Rdb.Ping(ctx).Result()
		cancel()

		if err == nil {
			utils.Logger.Infow("✅ Redis connected successfully", "addr", addr)
			return
		}

		utils.Logger.Errorw("❌ Redis connection failed",
			"attempt", i,
			"addr", addr,
			"error", err,
		)

		time.Sleep(backoff)
		backoff *= 2
	}

	utils.Logger.Fatalw("☢️ CRITICAL: Redis connection failed after retries", "addr", addr)
}
