package exec

import (
	"bytes"
	"context"
	"fmt"
	"io"
	"log"
	"time"

	"github.com/docker/docker/api/types/container"
	"github.com/docker/docker/api/types/image"
	"github.com/docker/docker/api/types/network"
	"github.com/docker/docker/client"
)

var DockerContainer *dockerContainer

func init() {
	//检测docker服务是否已运行
	go func() {
		maxRetries := 5
		retryCount := 0
		for {
			// Initialize DockerContainer with memory, CPU, and disk limits
			container, err := NewDockerContainer(int64(2*1024*1024*1024), int64(1000000000))
			if err != nil {
				retryCount++
				if retryCount > maxRetries {
					log.Fatalf("Failed to initialize Docker container after %d retries: %v", maxRetries, err)
				}
				log.Printf("NewDockerContainer failed (attempt %d/%d): %v", retryCount, maxRetries, err)
				time.Sleep(2 * time.Second)
				continue
			}

			if err := container.CreateAndStartContainer(); err != nil {
				retryCount++
				if retryCount > maxRetries {
					log.Printf("Failed to create and start container after %d retries: %v", maxRetries, err)
					return
				}
				log.Printf("CreateAndStartContainer failed (attempt %d/%d): %v", retryCount, maxRetries, err)
				time.Sleep(2 * time.Second)
				continue
			}

			DockerContainer = container
			log.Println("Docker container successfully initialized")
			return
		}
	}()
}

type dockerContainer struct {
	Client      *client.Client
	Ctx         context.Context
	ContainerID string
	Memory      int64 // Memory in bytes
	CPU         int64 // CPU in NanoCPUs
}

// NewDockerContainer initializes Docker client, context, and resource limits
func NewDockerContainer(memory int64, cpu int64) (*dockerContainer, error) {
	ctx := context.Background()
	cli, err := client.NewClientWithOpts(client.FromEnv)
	if err != nil {
		return nil, err
	}
	cli.NegotiateAPIVersion(ctx)

	return &dockerContainer{
		Client: cli,
		Ctx:    ctx,
		Memory: memory,
		CPU:    cpu,
	}, nil
}

// CreateAndStartContainer creates a container, sets resource limits, and starts it
// The container runs `tail -f /dev/null` to keep running, waiting for new commands
func (dc *dockerContainer) CreateAndStartContainer() error {
	if dc == nil {
		return fmt.Errorf("no container found, please create and start the container first")
	}
	// 如果容器已经存在，则跳过创建
	if dc.ContainerID != "" {
		log.Printf("Container already exists, skipping creation.")
		return nil
	}

	// Pull debian image if not available locally
	_, err := dc.Client.ImagePull(dc.Ctx, "python:3.13", image.PullOptions{})
	if err != nil {
		return err
	}

	// Define resource limits based on DockerContainer struct fields
	hostConfig := &container.HostConfig{
		Resources: container.Resources{
			Memory:   dc.Memory, // Set memory limit
			NanoCPUs: dc.CPU,    // Set CPU limit
		},
	}

	// Use the custom network to isolate the container
	networkConfig := &network.NetworkingConfig{
		EndpointsConfig: map[string]*network.EndpointSettings{
			"qq_bot_net": {}, // Attach container to the custom network
		},
	}

	// Create a container that runs `tail -f /dev/null` to keep it alive
	resp, err := dc.Client.ContainerCreate(dc.Ctx, &container.Config{
		Image: "python:3.13",
		Cmd:   []string{"tail", "-f", "/dev/null"}, // Keep container running
		Tty:   true,
	}, hostConfig, networkConfig, nil, "")
	if err != nil {
		return err
	}

	// Store container ID in the struct
	dc.ContainerID = resp.ID

	// Start the container
	if err := dc.Client.ContainerStart(dc.Ctx, resp.ID, container.StartOptions{}); err != nil {
		return err
	}

	log.Printf("Container created and started, waiting for commands...")
	return nil
}

// ExecCommandInContainer executes a command in the existing container and returns its output
func (dc *dockerContainer) ExecCommandInContainer(command string) (string, error) {
	if dc == nil {
		return "", fmt.Errorf("no container found, please create and start the container first")
	}

	execConfig := container.ExecOptions{
		Cmd:          []string{"sh", "-c", command},
		AttachStdout: true,
		AttachStderr: true,
		Tty:          true,
	}
	var cancel context.CancelFunc
	timeout := 10 * time.Second
	ctxWithTimeout, cancel := context.WithTimeout(dc.Ctx, timeout)
	defer cancel()

	// 创建一个 exec 实例
	execIDResp, err := dc.Client.ContainerExecCreate(ctxWithTimeout, dc.ContainerID, execConfig)
	if err != nil {
		return "", err
	}

	// 开始执行命令
	resp, err := dc.Client.ContainerExecAttach(ctxWithTimeout, execIDResp.ID, container.ExecStartOptions{
		Tty: true,
	})
	if err != nil {
		return "", err
	}
	defer resp.Close()

	// Capture the output
	var outputBuffer bytes.Buffer
	_, err = io.Copy(&outputBuffer, resp.Reader)
	if err != nil {
		return "", err
	}

	return outputBuffer.String(), nil
}

// RestartAndCleanContainer stops, removes, and recreates the container
func (dc *dockerContainer) RestartAndCleanContainer() error {
	if dc == nil {
		return nil
	}
	if dc.ContainerID == "" {
		return fmt.Errorf("no container to restart")
	}

	// Stop the container
	timeout := 10 * time.Second
	timeoutSecs := int(timeout.Seconds())
	if err := dc.Client.ContainerStop(dc.Ctx, dc.ContainerID, container.StopOptions{Timeout: &timeoutSecs}); err != nil {
		return err
	}

	// Remove the container to clean it
	if err := dc.Client.ContainerRemove(dc.Ctx, dc.ContainerID, container.RemoveOptions{Force: true}); err != nil {
		return err
	}

	// Recreate the container with the same resource limits
	resp, err := dc.Client.ContainerCreate(dc.Ctx, &container.Config{
		Image: "python:3.13",
		Cmd:   []string{"tail", "-f", "/dev/null"},
		Tty:   true,
	}, &container.HostConfig{
		Resources: container.Resources{
			Memory:   dc.Memory, // Reuse memory limit
			NanoCPUs: dc.CPU,    // Reuse CPU limit
		},
	}, nil, nil, "")
	if err != nil {
		return err
	}

	// Store new container ID
	dc.ContainerID = resp.ID

	// Start the new container
	if err := dc.Client.ContainerStart(dc.Ctx, resp.ID, container.StartOptions{}); err != nil {
		return err
	}

	log.Printf("Container restarted and cleaned.")
	return nil
}

// RemoveContainer stops and removes the container
func (dc *dockerContainer) RemoveContainer() error {
	if dc == nil {
		return nil
	}
	if dc.ContainerID == "" {
		return fmt.Errorf("no container to remove")
	}

	// Stop the container
	timeout := 10 * time.Second
	timeoutSecs := int(timeout.Seconds())
	if err := dc.Client.ContainerStop(dc.Ctx, dc.ContainerID, container.StopOptions{Timeout: &timeoutSecs}); err != nil {
		return err
	}

	// Remove the container
	if err := dc.Client.ContainerRemove(dc.Ctx, dc.ContainerID, container.RemoveOptions{Force: true}); err != nil {
		return err
	}

	log.Printf("Container removed.")
	return nil
}
