package main

import (
	"context"
	"fmt"
	"log/slog"
	"os"
	"path/filepath"
	"sort"
	"strings"

	"github.com/easyai/easyai-ai-gateway/apps/api/internal/config"
	"github.com/jackc/pgx/v5"
)

const (
	noTransactionMigrationMarker = "-- easyai:migration:no-transaction"
	migrationStatementSeparator  = "-- easyai:migration:statement"
)

func main() {
	cfg := config.Load()
	logger := slog.New(slog.NewTextHandler(os.Stdout, nil))
	ctx := context.Background()

	conn, err := pgx.Connect(ctx, cfg.DatabaseURL)
	if err != nil {
		logger.Error("connect postgres failed", "error", err)
		os.Exit(1)
	}
	defer conn.Close(ctx)
	if _, err := conn.Exec(ctx, "SET standard_conforming_strings = on"); err != nil {
		logger.Error("enforce standard SQL string semantics failed", "error", err)
		os.Exit(1)
	}

	if _, err := conn.Exec(ctx, `
CREATE TABLE IF NOT EXISTS schema_migrations (
  version text PRIMARY KEY,
  applied_at timestamptz NOT NULL DEFAULT now()
);`); err != nil {
		logger.Error("ensure schema_migrations failed", "error", err)
		os.Exit(1)
	}

	files, err := filepath.Glob("migrations/*.sql")
	if err != nil {
		logger.Error("read migrations failed", "error", err)
		os.Exit(1)
	}
	sort.Strings(files)

	for _, file := range files {
		version := strings.TrimSuffix(filepath.Base(file), filepath.Ext(file))
		var exists bool
		if err := conn.QueryRow(ctx, "SELECT EXISTS (SELECT 1 FROM schema_migrations WHERE version=$1)", version).Scan(&exists); err != nil {
			logger.Error("check migration failed", "version", version, "error", err)
			os.Exit(1)
		}
		if exists {
			logger.Info("migration skipped", "version", version)
			continue
		}

		sqlBytes, err := os.ReadFile(file)
		if err != nil {
			logger.Error("read migration file failed", "file", file, "error", err)
			os.Exit(1)
		}

		noTransaction, statements := migrationStatements(string(sqlBytes))
		if noTransaction {
			for _, statement := range statements {
				if _, err := conn.Exec(ctx, statement); err != nil {
					logger.Error("execute non-transaction migration failed", "version", version, "error", err)
					os.Exit(1)
				}
			}
			if _, err := conn.Exec(ctx, "INSERT INTO schema_migrations(version) VALUES($1)", version); err != nil {
				logger.Error("record non-transaction migration failed", "version", version, "error", err)
				os.Exit(1)
			}
			logger.Info("migration applied", "version", version, "transactional", false)
			continue
		}

		tx, err := conn.Begin(ctx)
		if err != nil {
			logger.Error("begin migration failed", "version", version, "error", err)
			os.Exit(1)
		}
		// Pin string parsing semantics inside every migration transaction. A
		// previous migration may have changed the session GUC, while each file is
		// parsed and executed independently.
		if _, err := tx.Exec(ctx, "SET LOCAL standard_conforming_strings = on"); err != nil {
			_ = tx.Rollback(ctx)
			logger.Error("enforce migration SQL string semantics failed", "version", version, "error", err)
			os.Exit(1)
		}
		if _, err := tx.Exec(ctx, string(sqlBytes)); err != nil {
			_ = tx.Rollback(ctx)
			logger.Error("execute migration failed", "version", version, "error", err)
			os.Exit(1)
		}
		if _, err := tx.Exec(ctx, "INSERT INTO schema_migrations(version) VALUES($1)", version); err != nil {
			_ = tx.Rollback(ctx)
			logger.Error("record migration failed", "version", version, "error", err)
			os.Exit(1)
		}
		if err := tx.Commit(ctx); err != nil {
			logger.Error("commit migration failed", "version", version, "error", err)
			os.Exit(1)
		}
		logger.Info("migration applied", "version", version)
	}

	fmt.Println("migrations complete")
}

func migrationStatements(sql string) (bool, []string) {
	trimmed := strings.TrimSpace(sql)
	if !strings.HasPrefix(trimmed, noTransactionMigrationMarker) {
		return false, []string{sql}
	}
	trimmed = strings.TrimSpace(strings.TrimPrefix(trimmed, noTransactionMigrationMarker))
	parts := strings.Split(trimmed, migrationStatementSeparator)
	statements := make([]string, 0, len(parts))
	for _, part := range parts {
		if statement := strings.TrimSpace(part); statement != "" {
			statements = append(statements, statement)
		}
	}
	return true, statements
}
