package docker

import (
	"context"
	"fmt"

	"github.com/moby/moby/client"
)

// Labels applied to all containers managed by Tinyforge.
//
// The legacy tinyforge.project / tinyforge.stage / tinyforge.instance-id
// labels were removed in the workload refactor — the deployer now stamps
// only the workload-shaped labels below at create time.
const (
	LabelManaged      = "tinyforge.managed"       // present on every Tinyforge-managed container
	LabelWorkloadID   = "tinyforge.workload.id"   // workload row primary key
	LabelWorkloadKind = "tinyforge.workload.kind" // 'project' | 'stack' | 'site'
	LabelRole         = "tinyforge.role"          // stage name (project), service name (stack), '' (site)
)

// Client wraps the Docker Engine API client.
type Client struct {
	api client.APIClient
}

// New creates a new Docker client connected to the default Docker socket.
func New() (*Client, error) {
	api, err := client.NewClientWithOpts(
		client.FromEnv,
		client.WithAPIVersionNegotiation(),
	)
	if err != nil {
		return nil, fmt.Errorf("create docker client: %w", err)
	}

	return &Client{api: api}, nil
}

// Close releases resources held by the Docker client.
func (c *Client) Close() error {
	if err := c.api.Close(); err != nil {
		return fmt.Errorf("close docker client: %w", err)
	}
	return nil
}

// Ping checks connectivity to the Docker daemon.
func (c *Client) Ping(ctx context.Context) error {
	_, err := c.api.Ping(ctx, client.PingOptions{})
	if err != nil {
		return fmt.Errorf("ping docker daemon: %w", err)
	}
	return nil
}

// DaemonInfo captures the subset of Docker daemon info surfaced in the UI.
// Fields map directly to /info and /version; JSON tags match the wire format
// consumed by the frontend's DockerHealth type.
type DaemonInfo struct {
	Version         string `json:"version,omitempty"`
	APIVersion      string `json:"api_version,omitempty"`
	OS              string `json:"os,omitempty"`
	Arch            string `json:"arch,omitempty"`
	Kernel          string `json:"kernel,omitempty"`
	OperatingSystem string `json:"operating_system,omitempty"`
	StorageDriver   string `json:"storage_driver,omitempty"`
	RootDir         string `json:"root_dir,omitempty"`
	Name            string `json:"name,omitempty"`
	NCPU            int    `json:"ncpu,omitempty"`
	MemoryTotal     int64  `json:"memory_total,omitempty"`
	Containers      int    `json:"containers,omitempty"`
	Running         int    `json:"running,omitempty"`
	Paused          int    `json:"paused,omitempty"`
	Stopped         int    `json:"stopped,omitempty"`
	Images          int    `json:"images,omitempty"`
}

// Info returns a compact snapshot of daemon health data. Missing pieces
// (e.g. if ServerVersion fails but Info succeeds) are returned as zero
// values rather than bubbling up — the endpoint should degrade gracefully.
func (c *Client) Info(ctx context.Context) (DaemonInfo, error) {
	info, err := c.api.Info(ctx, client.InfoOptions{})
	if err != nil {
		return DaemonInfo{}, fmt.Errorf("docker info: %w", err)
	}
	out := DaemonInfo{
		OperatingSystem: info.Info.OperatingSystem,
		Kernel:          info.Info.KernelVersion,
		Arch:            info.Info.Architecture,
		OS:              info.Info.OSType,
		StorageDriver:   info.Info.Driver,
		RootDir:         info.Info.DockerRootDir,
		Name:            info.Info.Name,
		NCPU:            info.Info.NCPU,
		MemoryTotal:     info.Info.MemTotal,
		Containers:      info.Info.Containers,
		Running:         info.Info.ContainersRunning,
		Paused:          info.Info.ContainersPaused,
		Stopped:         info.Info.ContainersStopped,
		Images:          info.Info.Images,
		Version:         info.Info.ServerVersion,
	}

	if ver, verr := c.api.ServerVersion(ctx, client.ServerVersionOptions{}); verr == nil {
		if ver.Version != "" {
			out.Version = ver.Version
		}
		out.APIVersion = ver.APIVersion
		if out.Arch == "" {
			out.Arch = ver.Arch
		}
		if out.OS == "" {
			out.OS = ver.Os
		}
	}

	return out, nil
}
