package containerd

import (
	"github.com/docker/docker/api/types/container"
	"github.com/docker/docker/dockerversion"
	"github.com/docker/docker/image"
	"github.com/docker/docker/layer"
	"github.com/docker/go-connections/nat"
	imagespec "github.com/moby/docker-image-spec/specs-go/v1"
	"github.com/opencontainers/go-digest"
	ocispec "github.com/opencontainers/image-spec/specs-go/v1"
)

// dockerOciImageToDockerImagePartial creates an image.Image from the imagespec.DockerOCIImage
// It doesn't set:
// - V1Image.ContainerConfig
// - V1Image.Container
// - Details
func dockerOciImageToDockerImagePartial(id image.ID, img imagespec.DockerOCIImage) *image.Image {
	v1Image := image.V1Image{
		DockerVersion: dockerversion.Version,
		Config:        dockerOCIImageConfigToContainerConfig(img.Config),
		Architecture:  img.Platform.Architecture,
		Variant:       img.Platform.Variant,
		OS:            img.Platform.OS,
		Author:        img.Author,
		Created:       img.Created,
	}

	rootFS := &image.RootFS{
		Type: img.RootFS.Type,
	}
	for _, diffId := range img.RootFS.DiffIDs {
		rootFS.DiffIDs = append(rootFS.DiffIDs, layer.DiffID(diffId))
	}

	out := image.NewImage(id)
	out.V1Image = v1Image
	out.RootFS = rootFS
	out.History = img.History
	out.OSFeatures = img.OSFeatures
	out.OSVersion = img.OSVersion
	return out
}

func dockerImageToDockerOCIImage(img image.Image) imagespec.DockerOCIImage {
	rootfs := ocispec.RootFS{
		Type:    img.RootFS.Type,
		DiffIDs: []digest.Digest{},
	}
	for _, diffId := range img.RootFS.DiffIDs {
		rootfs.DiffIDs = append(rootfs.DiffIDs, digest.Digest(diffId))
	}

	return imagespec.DockerOCIImage{
		Image: ocispec.Image{
			Created: img.Created,
			Author:  img.Author,
			Platform: ocispec.Platform{
				Architecture: img.Architecture,
				Variant:      img.Variant,
				OS:           img.OS,
				OSVersion:    img.OSVersion,
				OSFeatures:   img.OSFeatures,
			},
			RootFS:  rootfs,
			History: img.History,
		},
		Config: containerConfigToDockerOCIImageConfig(img.Config),
	}
}

func containerConfigToDockerOCIImageConfig(cfg *container.Config) imagespec.DockerOCIImageConfig {
	var ociCfg ocispec.ImageConfig
	var ext imagespec.DockerOCIImageConfigExt

	if cfg != nil {
		ociCfg = ocispec.ImageConfig{
			User:        cfg.User,
			Env:         cfg.Env,
			Entrypoint:  cfg.Entrypoint,
			Cmd:         cfg.Cmd,
			Volumes:     cfg.Volumes,
			WorkingDir:  cfg.WorkingDir,
			Labels:      cfg.Labels,
			StopSignal:  cfg.StopSignal,
			ArgsEscaped: cfg.ArgsEscaped, //nolint:staticcheck // Ignore SA1019. Need to keep it in image.
		}

		if len(cfg.ExposedPorts) > 0 {
			ociCfg.ExposedPorts = map[string]struct{}{}
			for k, v := range cfg.ExposedPorts {
				ociCfg.ExposedPorts[string(k)] = v
			}
		}
		ext.Healthcheck = cfg.Healthcheck
		ext.OnBuild = cfg.OnBuild
		ext.Shell = cfg.Shell
	}

	return imagespec.DockerOCIImageConfig{
		ImageConfig:             ociCfg,
		DockerOCIImageConfigExt: ext,
	}
}

func dockerOCIImageConfigToContainerConfig(cfg imagespec.DockerOCIImageConfig) *container.Config {
	exposedPorts := make(nat.PortSet, len(cfg.ExposedPorts))
	for k, v := range cfg.ExposedPorts {
		exposedPorts[nat.Port(k)] = v
	}

	return &container.Config{
		Entrypoint:   cfg.Entrypoint,
		Env:          cfg.Env,
		Cmd:          cfg.Cmd,
		User:         cfg.User,
		WorkingDir:   cfg.WorkingDir,
		ExposedPorts: exposedPorts,
		Volumes:      cfg.Volumes,
		Labels:       cfg.Labels,
		ArgsEscaped:  cfg.ArgsEscaped, //nolint:staticcheck // Ignore SA1019. Need to keep it in image.
		StopSignal:   cfg.StopSignal,
		Healthcheck:  cfg.Healthcheck,
		OnBuild:      cfg.OnBuild,
		Shell:        cfg.Shell,
	}
}
