package particle_os

import (
	"encoding/json"
	"fmt"
	"os"
	"os/exec"
	"path/filepath"
	"time"

	"github.com/sirupsen/logrus"
)

// ContainerInfo represents information about a container image
type ContainerInfo struct {
	ID       string            `json:"id"`
	Digest   string            `json:"digest"`
	Size     int64             `json:"size"`
	Created  string            `json:"created"`
	Labels   map[string]string `json:"labels"`
	Arch     string            `json:"arch"`
	OS       string            `json:"os"`
	Variant  string            `json:"variant"`
	Layers   []string          `json:"layers"`
}

// ContainerProcessor handles container image operations
type ContainerProcessor struct {
	logger    *logrus.Logger
	workDir   string
	usePodman bool
}

// NewContainerProcessor creates a new container processor
func NewContainerProcessor(workDir string, logLevel logrus.Level) *ContainerProcessor {
	// Check if podman is available, otherwise use docker
	usePodman := true
	if _, err := exec.LookPath("podman"); err != nil {
		usePodman = false
	}

	logger := logrus.New()
	logger.SetLevel(logLevel)

	return &ContainerProcessor{
		logger:    logger,
		workDir:   workDir,
		usePodman: usePodman,
	}
}

// ExtractContainer extracts a container image to the filesystem
func (cp *ContainerProcessor) ExtractContainer(imageRef, targetDir string) error {
	cp.logger.Infof("Extracting container: %s to %s", imageRef, targetDir)

	// Create target directory (clean up if it exists)
	cp.logger.Infof("Cleaning up existing target directory: %s", targetDir)
	cleanupCmd := exec.Command("sudo", "rm", "-rf", targetDir)
	if err := cleanupCmd.Run(); err != nil {
		cp.logger.Warnf("Failed to remove existing target directory: %v", err)
	}
	if err := os.MkdirAll(targetDir, 0755); err != nil {
		return fmt.Errorf("failed to create target directory: %w", err)
	}

	// Pull the image first
	if err := cp.pullImage(imageRef); err != nil {
		return fmt.Errorf("failed to pull image: %w", err)
	}

	// Extract the image
	if err := cp.extractImage(imageRef, targetDir); err != nil {
		return fmt.Errorf("failed to extract image: %w", err)
	}

	cp.logger.Info("Container extraction completed successfully")
	return nil
}

// GetContainerInfo gets information about a container image
func (cp *ContainerProcessor) GetContainerInfo(imageRef string) (*ContainerInfo, error) {
	cp.logger.Infof("Getting container info for: %s", imageRef)

	// Pull the image first
	if err := cp.pullImage(imageRef); err != nil {
		return nil, fmt.Errorf("failed to pull image: %w", err)
	}

	// Inspect the image
	info, err := cp.inspectImage(imageRef)
	if err != nil {
		return nil, fmt.Errorf("failed to inspect image: %w", err)
	}

	return info, nil
}

// pullImage pulls a container image
func (cp *ContainerProcessor) pullImage(imageRef string) error {
	cp.logger.Infof("Pulling image: %s", imageRef)

	var cmd *exec.Cmd
	if cp.usePodman {
		cmd = exec.Command("podman", "pull", imageRef)
	} else {
		cmd = exec.Command("docker", "pull", imageRef)
	}

	output, err := cmd.CombinedOutput()
	if err != nil {
		return fmt.Errorf("failed to pull image: %s, output: %s", err, string(output))
	}

	cp.logger.Info("Image pulled successfully")
	return nil
}

// extractImage extracts a container image to the filesystem
func (cp *ContainerProcessor) extractImage(imageRef, targetDir string) error {
	cp.logger.Infof("Extracting image: %s to %s", imageRef, targetDir)

	// Create a temporary container with shorter name
	containerName := fmt.Sprintf("po-%d", time.Now().Unix())
	cp.logger.Infof("Using container name: %s", containerName)
	
	// Clean up any existing container with the same name
	cp.logger.Infof("Cleaning up any existing container: %s", containerName)
	var cleanupCmd *exec.Cmd
	if cp.usePodman {
		cleanupCmd = exec.Command("podman", "rm", containerName)
	} else {
		cleanupCmd = exec.Command("docker", "rm", containerName)
	}
	cleanupCmd.Run() // Ignore errors for cleanup
	
	var createCmd *exec.Cmd
	if cp.usePodman {
		createCmd = exec.Command("podman", "create", "--name", containerName, imageRef)
	} else {
		createCmd = exec.Command("docker", "create", "--name", containerName, imageRef)
	}

	cp.logger.Infof("Creating container with command: %v", createCmd.Args)
	createOutput, err := createCmd.CombinedOutput()
	if err != nil {
		cp.logger.Errorf("Container creation failed: %v, output: %s", err, string(createOutput))
		return fmt.Errorf("failed to create container: %w, output: %s", err, string(createOutput))
	}
	cp.logger.Infof("Container created successfully: %s", containerName)

	// Extract the container filesystem
	var extractCmd *exec.Cmd
	if cp.usePodman {
		extractCmd = exec.Command("podman", "export", containerName)
	} else {
		extractCmd = exec.Command("docker", "export", containerName)
	}

	cp.logger.Infof("Exporting container with command: %v", extractCmd.Args)

	// Create tar file
	tarFile := filepath.Join(cp.workDir, "container.tar")
	cp.logger.Infof("Creating tar file: %s", tarFile)
	
	// Use a pipe to avoid Stdout conflicts
	extractCmd.Stdout = nil // Reset Stdout
	extractOutput, err := extractCmd.Output()
	if err != nil {
		cp.logger.Errorf("Container export failed: %v", err)
		return fmt.Errorf("failed to export container: %w", err)
	}
	
	// Write the output to the tar file
	if err := os.WriteFile(tarFile, extractOutput, 0644); err != nil {
		cp.logger.Errorf("Failed to write tar file: %v", err)
		return fmt.Errorf("failed to write tar file: %w", err)
	}
	
	cp.logger.Info("Container export completed successfully")

	// Extract tar to target directory
	cp.logger.Infof("Extracting tar file %s to %s", tarFile, targetDir)
	untarCmd := exec.Command("tar", "-xf", tarFile, "-C", targetDir)
	cp.logger.Infof("Running tar command: %v", untarCmd.Args)
	untarOutput, err := untarCmd.CombinedOutput()
	if err != nil {
		cp.logger.Errorf("Tar extraction failed: %v, output: %s", err, string(untarOutput))
		return fmt.Errorf("failed to extract tar: %w, output: %s", err, string(untarOutput))
	}
	cp.logger.Info("Tar extraction completed successfully")

	// Fix ownership to root:root for chroot operations
	cp.logger.Info("Fixing ownership for chroot operations")
	chownCmd := exec.Command("sudo", "chown", "-R", "root:root", targetDir)
	chownOutput, err := chownCmd.CombinedOutput()
	if err != nil {
		cp.logger.Warnf("Failed to fix ownership: %v, output: %s", err, string(chownOutput))
	} else {
		cp.logger.Info("Ownership fixed successfully")
	}
	
	// Fix permissions for /tmp and other critical directories
	cp.logger.Info("Fixing permissions for critical directories")
	chmodCmd := exec.Command("sudo", "chmod", "-R", "1777", filepath.Join(targetDir, "tmp"))
	if err := chmodCmd.Run(); err != nil {
		cp.logger.Warnf("Failed to fix tmp permissions: %v", err)
	}
	
	chmodCmd = exec.Command("sudo", "chmod", "-R", "1777", filepath.Join(targetDir, "var", "tmp"))
	if err := chmodCmd.Run(); err != nil {
		cp.logger.Warnf("Failed to fix var/tmp permissions: %v", err)
	}

	// Clean up
	var rmCmd *exec.Cmd
	if cp.usePodman {
		rmCmd = exec.Command("podman", "rm", containerName)
	} else {
		rmCmd = exec.Command("docker", "rm", containerName)
	}
	rmOutput, err := rmCmd.CombinedOutput()
	if err != nil {
		cp.logger.Warnf("Failed to cleanup container: %v, output: %s", err, string(rmOutput))
	} else {
		cp.logger.Info("Container cleanup completed")
	}

	// Remove tar file
	if err := os.Remove(tarFile); err != nil {
		cp.logger.Warnf("Failed to remove tar file: %v", err)
	} else {
		cp.logger.Info("Tar file cleanup completed")
	}

	cp.logger.Info("Image extraction completed")
	return nil
}

// inspectImage inspects a container image and returns its information
func (cp *ContainerProcessor) inspectImage(imageRef string) (*ContainerInfo, error) {
	cp.logger.Infof("Inspecting image: %s", imageRef)

	var cmd *exec.Cmd
	if cp.usePodman {
		cmd = exec.Command("podman", "inspect", imageRef)
	} else {
		cmd = exec.Command("docker", "inspect", imageRef)
	}

	output, err := cmd.Output()
	if err != nil {
		return nil, fmt.Errorf("failed to inspect image: %w", err)
	}

	// Parse the inspect output (it's an array, so we take the first element)
	var inspectResults []map[string]interface{}
	if err := json.Unmarshal(output, &inspectResults); err != nil {
		return nil, fmt.Errorf("failed to parse inspect output: %w", err)
	}

	if len(inspectResults) == 0 {
		return nil, fmt.Errorf("no inspect results found")
	}

	inspect := inspectResults[0]

	// Extract relevant information
	info := &ContainerInfo{
		ID:      getString(inspect, "Id"),
		Digest:  getString(inspect, "Digest"),
		Created: getString(inspect, "Created"),
		Labels:  make(map[string]string),
	}

	// Get size
	if size, ok := inspect["Size"].(float64); ok {
		info.Size = int64(size)
	}

	// Get architecture and OS
	if config, ok := inspect["Architecture"].(string); ok {
		info.Arch = config
	}
	if os, ok := inspect["Os"].(string); ok {
		info.OS = os
	}

	// Get labels
	if labels, ok := inspect["Labels"].(map[string]interface{}); ok {
		for k, v := range labels {
			if str, ok := v.(string); ok {
				info.Labels[k] = str
			}
		}
	}

	// Get layers
	if layers, ok := inspect["Layers"].([]interface{}); ok {
		for _, layer := range layers {
			if layerStr, ok := layer.(string); ok {
				info.Layers = append(info.Layers, layerStr)
			}
		}
	}

	return info, nil
}

// getString safely extracts a string value from a map
func getString(m map[string]interface{}, key string) string {
	if val, ok := m[key].(string); ok {
		return val
	}
	return ""
}

// GetContainerRuntime returns the container runtime being used
func (cp *ContainerProcessor) GetContainerRuntime() string {
	if cp.usePodman {
		return "podman"
	}
	return "docker"
}

// IsAvailable checks if the required container runtime is available
func (cp *ContainerProcessor) IsAvailable() bool {
	if cp.usePodman {
		_, err := exec.LookPath("podman")
		return err == nil
	}
	_, err := exec.LookPath("docker")
	return err == nil
}
