package database

import (
	"database/sql"
	"fmt"
	"log"
	"os"
	"path/filepath"
	"strings"
	"time"

	_ "github.com/lib/pq"
	_ "modernc.org/sqlite"

	"switchboard-core/config"
)

// DB is the application-wide database connection pool.
var DB *sql.DB

// rawDSN holds the Postgres connection string for LISTEN/NOTIFY consumers.
// Empty when running SQLite.
var rawDSN string

// DSN returns the raw Postgres connection string, or "" for SQLite.
// Used by the event broadcast layer to open a dedicated listener connection.
func DSN() string { return rawDSN }

// Connect opens a connection pool to the configured database.
// Driver is selected by DB_DRIVER env var: "postgres" (default) or "sqlite".
// For SQLite the DATABASE_URL is treated as a file path; ":memory:" is supported.
func Connect(cfg *config.Config) error {
	driver := strings.ToLower(cfg.DBDriver)
	if driver == "" {
		driver = detectDriver(cfg.DatabaseURL)
	}

	switch driver {
	case "sqlite", "sqlite3":
		return connectSQLite(cfg.DatabaseURL)
	case "postgres", "postgresql", "":
		return connectPostgres(cfg.DatabaseURL)
	default:
		return fmt.Errorf("unsupported DB_DRIVER: %q (expected postgres or sqlite)", driver)
	}
}

// connectPostgres opens a PostgreSQL connection pool.
func connectPostgres(dsn string) error {
	if dsn == "" {
		log.Println("⚠ DATABASE_URL not set — running without database")
		return nil
	}

	// Log the database name for operational clarity (never log credentials).
	dbName = extractDBName(dsn)
	rawDSN = dsn
	log.Printf("Connecting to Postgres database %q ...", dbName)

	var err error
	DB, err = sql.Open("postgres", dsn)
	if err != nil {
		return fmt.Errorf("database open: %w", err)
	}

	DB.SetMaxOpenConns(25)
	DB.SetMaxIdleConns(5)
	DB.SetConnMaxLifetime(5 * time.Minute) // recycle connections — prevents stale TCP after PG restart/network blip
	DB.SetConnMaxIdleTime(1 * time.Minute) // close idle connections faster — reduces pool pressure after write bursts

	if err := DB.Ping(); err != nil {
		return fmt.Errorf("database ping: %w", err)
	}

	CurrentDialect = DialectPostgres
	log.Printf("✅ Database connected (postgres, db=%s)", dbName)
	return nil
}

// connectSQLite opens a SQLite database with WAL mode enabled.
func connectSQLite(dsn string) error {
	if dsn == "" {
		dsn = "switchboard.db"
	}

	// Ensure parent directory exists for file-based DBs.
	if dsn != ":memory:" && !strings.HasPrefix(dsn, "file:") {
		dir := filepath.Dir(dsn)
		if dir != "" && dir != "." {
			if err := os.MkdirAll(dir, 0o755); err != nil {
				return fmt.Errorf("create db directory %s: %w", dir, err)
			}
		}
	}

	// modernc.org/sqlite uses "sqlite" as the driver name.
	// Append pragmas via query string for file paths.
	// _time_format tells the driver how to convert between Go time.Time and SQLite TEXT timestamps.
	connStr := dsn
	if dsn != ":memory:" && !strings.Contains(dsn, "?") {
		connStr = fmt.Sprintf("file:%s?_pragma=journal_mode%%3Dwal&_pragma=busy_timeout%%3D5000&_pragma=foreign_keys%%3Don", dsn)
	} else if dsn == ":memory:" {
		connStr = "file::memory:?_pragma=foreign_keys%%3Don"
	}

	var err error
	DB, err = sql.Open("sqlite", connStr)
	if err != nil {
		return fmt.Errorf("database open: %w", err)
	}

	// SQLite performs best with a single writer.
	DB.SetMaxOpenConns(1)
	DB.SetMaxIdleConns(1)

	if err := DB.Ping(); err != nil {
		return fmt.Errorf("database ping: %w", err)
	}

	// Verify WAL mode took effect (some in-memory modes ignore it).
	var mode string
	DB.QueryRow("PRAGMA journal_mode").Scan(&mode)
	dbName = dsn
	log.Printf("✅ Database connected (sqlite, journal_mode=%s, path=%s)", mode, dsn)

	CurrentDialect = DialectSQLite
	return nil
}

// detectDriver infers the driver from DATABASE_URL format.
func detectDriver(dsn string) string {
	if dsn == "" {
		return "postgres"
	}
	if strings.HasPrefix(dsn, "postgres") {
		return "postgres"
	}
	if strings.HasSuffix(dsn, ".db") || strings.HasSuffix(dsn, ".sqlite") ||
		dsn == ":memory:" || strings.HasPrefix(dsn, "file:") {
		return "sqlite"
	}
	return "postgres"
}

// extractDBName pulls the database name from a DSN for logging.
// Handles both URL-style (postgres://user:pass@host/dbname) and
// keyword-style (host=... dbname=...) formats. Never returns creds.
func extractDBName(dsn string) string {
	// URL style: postgres://user:pass@host:port/dbname?params
	if strings.Contains(dsn, "://") {
		idx := strings.LastIndex(dsn, "/")
		if idx >= 0 && idx < len(dsn)-1 {
			name := dsn[idx+1:]
			if q := strings.Index(name, "?"); q >= 0 {
				name = name[:q]
			}
			if name != "" {
				return name
			}
		}
	}
	// Keyword style: host=... dbname=xyz ...
	for _, part := range strings.Fields(dsn) {
		if strings.HasPrefix(part, "dbname=") {
			return strings.TrimPrefix(part, "dbname=")
		}
	}
	return "(unknown)"
}

// Close gracefully shuts down the connection pool.
func Close() {
	if DB != nil {
		DB.Close()
	}
}

// IsConnected returns true if a database connection is available.
func IsConnected() bool {
	if DB == nil {
		return false
	}
	return DB.Ping() == nil
}

// dbName holds the database name extracted at connect time.
var dbName string

// Name returns the database name (extracted from the DSN at connect time).
func Name() string {
	return dbName
}
