package php

import (
	"context"
	"io"
	"os"
	"os/exec"
	"path/filepath"
	"strings"

	"github.com/host-uk/core/pkg/cli"
)

// DockerBuildOptions configures Docker image building for PHP projects.
type DockerBuildOptions struct {
	// ProjectDir is the path to the PHP/Laravel project.
	ProjectDir string

	// ImageName is the name for the Docker image.
	ImageName string

	// Tag is the image tag (default: "latest").
	Tag string

	// Platform specifies the target platform (e.g., "linux/amd64", "linux/arm64").
	Platform string

	// Dockerfile is the path to a custom Dockerfile.
	// If empty, one will be auto-generated for FrankenPHP.
	Dockerfile string

	// NoBuildCache disables Docker build cache.
	NoBuildCache bool

	// BuildArgs are additional build arguments.
	BuildArgs map[string]string

	// Output is the writer for build output (default: os.Stdout).
	Output io.Writer
}

// LinuxKitBuildOptions configures LinuxKit image building for PHP projects.
type LinuxKitBuildOptions struct {
	// ProjectDir is the path to the PHP/Laravel project.
	ProjectDir string

	// OutputPath is the path for the output image.
	OutputPath string

	// Format is the output format: "iso", "qcow2", "raw", "vmdk".
	Format string

	// Template is the LinuxKit template name (default: "server-php").
	Template string

	// Variables are template variables to apply.
	Variables map[string]string

	// Output is the writer for build output (default: os.Stdout).
	Output io.Writer
}

// ServeOptions configures running a production PHP container.
type ServeOptions struct {
	// ImageName is the Docker image to run.
	ImageName string

	// Tag is the image tag (default: "latest").
	Tag string

	// ContainerName is the name for the container.
	ContainerName string

	// Port is the host port to bind (default: 80).
	Port int

	// HTTPSPort is the host HTTPS port to bind (default: 443).
	HTTPSPort int

	// Detach runs the container in detached mode.
	Detach bool

	// EnvFile is the path to an environment file.
	EnvFile string

	// Volumes maps host paths to container paths.
	Volumes map[string]string

	// Output is the writer for output (default: os.Stdout).
	Output io.Writer
}

// BuildDocker builds a Docker image for the PHP project.
func BuildDocker(ctx context.Context, opts DockerBuildOptions) error {
	if opts.ProjectDir == "" {
		cwd, err := os.Getwd()
		if err != nil {
			return cli.WrapVerb(err, "get", "working directory")
		}
		opts.ProjectDir = cwd
	}

	// Validate project directory
	if !IsPHPProject(opts.ProjectDir) {
		return cli.Err("not a PHP project: %s (missing composer.json)", opts.ProjectDir)
	}

	// Set defaults
	if opts.ImageName == "" {
		opts.ImageName = filepath.Base(opts.ProjectDir)
	}
	if opts.Tag == "" {
		opts.Tag = "latest"
	}
	if opts.Output == nil {
		opts.Output = os.Stdout
	}

	// Determine Dockerfile path
	dockerfilePath := opts.Dockerfile
	var tempDockerfile string

	if dockerfilePath == "" {
		// Generate Dockerfile
		content, err := GenerateDockerfile(opts.ProjectDir)
		if err != nil {
			return cli.WrapVerb(err, "generate", "Dockerfile")
		}

		// Write to temporary file
		m := getMedium()
		tempDockerfile = filepath.Join(opts.ProjectDir, "Dockerfile.core-generated")
		if err := m.Write(tempDockerfile, content); err != nil {
			return cli.WrapVerb(err, "write", "Dockerfile")
		}
		defer func() { _ = m.Delete(tempDockerfile) }()

		dockerfilePath = tempDockerfile
	}

	// Build Docker image
	imageRef := cli.Sprintf("%s:%s", opts.ImageName, opts.Tag)

	args := []string{"build", "-t", imageRef, "-f", dockerfilePath}

	if opts.Platform != "" {
		args = append(args, "--platform", opts.Platform)
	}

	if opts.NoBuildCache {
		args = append(args, "--no-cache")
	}

	for key, value := range opts.BuildArgs {
		args = append(args, "--build-arg", cli.Sprintf("%s=%s", key, value))
	}

	args = append(args, opts.ProjectDir)

	cmd := exec.CommandContext(ctx, "docker", args...)
	cmd.Dir = opts.ProjectDir
	cmd.Stdout = opts.Output
	cmd.Stderr = opts.Output

	if err := cmd.Run(); err != nil {
		return cli.Wrap(err, "docker build failed")
	}

	return nil
}

// BuildLinuxKit builds a LinuxKit image for the PHP project.
func BuildLinuxKit(ctx context.Context, opts LinuxKitBuildOptions) error {
	if opts.ProjectDir == "" {
		cwd, err := os.Getwd()
		if err != nil {
			return cli.WrapVerb(err, "get", "working directory")
		}
		opts.ProjectDir = cwd
	}

	// Validate project directory
	if !IsPHPProject(opts.ProjectDir) {
		return cli.Err("not a PHP project: %s (missing composer.json)", opts.ProjectDir)
	}

	// Set defaults
	if opts.Template == "" {
		opts.Template = "server-php"
	}
	if opts.Format == "" {
		opts.Format = "qcow2"
	}
	if opts.OutputPath == "" {
		opts.OutputPath = filepath.Join(opts.ProjectDir, "dist", filepath.Base(opts.ProjectDir))
	}
	if opts.Output == nil {
		opts.Output = os.Stdout
	}

	// Ensure output directory exists
	m := getMedium()
	outputDir := filepath.Dir(opts.OutputPath)
	if err := m.EnsureDir(outputDir); err != nil {
		return cli.WrapVerb(err, "create", "output directory")
	}

	// Find linuxkit binary
	linuxkitPath, err := lookupLinuxKit()
	if err != nil {
		return err
	}

	// Get template content
	templateContent, err := getLinuxKitTemplate(opts.Template)
	if err != nil {
		return cli.WrapVerb(err, "get", "template")
	}

	// Apply variables
	if opts.Variables == nil {
		opts.Variables = make(map[string]string)
	}
	// Add project-specific variables
	opts.Variables["PROJECT_DIR"] = opts.ProjectDir
	opts.Variables["PROJECT_NAME"] = filepath.Base(opts.ProjectDir)

	content, err := applyTemplateVariables(templateContent, opts.Variables)
	if err != nil {
		return cli.WrapVerb(err, "apply", "template variables")
	}

	// Write template to temp file
	tempYAML := filepath.Join(opts.ProjectDir, ".core-linuxkit.yml")
	if err := m.Write(tempYAML, content); err != nil {
		return cli.WrapVerb(err, "write", "template")
	}
	defer func() { _ = m.Delete(tempYAML) }()

	// Build LinuxKit image
	args := []string{
		"build",
		"--format", opts.Format,
		"--name", opts.OutputPath,
		tempYAML,
	}

	cmd := exec.CommandContext(ctx, linuxkitPath, args...)
	cmd.Dir = opts.ProjectDir
	cmd.Stdout = opts.Output
	cmd.Stderr = opts.Output

	if err := cmd.Run(); err != nil {
		return cli.Wrap(err, "linuxkit build failed")
	}

	return nil
}

// ServeProduction runs a production PHP container.
func ServeProduction(ctx context.Context, opts ServeOptions) error {
	if opts.ImageName == "" {
		return cli.Err("image name is required")
	}

	// Set defaults
	if opts.Tag == "" {
		opts.Tag = "latest"
	}
	if opts.Port == 0 {
		opts.Port = 80
	}
	if opts.HTTPSPort == 0 {
		opts.HTTPSPort = 443
	}
	if opts.Output == nil {
		opts.Output = os.Stdout
	}

	imageRef := cli.Sprintf("%s:%s", opts.ImageName, opts.Tag)

	args := []string{"run"}

	if opts.Detach {
		args = append(args, "-d")
	} else {
		args = append(args, "--rm")
	}

	if opts.ContainerName != "" {
		args = append(args, "--name", opts.ContainerName)
	}

	// Port mappings
	args = append(args, "-p", cli.Sprintf("%d:80", opts.Port))
	args = append(args, "-p", cli.Sprintf("%d:443", opts.HTTPSPort))

	// Environment file
	if opts.EnvFile != "" {
		args = append(args, "--env-file", opts.EnvFile)
	}

	// Volume mounts
	for hostPath, containerPath := range opts.Volumes {
		args = append(args, "-v", cli.Sprintf("%s:%s", hostPath, containerPath))
	}

	args = append(args, imageRef)

	cmd := exec.CommandContext(ctx, "docker", args...)
	cmd.Stdout = opts.Output
	cmd.Stderr = opts.Output

	if opts.Detach {
		output, err := cmd.Output()
		if err != nil {
			return cli.WrapVerb(err, "start", "container")
		}
		containerID := strings.TrimSpace(string(output))
		cli.Print("Container started: %s\n", containerID[:12])
		return nil
	}

	return cmd.Run()
}

// Shell opens a shell in a running container.
func Shell(ctx context.Context, containerID string) error {
	if containerID == "" {
		return cli.Err("container ID is required")
	}

	// Resolve partial container ID
	fullID, err := resolveDockerContainerID(ctx, containerID)
	if err != nil {
		return err
	}

	cmd := exec.CommandContext(ctx, "docker", "exec", "-it", fullID, "/bin/sh")
	cmd.Stdin = os.Stdin
	cmd.Stdout = os.Stdout
	cmd.Stderr = os.Stderr

	return cmd.Run()
}

// IsPHPProject checks if the given directory is a PHP project.
func IsPHPProject(dir string) bool {
	composerPath := filepath.Join(dir, "composer.json")
	return getMedium().IsFile(composerPath)
}

// commonLinuxKitPaths defines default search locations for linuxkit.
var commonLinuxKitPaths = []string{
	"/usr/local/bin/linuxkit",
	"/opt/homebrew/bin/linuxkit",
}

// lookupLinuxKit finds the linuxkit binary.
func lookupLinuxKit() (string, error) {
	// Check PATH first
	if path, err := exec.LookPath("linuxkit"); err == nil {
		return path, nil
	}

	m := getMedium()
	for _, p := range commonLinuxKitPaths {
		if m.IsFile(p) {
			return p, nil
		}
	}

	return "", cli.Err("linuxkit not found. Install with: brew install linuxkit (macOS) or see https://github.com/linuxkit/linuxkit")
}

// getLinuxKitTemplate retrieves a LinuxKit template by name.
func getLinuxKitTemplate(name string) (string, error) {
	// Default server-php template for PHP projects
	if name == "server-php" {
		return defaultServerPHPTemplate, nil
	}

	// Try to load from container package templates
	// This would integrate with github.com/host-uk/core/pkg/container
	return "", cli.Err("template not found: %s", name)
}

// applyTemplateVariables applies variable substitution to template content.
func applyTemplateVariables(content string, vars map[string]string) (string, error) {
	result := content
	for key, value := range vars {
		placeholder := "${" + key + "}"
		result = strings.ReplaceAll(result, placeholder, value)
	}
	return result, nil
}

// resolveDockerContainerID resolves a partial container ID to a full ID.
func resolveDockerContainerID(ctx context.Context, partialID string) (string, error) {
	cmd := exec.CommandContext(ctx, "docker", "ps", "-a", "--no-trunc", "--format", "{{.ID}}")
	output, err := cmd.Output()
	if err != nil {
		return "", cli.WrapVerb(err, "list", "containers")
	}

	lines := strings.Split(strings.TrimSpace(string(output)), "\n")
	var matches []string

	for _, line := range lines {
		if strings.HasPrefix(line, partialID) {
			matches = append(matches, line)
		}
	}

	switch len(matches) {
	case 0:
		return "", cli.Err("no container found matching: %s", partialID)
	case 1:
		return matches[0], nil
	default:
		return "", cli.Err("multiple containers match '%s', be more specific", partialID)
	}
}

// defaultServerPHPTemplate is the default LinuxKit template for PHP servers.
const defaultServerPHPTemplate = `# LinuxKit configuration for PHP/FrankenPHP server
kernel:
  image: linuxkit/kernel:6.6.13
  cmdline: "console=tty0 console=ttyS0"
init:
  - linuxkit/init:v1.0.1
  - linuxkit/runc:v1.0.1
  - linuxkit/containerd:v1.0.1
onboot:
  - name: sysctl
    image: linuxkit/sysctl:v1.0.1
  - name: dhcpcd
    image: linuxkit/dhcpcd:v1.0.1
    command: ["/sbin/dhcpcd", "--nobackground", "-f", "/dhcpcd.conf"]
services:
  - name: getty
    image: linuxkit/getty:v1.0.1
    env:
      - INSECURE=true
  - name: sshd
    image: linuxkit/sshd:v1.0.1
files:
  - path: etc/ssh/authorized_keys
    contents: |
      ${SSH_KEY:-}
`
