package admin

import (
	"context"
	"crypto/sha256"
	"encoding/hex"
	"errors"
	"fmt"
	"os"
	"path/filepath"
	"sort"
	"strings"

	"github.com/jackc/pgx/v5"
	"github.com/jackc/pgx/v5/pgxpool"
)

type Migration struct {
	Version  string
	Path     string
	Checksum string
}

func RunMigrations(ctx context.Context, db *pgxpool.Pool, dir string) error {
	if db == nil {
		return errors.New("database is required")
	}

	migrations, err := LoadMigrationFiles(dir)
	if err != nil {
		return err
	}

	tx, err := db.Begin(ctx)
	if err != nil {
		return fmt.Errorf("begin migration transaction: %w", err)
	}
	defer tx.Rollback(ctx)

	if _, err := tx.Exec(ctx, `
CREATE TABLE IF NOT EXISTS admin_schema_migrations (
	version TEXT PRIMARY KEY,
	checksum TEXT NOT NULL,
	applied_at TIMESTAMPTZ NOT NULL DEFAULT now()
)`); err != nil {
		return fmt.Errorf("ensure migration table: %w", err)
	}

	for _, migration := range migrations {
		if err := applyMigration(ctx, tx, migration); err != nil {
			return err
		}
	}

	if err := tx.Commit(ctx); err != nil {
		return fmt.Errorf("commit migrations: %w", err)
	}
	return nil
}

func LoadMigrationFiles(dir string) ([]Migration, error) {
	entries, err := os.ReadDir(dir)
	if err != nil {
		return nil, fmt.Errorf("read migrations dir: %w", err)
	}

	var migrations []Migration
	for _, entry := range entries {
		if entry.IsDir() || !strings.HasSuffix(entry.Name(), ".sql") {
			continue
		}

		path := filepath.Join(dir, entry.Name())
		content, err := os.ReadFile(path)
		if err != nil {
			return nil, fmt.Errorf("read migration %s: %w", entry.Name(), err)
		}

		migrations = append(migrations, Migration{
			Version:  entry.Name(),
			Path:     path,
			Checksum: checksum(content),
		})
	}

	sort.Slice(migrations, func(i, j int) bool {
		return migrations[i].Version < migrations[j].Version
	})

	return migrations, nil
}

func applyMigration(ctx context.Context, tx pgx.Tx, migration Migration) error {
	var appliedChecksum string
	err := tx.QueryRow(ctx, `
SELECT checksum
FROM admin_schema_migrations
WHERE version = $1`, migration.Version).Scan(&appliedChecksum)

	if err == nil {
		if appliedChecksum != migration.Checksum {
			return fmt.Errorf("migration %s checksum changed", migration.Version)
		}
		return nil
	}
	if !errors.Is(err, pgx.ErrNoRows) {
		return fmt.Errorf("check migration %s: %w", migration.Version, err)
	}

	content, err := os.ReadFile(migration.Path)
	if err != nil {
		return fmt.Errorf("read migration %s: %w", migration.Version, err)
	}
	if _, err := tx.Exec(ctx, string(content)); err != nil {
		return fmt.Errorf("apply migration %s: %w", migration.Version, err)
	}
	if _, err := tx.Exec(ctx, `
INSERT INTO admin_schema_migrations (version, checksum)
VALUES ($1, $2)`, migration.Version, migration.Checksum); err != nil {
		return fmt.Errorf("record migration %s: %w", migration.Version, err)
	}
	return nil
}

func checksum(content []byte) string {
	sum := sha256.Sum256(content)
	return hex.EncodeToString(sum[:])
}
