package main

import (
	"context"
	"crypto/rand"
	"encoding/hex"
	"fmt"
	"strings"
	"time"

	"github.com/containerd/containerd"
	"github.com/containerd/containerd/errdefs"
	"github.com/containerd/containerd/images"
	"github.com/containerd/containerd/leases"
	"github.com/containerd/containerd/namespaces"
)

// Containerd implements ContainerRuntime interface
type Containerd struct {
	client    *containerd.Client
	ctx       context.Context
	namespace string
}

// NewImageManager creates a new Containerd
func NewImageManager(socketPath, namespace string) (*Containerd, error) {
	client, err := containerd.New(socketPath)
	if err != nil {
		return nil, fmt.Errorf("failed to connect to containerd at %s: %w", socketPath, err)
	}
	client.LeasesService()

	ctx := namespaces.WithNamespace(context.Background(), namespace)

	return &Containerd{
		client:    client,
		ctx:       ctx,
		namespace: namespace,
	}, nil
}

// List returns all images with their pinned status
func (m *Containerd) List() ([]Image, error) {
	// Get all images
	images, err := m.client.ImageService().List(m.ctx)
	if err != nil {
		return nil, fmt.Errorf("failed to list images: %w", err)
	}

	// Get all leases
	leases, err := m.client.LeasesService().List(m.ctx)
	if err != nil {
		return nil, fmt.Errorf("failed to list leases: %w", err)
	}

	// Create a map of image references that are pinned
	pinnedImages := make(map[string]bool)
	for _, lease := range leases {
		// Check if lease has labels referencing an image
		if label, ok := lease.Labels["containerd.io/gc.ref.content.image"]; ok {
			pinnedImages[label] = true
		}
	}

	// Create the result list
	var result []Image
	for _, img := range images {
		result = append(result, Image{
			Name:   img.Name,
			Pinned: pinnedImages[img.Name],
		})
	}

	return result, nil
}

// Pin creates a lease for an image to prevent garbage collection
func (m *Containerd) Pin(imageRef string) error {
	// Create a unique lease ID based on image reference
	leaseID := fmt.Sprintf("pin-%s-%d", generateID(), time.Now().UnixNano())

	// Get the image to validate it exists
	_, err := m.client.ImageService().Get(m.ctx, imageRef)
	if err != nil {
		return fmt.Errorf("failed to get image %s: %w", imageRef, err)
	}

	// Create a new lease
	opts := []leases.Opt{
		leases.WithID(leaseID),
		leases.WithLabels(map[string]string{
			"containerd.io/gc.ref.content.image": imageRef,
		}),
	}

	_, err = m.client.LeasesService().Create(m.ctx, opts...)
	if err != nil {
		return fmt.Errorf("failed to create lease: %w", err)
	}

	return nil
}

// Unpin removes a lease for an image allowing garbage collection
func (m *Containerd) Unpin(imageRef string) error {
	// List all leases
	leaseList, err := m.client.LeasesService().List(m.ctx)
	if err != nil {
		return fmt.Errorf("failed to list leases: %w", err)
	}

	// Find leases that reference our image
	var foundLease bool
	for _, lease := range leaseList {
		// Check if this lease has a label referencing our image
		if label, ok := lease.Labels["containerd.io/gc.ref.content.image"]; ok && label == imageRef {
			// Delete this lease
			if err := m.client.LeasesService().Delete(m.ctx, lease); err != nil {
				return fmt.Errorf("failed to delete lease %s: %w", lease.ID, err)
			}
			foundLease = true
		}
	}

	if !foundLease {
		return fmt.Errorf("no lease found for image %s", imageRef)
	}

	return nil
}

// Pull pulls an image from a registry
func (m *Containerd) Pull(imageRef string) error {
	// Set up pull options
	pullOpts := []containerd.RemoteOpt{
		containerd.WithPlatform(""),
		containerd.WithPullUnpack,
	}

	// Pull the image
	_, err := m.client.Pull(m.ctx, imageRef, pullOpts...)
	if err != nil {
		return fmt.Errorf("failed to pull image %s: %w", imageRef, err)
	}

	return nil
}

// Remove deletes an image
func (m *Containerd) Remove(imageRef string) error {
	// Get the image

	_, err := m.client.ImageService().Get(m.ctx, imageRef)
	if err != nil {
		if errdefs.IsNotFound(err) {
			return fmt.Errorf("image %s not found", imageRef)
		}
		return fmt.Errorf("failed to get image %s: %w", imageRef, err)
	}

	// Delete the image
	err = m.client.ImageService().Delete(m.ctx, imageRef, images.SynchronousDelete())
	if err != nil {
		return fmt.Errorf("failed to delete image %s: %w", imageRef, err)
	}

	return nil
}

// Close closes the containerd client connection
func (m *Containerd) Close() error {
	return m.client.Close()
}

// generateID creates a random unique ID
func generateID() string {
	b := make([]byte, 16)
	if _, err := rand.Read(b); err != nil {
		// Fall back to a timestamp-based ID in the unlikely event of rand failure
		return strings.Replace(fmt.Sprintf("%d", time.Now().UnixNano()), "-", "", -1)
	}
	return hex.EncodeToString(b)
}
