package acceptance

import (
	"context"
	"database/sql"
	"fmt"
	"os"
	"strings"
	"testing"
	"time"

	_ "github.com/go-sql-driver/mysql"
	"github.com/redis/go-redis/v9"
)

type FixtureLoader struct {
	cfg Config
}

func newFixtureLoader(c Config) *FixtureLoader {
	return &FixtureLoader{cfg: c}
}

func (l *FixtureLoader) SeedIfConfigured(t *testing.T, ctx context.Context) {
	t.Helper()
	if !l.hasDBConfig() {
		t.Log("staging DB config not provided; skipping seed loader")
		return
	}
	sqlBytes, err := os.ReadFile(l.cfg.SeedSQLPath)
	if err != nil {
		t.Fatalf("read seed SQL %q: %v", l.cfg.SeedSQLPath, err)
	}

	db, err := sql.Open("mysql", l.dsn(true))
	if err != nil {
		t.Fatalf("open staging DB: %v", err)
	}
	defer func() {
		_ = db.Close()
	}()

	if err := db.PingContext(ctx); err != nil {
		t.Fatalf("ping staging DB: %v", err)
	}

	statements := splitSQL(string(sqlBytes))
	for _, statement := range statements {
		statement = strings.ReplaceAll(statement, "{{RUN_ID}}", l.cfg.RunID)
		if _, err := db.ExecContext(ctx, statement); err != nil {
			t.Fatalf("execute seed SQL: %v; statement=%s", err, statement)
		}
	}
}

func (l *FixtureLoader) NodeSecret(ctx context.Context) (string, error) {
	if l.cfg.NodeSecret != "" {
		return l.cfg.NodeSecret, nil
	}
	if !l.hasDBConfig() {
		return "", nil
	}

	db, err := sql.Open("mysql", l.dsn(false))
	if err != nil {
		return "", fmt.Errorf("open staging DB: %w", err)
	}
	defer func() {
		_ = db.Close()
	}()

	ctx, cancel := context.WithTimeout(ctx, 5*time.Second)
	defer cancel()

	var secret string
	err = db.QueryRowContext(ctx, "SELECT value FROM system WHERE category = 'server' AND `key` = 'NodeSecret' LIMIT 1").Scan(&secret)
	if err != nil {
		return "", fmt.Errorf("read NodeSecret from staging DB: %w", err)
	}
	return secret, nil
}

func (l *FixtureLoader) RedisClient() *redis.Client {
	if l.cfg.Redis.Addr == "" {
		return nil
	}
	return redis.NewClient(&redis.Options{
		Addr:     l.cfg.Redis.Addr,
		Password: l.cfg.Redis.Password,
		DB:       l.cfg.Redis.DB,
	})
}

func (l *FixtureLoader) hasDBConfig() bool {
	return l.cfg.DB.Host != "" && l.cfg.DB.User != "" && l.cfg.DB.Name != ""
}

func (l *FixtureLoader) dsn(multiStatements bool) string {
	params := "parseTime=true&timeout=5s&readTimeout=10s&writeTimeout=10s"
	if multiStatements {
		params += "&multiStatements=true"
	}
	return fmt.Sprintf("%s:%s@tcp(%s)/%s?%s",
		l.cfg.DB.User,
		l.cfg.DB.Password,
		l.cfg.DB.Host,
		l.cfg.DB.Name,
		params,
	)
}

func splitSQL(script string) []string {
	parts := strings.Split(script, ";")
	statements := make([]string, 0, len(parts))
	for _, part := range parts {
		lines := strings.Split(part, "\n")
		kept := make([]string, 0, len(lines))
		for _, line := range lines {
			line = strings.TrimSpace(line)
			if line == "" || strings.HasPrefix(line, "--") {
				continue
			}
			kept = append(kept, line)
		}
		statement := strings.Join(kept, "\n")
		if statement == "" || strings.HasPrefix(statement, "--") {
			continue
		}
		statements = append(statements, statement)
	}
	return statements
}
