package database

import (
	"fmt"
	"log"
	"os"
	"path/filepath"
	"runtime"
	"sort"
	"strings"
)

// schemaVersion tracks the latest applied migration.
var schemaVersion string = "none"

// SchemaVersion returns the current schema version string.
func SchemaVersion() string { return schemaVersion }

// Migrate runs all pending migrations for the current dialect.
// Migration files live in migrations/<dialect>/ subdirectories.
// Falls back to the root migrations/ directory if no subdirectory exists
// (backward compatible with pre-v0.17.1 postgres-only layouts).
func Migrate() error {
	if DB == nil {
		return fmt.Errorf("database not connected")
	}

	// Create tracking table — syntax compatible with both drivers.
	createMigrations := `CREATE TABLE IF NOT EXISTS schema_migrations (
		version    TEXT PRIMARY KEY,
		applied_at TEXT DEFAULT (datetime('now'))
	)`
	if IsPostgres() {
		createMigrations = `CREATE TABLE IF NOT EXISTS schema_migrations (
			version    TEXT PRIMARY KEY,
			applied_at TIMESTAMPTZ DEFAULT NOW()
		)`
	}
	if _, err := DB.Exec(createMigrations); err != nil {
		return fmt.Errorf("create migrations table: %w", err)
	}

	// Locate migration files for current dialect.
	migrationsDir := findMigrationsDir()
	if migrationsDir == "" {
		log.Println("⚠ No migrations directory found — skipping schema migration")
		return nil
	}

	entries, err := os.ReadDir(migrationsDir)
	if err != nil {
		return fmt.Errorf("read migrations dir: %w", err)
	}

	var files []string
	for _, e := range entries {
		if !e.IsDir() && strings.HasSuffix(e.Name(), ".sql") {
			files = append(files, e.Name())
		}
	}
	sort.Strings(files)

	if len(files) == 0 {
		log.Println("  No migration files found")
		return nil
	}

	// Compat: rename old numeric-only version entries to full filenames.
	for _, file := range files {
		prefix := strings.SplitN(file, "_", 2)[0]
		DB.Exec("UPDATE schema_migrations SET version = ? WHERE version = ? AND version != ?", file, prefix, file)
		if IsPostgres() {
			DB.Exec("UPDATE schema_migrations SET version = $1 WHERE version = $2 AND version != $1", file, prefix)
		}
	}

	// Apply pending migrations.
	applied := 0
	for _, file := range files {
		version := extractVersion(file)
		if version == "" {
			continue
		}

		var exists bool
		if IsPostgres() {
			DB.QueryRow("SELECT EXISTS(SELECT 1 FROM schema_migrations WHERE version = $1)", version).Scan(&exists)
		} else {
			DB.QueryRow("SELECT EXISTS(SELECT 1 FROM schema_migrations WHERE version = ?)", version).Scan(&exists)
		}
		if exists {
			schemaVersion = version
			continue
		}

		path := filepath.Join(migrationsDir, file)
		sqlBytes, err := os.ReadFile(path)
		if err != nil {
			return fmt.Errorf("read %s: %w", file, err)
		}

		log.Printf("  Applying migration %s...", file)
		if _, err := DB.Exec(string(sqlBytes)); err != nil {
			return fmt.Errorf("apply %s: %w", file, err)
		}

		if IsPostgres() {
			if _, err := DB.Exec("INSERT INTO schema_migrations (version) VALUES ($1)", version); err != nil {
				return fmt.Errorf("record %s: %w", file, err)
			}
		} else {
			if _, err := DB.Exec("INSERT INTO schema_migrations (version) VALUES (?)", version); err != nil {
				return fmt.Errorf("record %s: %w", file, err)
			}
		}

		schemaVersion = version
		applied++
	}

	if applied > 0 {
		log.Printf("  ✅ Applied %d migration(s), schema at %s", applied, schemaVersion)
	} else {
		log.Printf("  Schema up to date at %s", schemaVersion)
	}

	return nil
}

// extractVersion returns the filename as the version key if it's a valid
// migration file (starts with digit, ends with .sql).
func extractVersion(filename string) string {
	if !strings.HasSuffix(filename, ".sql") {
		return ""
	}
	if len(filename) == 0 || filename[0] < '0' || filename[0] > '9' {
		return ""
	}
	return filename
}

// findMigrationsDir locates the dialect-specific migrations directory.
// Looks for migrations/<dialect>/ first, then falls back to migrations/.
func findMigrationsDir() string {
	dialectDir := CurrentDialect.String() // "postgres" or "sqlite"

	// Candidates: dialect-specific first, then generic.
	bases := []string{
		"database/migrations",
		"server/database/migrations",
		"../database/migrations",
	}

	// Also check relative to this source file (for tests).
	_, thisFile, _, ok := runtime.Caller(0)
	if ok {
		dir := filepath.Dir(thisFile)
		bases = append(bases, filepath.Join(dir, "migrations"))
	}

	// Try dialect-specific subdir first.
	for _, base := range bases {
		candidate := filepath.Join(base, dialectDir)
		if info, err := os.Stat(candidate); err == nil && info.IsDir() {
			return candidate
		}
	}

	// Fall back to root migrations/ (backward compat for postgres).
	if IsPostgres() {
		for _, base := range bases {
			if info, err := os.Stat(base); err == nil && info.IsDir() {
				return base
			}
		}
	}

	return ""
}
