package testutils

import (
	"context"
	"fmt"
	"os"
	"path/filepath"
	"runtime"
	"sort"
	"strings"
	"sync"
	"time"

	"github.com/testcontainers/testcontainers-go"
	"github.com/testcontainers/testcontainers-go/modules/postgres"
	"github.com/testcontainers/testcontainers-go/wait"
	"go.uber.org/zap"
)

var (
	pgContainer   *postgres.PostgresContainer
	pgDSN         string
	containerOnce sync.Once
	pgErr         error
)

// GetTestContainerDB ensures the postgres container is running and returns the DSN.
// It uses a singleton pattern to start the container only once per test run.
func GetTestContainerDB(ctx context.Context) (string, error) {
	containerOnce.Do(func() {
		pgErr = setupPostgresContainer(ctx)
	})
	return pgDSN, pgErr
}

func setupPostgresContainer(ctx context.Context) error {
	// Find project root relative to this file
	// This file is in internal/testutils/setup.go
	_, filename, _, _ := runtime.Caller(0)
	projectRoot := filepath.Join(filepath.Dir(filename), "../..")
	migrationsDir := filepath.Join(projectRoot, "migrations")

	// Collect migration files
	files, err := os.ReadDir(migrationsDir)
	if err != nil {
		return fmt.Errorf("failed to read migrations dir: %w", err)
	}

	var migrationFiles []string
	for _, f := range files {
		// MOD-P1-001: Exclude cleanup migrations that may fail if tables don't exist yet
		// These migrations are meant to be run on existing databases, not fresh ones
		if strings.HasSuffix(f.Name(), ".sql") && !strings.Contains(f.Name(), "000000_cleanup") {
			migrationFiles = append(migrationFiles, filepath.Join(migrationsDir, f.Name()))
		}
	}
	sort.Strings(migrationFiles) // Ensure alphabetical order (001_, 002_, ...)

	// MOD-P1-001: Retry container startup with exponential backoff
	// Use a simple logger for testcontainers (zap.L() may not be initialized in tests)
	logger := zap.NewNop()
	if zap.L() != nil {
		logger = zap.L()
	}

	var containerErr error
	maxRetries := 3
	retryDelay := 2 * time.Second

	for attempt := 1; attempt <= maxRetries; attempt++ {
		logger.Info("Starting PostgreSQL testcontainer",
			zap.Int("attempt", attempt),
			zap.Int("max_retries", maxRetries),
			zap.Int("migration_files", len(migrationFiles)),
		)

		// Start Postgres container with improved wait strategy
		pgContainer, containerErr = postgres.Run(ctx,
			"postgres:15-alpine",
			postgres.WithDatabase("veza_test"),
			postgres.WithUsername("veza"),
			postgres.WithPassword("veza"),
			postgres.WithInitScripts(migrationFiles...),
			testcontainers.WithWaitStrategy(
				wait.ForLog("database system is ready to accept connections").
					WithOccurrence(2).
					WithStartupTimeout(90*time.Second)), // Increased timeout from 60s to 90s
		)

		if containerErr == nil {
			logger.Info("PostgreSQL testcontainer started successfully",
				zap.Int("attempt", attempt),
			)
			break // Success
		}

		// Log retry attempt
		logger.Warn("Failed to start PostgreSQL testcontainer, retrying",
			zap.Int("attempt", attempt),
			zap.Int("max_retries", maxRetries),
			zap.Error(containerErr),
		)

		if attempt < maxRetries {
			backoff := retryDelay * time.Duration(attempt) // Exponential backoff
			logger.Info("Waiting before retry",
				zap.Duration("backoff", backoff),
			)
			time.Sleep(backoff)
		}
	}

	if containerErr != nil {
		logger.Error("Failed to start PostgreSQL testcontainer after all retries",
			zap.Int("max_retries", maxRetries),
			zap.Error(containerErr),
		)
		return fmt.Errorf("failed to start postgres container after %d attempts: %w", maxRetries, containerErr)
	}

	var dsnErr error
	pgDSN, dsnErr = pgContainer.ConnectionString(ctx, "sslmode=disable")
	if dsnErr != nil {
		return fmt.Errorf("failed to get connection string: %w", dsnErr)
	}

	return nil
}

// TerminateContainer allows manual termination if needed (mostly for cleanup)
func TerminateContainer(ctx context.Context) error {
	if pgContainer != nil {
		return pgContainer.Terminate(ctx)
	}
	return nil
}
