package local

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

	"github.com/go-sql-driver/mysql"

	migrate "github.com/sqlc-dev/sqlc/internal/migrations"
	"github.com/sqlc-dev/sqlc/internal/sql/sqlpath"
	"github.com/sqlc-dev/sqlc/internal/sqltest/docker"
	"github.com/sqlc-dev/sqlc/internal/sqltest/native"
)

var mysqlSync sync.Once
var mysqlPool *sql.DB

func MySQL(t *testing.T, migrations []string) string {
	ctx := context.Background()
	t.Helper()

	dburi := os.Getenv("MYSQL_SERVER_URI")
	if dburi == "" {
		if ierr := docker.Installed(); ierr == nil {
			u, err := docker.StartMySQLServer(ctx)
			if err != nil {
				t.Fatal(err)
			}
			dburi = u
		} else if ierr := native.Supported(); ierr == nil {
			// Fall back to native installation when Docker is not available
			u, err := native.StartMySQLServer(ctx)
			if err != nil {
				t.Fatal(err)
			}
			dburi = u
		} else {
			t.Skip("MYSQL_SERVER_URI is empty and neither Docker nor native installation is available")
		}
	}

	mysqlSync.Do(func() {
		db, err := sql.Open("mysql", dburi)
		if err != nil {
			t.Fatal(err)
		}
		mysqlPool = db
	})

	if mysqlPool == nil {
		t.Fatalf("MySQL pool creation failed")
	}

	var seed []string
	files, err := sqlpath.Glob(migrations)
	if err != nil {
		t.Fatal(err)
	}
	for _, f := range files {
		blob, err := os.ReadFile(f)
		if err != nil {
			t.Fatal(err)
		}
		seed = append(seed, migrate.RemoveRollbackStatements(string(blob)))
	}

	cfg, err := mysql.ParseDSN(dburi)
	if err != nil {
		t.Fatal(err)
	}

	name := fmt.Sprintf("sqlc_test_%s", id())

	if _, err := mysqlPool.ExecContext(ctx, fmt.Sprintf("CREATE DATABASE `%s`", name)); err != nil {
		t.Fatal(err)
	}

	cfg.DBName = name

	dropQuery := fmt.Sprintf("DROP DATABASE `%s`", name)

	t.Cleanup(func() {
		if _, err := mysqlPool.ExecContext(ctx, dropQuery); err != nil {
			t.Fatal(err)
		}
	})

	db, err := sql.Open("mysql", cfg.FormatDSN())
	if err != nil {
		t.Fatalf("connect %s: %s", name, err)
	}
	defer db.Close()

	for _, q := range seed {
		if len(strings.TrimSpace(q)) == 0 {
			continue
		}
		if _, err := db.ExecContext(ctx, q); err != nil {
			t.Fatalf("%s: %s", q, err)
		}
	}

	return cfg.FormatDSN()
}
