// Package drill - Docker container management for DR drills
package drill

import (
	"context"
	"fmt"
	"os/exec"
	"strings"
	"time"
)

// DockerManager handles Docker container operations for DR drills
type DockerManager struct {
	verbose bool
}

// NewDockerManager creates a new Docker manager
func NewDockerManager(verbose bool) *DockerManager {
	return &DockerManager{verbose: verbose}
}

// ContainerConfig holds Docker container configuration
type ContainerConfig struct {
	Image         string            // Docker image (e.g., "postgres:15")
	Name          string            // Container name
	Port          int               // Host port to map
	ContainerPort int               // Container port
	Environment   map[string]string // Environment variables
	Volumes       []string          // Volume mounts
	Network       string            // Docker network
	Timeout       int               // Startup timeout in seconds
}

// ContainerInfo holds information about a running container
type ContainerInfo struct {
	ID      string
	Name    string
	Image   string
	Port    int
	Status  string
	Started time.Time
	Healthy bool
}

// CheckDockerAvailable verifies Docker is installed and running
func (dm *DockerManager) CheckDockerAvailable(ctx context.Context) error {
	cmd := exec.CommandContext(ctx, "docker", "version")
	output, err := cmd.CombinedOutput()
	if err != nil {
		return fmt.Errorf("docker not available: %w (output: %s)", err, string(output))
	}
	return nil
}

// PullImage pulls a Docker image if not present
func (dm *DockerManager) PullImage(ctx context.Context, image string) error {
	// Check if image exists locally
	checkCmd := exec.CommandContext(ctx, "docker", "image", "inspect", image)
	if err := checkCmd.Run(); err == nil {
		// Image exists
		return nil
	}

	// Pull the image
	pullCmd := exec.CommandContext(ctx, "docker", "pull", image)
	output, err := pullCmd.CombinedOutput()
	if err != nil {
		return fmt.Errorf("failed to pull image %s: %w (output: %s)", image, err, string(output))
	}

	return nil
}

// CreateContainer creates and starts a database container
func (dm *DockerManager) CreateContainer(ctx context.Context, config *ContainerConfig) (*ContainerInfo, error) {
	args := []string{
		"run", "-d",
		"--name", config.Name,
		"-p", fmt.Sprintf("%d:%d", config.Port, config.ContainerPort),
	}

	// Add environment variables
	for k, v := range config.Environment {
		args = append(args, "-e", fmt.Sprintf("%s=%s", k, v))
	}

	// Add volumes
	for _, v := range config.Volumes {
		args = append(args, "-v", v)
	}

	// Add network if specified
	if config.Network != "" {
		args = append(args, "--network", config.Network)
	}

	// Add image
	args = append(args, config.Image)

	cmd := exec.CommandContext(ctx, "docker", args...)
	output, err := cmd.CombinedOutput()
	if err != nil {
		return nil, fmt.Errorf("failed to create container: %w (output: %s)", err, string(output))
	}

	containerID := strings.TrimSpace(string(output))

	return &ContainerInfo{
		ID:      containerID,
		Name:    config.Name,
		Image:   config.Image,
		Port:    config.Port,
		Status:  "created",
		Started: time.Now(),
	}, nil
}

// WaitForHealth waits for container to be healthy
func (dm *DockerManager) WaitForHealth(ctx context.Context, containerID string, dbType string, timeout int) error {
	deadline := time.Now().Add(time.Duration(timeout) * time.Second)
	ticker := time.NewTicker(time.Second)
	defer ticker.Stop()

	for {
		select {
		case <-ctx.Done():
			return ctx.Err()
		case <-ticker.C:
			if time.Now().After(deadline) {
				return fmt.Errorf("timeout waiting for container to be healthy")
			}

			// Check container health
			healthCmd := dm.healthCheckCommand(dbType)
			args := append([]string{"exec", containerID}, healthCmd...)
			cmd := exec.CommandContext(ctx, "docker", args...)
			if err := cmd.Run(); err == nil {
				return nil // Container is healthy
			}
		}
	}
}

// healthCheckCommand returns the health check command for a database type
func (dm *DockerManager) healthCheckCommand(dbType string) []string {
	switch dbType {
	case "postgresql", "postgres":
		return []string{"pg_isready", "-U", "postgres"}
	case "mysql":
		return []string{"mysqladmin", "ping", "-h", "localhost", "-u", "root", "--password=root"}
	case "mariadb":
		return []string{"mariadb-admin", "ping", "-h", "localhost", "-u", "root", "--password=root"}
	default:
		return []string{"echo", "ok"}
	}
}

// ExecCommand executes a command inside the container
func (dm *DockerManager) ExecCommand(ctx context.Context, containerID string, command []string) (string, error) {
	args := append([]string{"exec", containerID}, command...)
	cmd := exec.CommandContext(ctx, "docker", args...)
	output, err := cmd.CombinedOutput()
	if err != nil {
		return string(output), fmt.Errorf("exec failed: %w", err)
	}
	return string(output), nil
}

// CopyToContainer copies a file to the container
func (dm *DockerManager) CopyToContainer(ctx context.Context, containerID, src, dest string) error {
	cmd := exec.CommandContext(ctx, "docker", "cp", src, fmt.Sprintf("%s:%s", containerID, dest))
	output, err := cmd.CombinedOutput()
	if err != nil {
		return fmt.Errorf("copy failed: %w (output: %s)", err, string(output))
	}
	return nil
}

// StopContainer stops a running container
func (dm *DockerManager) StopContainer(ctx context.Context, containerID string) error {
	cmd := exec.CommandContext(ctx, "docker", "stop", containerID)
	output, err := cmd.CombinedOutput()
	if err != nil {
		return fmt.Errorf("failed to stop container: %w (output: %s)", err, string(output))
	}
	return nil
}

// RemoveContainer removes a container
func (dm *DockerManager) RemoveContainer(ctx context.Context, containerID string) error {
	cmd := exec.CommandContext(ctx, "docker", "rm", "-f", containerID)
	output, err := cmd.CombinedOutput()
	if err != nil {
		return fmt.Errorf("failed to remove container: %w (output: %s)", err, string(output))
	}
	return nil
}

// GetContainerLogs retrieves container logs
func (dm *DockerManager) GetContainerLogs(ctx context.Context, containerID string, tail int) (string, error) {
	args := []string{"logs"}
	if tail > 0 {
		args = append(args, "--tail", fmt.Sprintf("%d", tail))
	}
	args = append(args, containerID)

	cmd := exec.CommandContext(ctx, "docker", args...)
	output, err := cmd.CombinedOutput()
	if err != nil {
		return "", fmt.Errorf("failed to get logs: %w", err)
	}
	return string(output), nil
}

// ListDrillContainers lists all containers created by drill operations
func (dm *DockerManager) ListDrillContainers(ctx context.Context) ([]*ContainerInfo, error) {
	cmd := exec.CommandContext(ctx, "docker", "ps", "-a",
		"--filter", "name=drill_",
		"--format", "{{.ID}}\t{{.Names}}\t{{.Image}}\t{{.Status}}")

	output, err := cmd.CombinedOutput()
	if err != nil {
		return nil, fmt.Errorf("failed to list containers: %w", err)
	}

	var containers []*ContainerInfo
	lines := strings.Split(strings.TrimSpace(string(output)), "\n")
	for _, line := range lines {
		if line == "" {
			continue
		}
		parts := strings.Split(line, "\t")
		if len(parts) >= 4 {
			containers = append(containers, &ContainerInfo{
				ID:     parts[0],
				Name:   parts[1],
				Image:  parts[2],
				Status: parts[3],
			})
		}
	}

	return containers, nil
}

// GetDefaultImage returns the default Docker image for a database type
func GetDefaultImage(dbType, version string) string {
	if version == "" {
		version = "latest"
	}

	switch dbType {
	case "postgresql", "postgres":
		return fmt.Sprintf("postgres:%s", version)
	case "mysql":
		return fmt.Sprintf("mysql:%s", version)
	case "mariadb":
		return fmt.Sprintf("mariadb:%s", version)
	default:
		return ""
	}
}

// GetDefaultPort returns the default port for a database type
func GetDefaultPort(dbType string) int {
	switch dbType {
	case "postgresql", "postgres":
		return 5432
	case "mysql", "mariadb":
		return 3306
	default:
		return 0
	}
}

// GetDefaultEnvironment returns default environment variables for a database container
func GetDefaultEnvironment(dbType string) map[string]string {
	switch dbType {
	case "postgresql", "postgres":
		return map[string]string{
			"POSTGRES_PASSWORD": "drill_test_password",
			"POSTGRES_USER":     "postgres",
			"POSTGRES_DB":       "postgres",
		}
	case "mysql":
		return map[string]string{
			"MYSQL_ROOT_PASSWORD": "root",
			"MYSQL_DATABASE":      "test",
		}
	case "mariadb":
		return map[string]string{
			"MARIADB_ROOT_PASSWORD": "root",
			"MARIADB_DATABASE":      "test",
		}
	default:
		return map[string]string{}
	}
}
