package main

import (
	"encoding/json"
	"flag"
	"fmt"
	"os"
	"os/exec"
	"strings"
	"time"
)

// MetabaseResult contiene los IDs y nombres de los recursos creados.
type MetabaseResult struct {
	NetworkID    string `json:"network_id"`
	PostgresID   string `json:"postgres_id"`
	MetabaseID   string `json:"metabase_id"`
	NetworkName  string `json:"network_name"`
	PostgresName string `json:"postgres_name"`
	MetabaseName string `json:"metabase_name"`
	MetabaseURL  string `json:"metabase_url"`
}

func main() {
	project := flag.String("project", "", "Prefijo para nombres de contenedores y red (requerido)")
	mbPort := flag.String("metabase-port", "3000", "Puerto host para Metabase")
	pgPort := flag.String("pg-port", "5432", "Puerto host para Postgres")
	pgUser := flag.String("pg-user", "metabase", "Usuario Postgres")
	pgPass := flag.String("pg-password", "metabase", "Password Postgres")
	pgDB := flag.String("pg-database", "metabase", "Base de datos Postgres")
	pgVolume := flag.String("pg-volume", "", "Path host para persistencia Postgres (default: docker named volume)")
	mbVolumes := flag.String("mb-volumes", "", "Volumes adicionales para Metabase, separados por coma (ej: /host/path:/container/path,/otro:/dest)")
	flag.Parse()

	if *project == "" {
		fmt.Fprintln(os.Stderr, "error: --project requerido")
		flag.Usage()
		os.Exit(1)
	}

	var extraVolumes []string
	if *mbVolumes != "" {
		extraVolumes = strings.Split(*mbVolumes, ",")
	}

	result, err := initMetabase(*project, *mbPort, *pgPort, *pgUser, *pgPass, *pgDB, *pgVolume, extraVolumes)
	if err != nil {
		fmt.Fprintf(os.Stderr, "error: %v\n", err)
		os.Exit(1)
	}

	enc := json.NewEncoder(os.Stdout)
	enc.SetIndent("", "  ")
	enc.Encode(result)
}

func initMetabase(project, mbPort, pgPort, pgUser, pgPass, pgDB, pgVolume string, mbExtraVolumes []string) (*MetabaseResult, error) {
	networkName := project + "-net"
	pgName := project + "-postgres"
	mbName := project + "-metabase"

	// 1. Crear red
	fmt.Fprintf(os.Stderr, "[1/5] Creando red %s...\n", networkName)
	netID, err := dockerCreateNetwork(networkName, "bridge")
	if err != nil {
		return nil, fmt.Errorf("creating network: %w", err)
	}

	// 2. Pull imágenes
	for i, img := range []string{"postgres:16", "metabase/metabase:latest"} {
		fmt.Fprintf(os.Stderr, "[2/5] Pulling %s (%d/2)...\n", img, i+1)
		if err := dockerPull(img); err != nil {
			return nil, fmt.Errorf("pulling %s: %w", img, err)
		}
	}

	// 3. Iniciar Postgres
	fmt.Fprintf(os.Stderr, "[3/5] Iniciando Postgres %s...\n", pgName)
	vol := pgName + "-data:/var/lib/postgresql/data"
	if pgVolume != "" {
		vol = pgVolume + ":/var/lib/postgresql/data"
	}

	pgArgs := []string{
		"run", "-d",
		"--name", pgName,
		"--network", networkName,
		"-p", pgPort + ":5432",
		"-e", "POSTGRES_USER=" + pgUser,
		"-e", "POSTGRES_PASSWORD=" + pgPass,
		"-e", "POSTGRES_DB=" + pgDB,
		"-v", vol,
		"postgres:16",
	}
	pgID, err := dockerCmd(pgArgs...)
	if err != nil {
		return nil, fmt.Errorf("starting postgres: %w", err)
	}

	// 4. Health check con retry exponencial
	fmt.Fprintf(os.Stderr, "[4/5] Esperando a que Postgres esté listo...\n")
	err = retryWithBackoff(func() error {
		out, execErr := dockerCmd("exec", pgName, "pg_isready", "-U", pgUser)
		if execErr != nil {
			return fmt.Errorf("pg_isready: %s", out)
		}
		return nil
	}, 10, 2*time.Second)
	if err != nil {
		return nil, fmt.Errorf("waiting for postgres: %w", err)
	}
	fmt.Fprintf(os.Stderr, "       Postgres listo.\n")

	// 5. Iniciar Metabase
	fmt.Fprintf(os.Stderr, "[5/5] Iniciando Metabase %s...\n", mbName)
	mbArgs := []string{
		"run", "-d",
		"--name", mbName,
		"--network", networkName,
		"-p", mbPort + ":3000",
		"-e", "MB_DB_TYPE=postgres",
		"-e", "MB_DB_DBNAME=" + pgDB,
		"-e", "MB_DB_PORT=5432",
		"-e", "MB_DB_USER=" + pgUser,
		"-e", "MB_DB_PASS=" + pgPass,
		"-e", "MB_DB_HOST=" + pgName,
	}
	for _, v := range mbExtraVolumes {
		mbArgs = append(mbArgs, "-v", strings.TrimSpace(v))
	}
	mbArgs = append(mbArgs, "metabase/metabase:latest")
	mbID, err := dockerCmd(mbArgs...)
	if err != nil {
		return nil, fmt.Errorf("starting metabase: %w", err)
	}

	// 6. Fix permisos para volumes SQLite — Metabase corre como UID 2000
	for _, v := range mbExtraVolumes {
		parts := strings.SplitN(strings.TrimSpace(v), ":", 2)
		if len(parts) < 2 {
			continue
		}
		destPath := parts[1]
		// Si es un archivo, fix el directorio padre; si es directorio, fix directo
		dir := destPath
		if strings.Contains(destPath, ".") {
			// Probablemente un archivo, usar dirname
			idx := strings.LastIndex(destPath, "/")
			if idx > 0 {
				dir = destPath[:idx]
			}
		}
		fmt.Fprintf(os.Stderr, "       Fijando permisos de %s para usuario metabase...\n", dir)
		dockerCmd("exec", "-u", "root", mbName, "chown", "metabase:metabase", dir)
	}

	mbURL := fmt.Sprintf("http://localhost:%s", mbPort)
	fmt.Fprintf(os.Stderr, "\nStack listo. Metabase disponible en %s\n", mbURL)

	return &MetabaseResult{
		NetworkID:    netID,
		PostgresID:   pgID,
		MetabaseID:   mbID,
		NetworkName:  networkName,
		PostgresName: pgName,
		MetabaseName: mbName,
		MetabaseURL:  mbURL,
	}, nil
}

// --- Funciones Docker (reimplementan las del registry sin imports internos) ---

func dockerCreateNetwork(name, driver string) (string, error) {
	return dockerCmd("network", "create", "--driver", driver, name)
}

func dockerPull(image string) error {
	_, err := dockerCmd("pull", image)
	return err
}

func dockerCmd(args ...string) (string, error) {
	out, err := exec.Command("docker", args...).CombinedOutput()
	if err != nil {
		return "", fmt.Errorf("docker %s: %s", args[0], strings.TrimSpace(string(out)))
	}
	return strings.TrimSpace(string(out)), nil
}

// retryWithBackoff reimplementa RetryWithBackoff de core para funciones () error.
func retryWithBackoff(fn func() error, maxRetries int, baseDelay time.Duration) error {
	var lastErr error
	for attempt := 0; attempt <= maxRetries; attempt++ {
		if err := fn(); err == nil {
			return nil
		} else {
			lastErr = err
		}
		if attempt < maxRetries {
			delay := baseDelay * (1 << uint(attempt))
			time.Sleep(delay)
		}
	}
	return fmt.Errorf("all %d retries exhausted: %w", maxRetries+1, lastErr)
}
