package kernel

import (
	"context"
	"database/sql"
	"embed"
	"fmt"
	"log"
	"time"

	"github.com/jackc/pgx/v4/pgxpool"
	_ "github.com/jackc/pgx/v4/stdlib"
	"github.com/pressly/goose/v3"
)

type PostgresConnection struct {
	Scheme   string `yaml:"scheme" env:"POSTGRES_SCHEME" env-default:"postgres" yaml-default:"postgres"`
	Port     string `yaml:"port" env:"POSTGRES_PORT" env-default:"5432"`
	Host     string `yaml:"host" env:"POSTGRES_HOST" env-default:"localhost"`
	Name     string `yaml:"name" env:"POSTGRES_NAME" env-default:"postgres"`
	User     string `yaml:"user" env:"POSTGRES_USER" env-default:"user"`
	Password string `yaml:"password" env:"POSTGRES_PASSWORD"`
	PoolMax  int    `yaml:"pool_max" env:"POSTGRES_POOL_MAX" env-default:"2"`
}

func (pc PostgresConnection) String() string {
	return fmt.Sprintf("%s://%s:%s@%s:%s/%s",
		pc.Scheme, pc.User, pc.Password, pc.Host, pc.Port, pc.Name)
}

func NewDBConnection(ctx context.Context, pg PostgresConnection) (conn *pgxpool.Pool, err error) {
	cfg, err := pgxpool.ParseConfig(pg.String())
	if err != nil {
		return nil, err
	}
	cfg.MaxConns = 30

	for retries := 0; retries < 5; retries++ {

		if conn, err = pgxpool.ConnectConfig(ctx, cfg); err != nil {
			sleepTime := time.Second * time.Duration(retries)
			log.Printf("%d attempt(s) to postgres failed, retrying in %v: %v", retries+1, sleepTime, err)
			time.Sleep(sleepTime)
			continue
		}

		break
	}

	if err != nil {
		return nil, fmt.Errorf("could not connect to postgres: %w", err)
	}

	return conn, nil
}

// deprecated
func Migrate(ctx context.Context, pg PostgresConnection, sql string) error {
	conn, err := NewDBConnection(ctx, pg)
	if err != nil {
		return err
	}

	defer conn.Close()

	if _, err := conn.Exec(ctx, sql); err != nil {
		return err
	}

	return nil
}

func MigrateDB(ctx context.Context, pg PostgresConnection, migrationFS embed.FS, migrationKey string) error {
	db, err := sql.Open("pgx", pg.String())
	if err != nil {
		return fmt.Errorf("could not open DB connection: %w", err)
	}
	defer db.Close()

	goose.SetTableName(fmt.Sprintf("bh_%s_migrations", migrationKey))
	goose.SetVerbose(currentLogLevel == LevelTrace)
	goose.SetBaseFS(migrationFS)

	if err := goose.SetDialect("postgres"); err != nil {
		return err
	}

	if migrationKey == "" {
		migrationKey = "public"
	}

	path := "internal/data/postgres/migrations"
	if err := goose.Up(db, path); err != nil {
		return err
	}

	if err := goose.Version(db, path); err != nil {
		return err
	}

	return nil
}
