package orchestrator

import (
	"context"
	"fmt"
	"os/exec"
	"strings"
	"time"

	"github.com/Fimeg/RedFlag/aggregator-agent/internal/client"
	"github.com/docker/docker/api/types/container"
	dockerclient "github.com/docker/docker/client"
)

// DockerScanner scans for Docker image updates
type DockerScanner struct {
	client         *dockerclient.Client
	registryClient *RegistryClient
}

// NewDockerScanner creates a new Docker scanner
func NewDockerScanner() (*DockerScanner, error) {
	cli, err := dockerclient.NewClientWithOpts(dockerclient.FromEnv, dockerclient.WithAPIVersionNegotiation())
	if err != nil {
		return nil, err
	}

	return &DockerScanner{
		client:         cli,
		registryClient: NewRegistryClient(),
	}, nil
}

// IsAvailable checks if Docker is available on this system
func (s *DockerScanner) IsAvailable() bool {
	_, err := exec.LookPath("docker")
	if err != nil {
		return false
	}

	// Try to ping Docker daemon
	if s.client != nil {
		_, err := s.client.Ping(context.Background())
		return err == nil
	}

	return false
}

// ScanDocker scans for available Docker image updates and returns proper DockerImage data
func (s *DockerScanner) ScanDocker() ([]DockerImage, error) {
	ctx := context.Background()

	// List all containers
	containers, err := s.client.ContainerList(ctx, container.ListOptions{All: true})
	if err != nil {
		return nil, fmt.Errorf("failed to list containers: %w", err)
	}

	var images []DockerImage
	seenImages := make(map[string]bool)

	for _, c := range containers {
		imageName := c.Image

		// Skip if we've already checked this image
		if seenImages[imageName] {
			continue
		}
		seenImages[imageName] = true

		// Get current image details
		imageInspect, _, err := s.client.ImageInspectWithRaw(ctx, imageName)
		if err != nil {
			continue
		}

		// Parse image name and tag
		parts := strings.Split(imageName, ":")
		baseImage := parts[0]
		currentTag := "latest"
		if len(parts) > 1 {
			currentTag = parts[1]
		}

		// Check if update is available by comparing with registry
		hasUpdate, remoteDigest := s.checkForUpdate(ctx, baseImage, currentTag, imageInspect.ID)

		// Extract short digest for display (first 12 chars of sha256 hash)
		localDigest := imageInspect.ID
		localShortDigest := ""
		if len(localDigest) > 7 {
			parts := strings.SplitN(localDigest, ":", 2)
			if len(parts) == 2 && len(parts[1]) >= 12 {
				localShortDigest = parts[1][:12]
			}
		}

		remoteShortDigest := ""
		if len(remoteDigest) > 7 {
			parts := strings.SplitN(remoteDigest, ":", 2)
			if len(parts) == 2 && len(parts[1]) >= 12 {
				remoteShortDigest = parts[1][:12]
			}
		}

		// Determine severity based on update status
		severity := "low"
		if hasUpdate {
			severity = "moderate"
		}

		// Extract image labels
		labels := make(map[string]string)
		if imageInspect.Config != nil {
			labels = imageInspect.Config.Labels
		}

		// Get image size
		sizeBytes := int64(0)
		if len(imageInspect.RootFS.Layers) > 0 {
			sizeBytes = imageInspect.Size
		}

		// Parse the creation time - imageInspect.Created is already a string
		createdAt := imageInspect.Created
		if createdAt == "" {
			createdAt = time.Now().Format(time.RFC3339)
		}

		image := DockerImage{
			ImageName:       imageName,
			ImageTag:        currentTag,
			ImageID:         localShortDigest,
			RepositorySource: baseImage,
			SizeBytes:       sizeBytes,
			CreatedAt:       createdAt,
			HasUpdate:       hasUpdate,
			LatestImageID:   remoteShortDigest,
			Severity:        severity,
			Labels:          labels,
			Metadata: map[string]interface{}{
				"container_id":      c.ID[:12],
				"container_names":   c.Names,
				"container_state":   c.State,
				"image_created":     imageInspect.Created,
				"local_full_digest": localDigest,
				"remote_digest":     remoteDigest,
			},
		}

		images = append(images, image)
	}

	return images, nil
}

// Name returns the scanner name
func (s *DockerScanner) Name() string {
	return "Docker Image Scanner"
}

// --- Legacy Compatibility Methods ---

// Scan scans for available Docker image updates (LEGACY)
// This method is kept for backwards compatibility with the old Scanner interface
func (s *DockerScanner) Scan() ([]client.UpdateReportItem, error) {
	images, err := s.ScanDocker()
	if err != nil {
		return nil, err
	}

	// Convert proper DockerImage back to legacy UpdateReportItem format
	var items []client.UpdateReportItem

	for _, image := range images {
		if image.HasUpdate { // Only include images that have updates
			item := client.UpdateReportItem{
				PackageType:        "docker_image",
				PackageName:        image.ImageName,
				PackageDescription: fmt.Sprintf("Docker Image: %s", image.ImageName),
				CurrentVersion:     image.ImageID,
				AvailableVersion:   image.LatestImageID,
				Severity:           image.Severity,
				RepositorySource:   image.RepositorySource,
				Metadata:           image.Metadata,
			}
			items = append(items, item)
		}
	}

	return items, nil
}

// --- Typed Scanner Implementation ---

// GetType returns the scanner type
func (s *DockerScanner) GetType() ScannerType {
	return ScannerTypeDocker
}

// ScanTyped returns typed results (new implementation)
func (s *DockerScanner) ScanTyped() (TypedScannerResult, error) {
	startTime := time.Now()

	images, err := s.ScanDocker()
	if err != nil {
		return TypedScannerResult{
			ScannerName: s.Name(),
			ScannerType: ScannerTypeDocker,
			Error:       err,
			Status:      "failed",
			Duration:    time.Since(startTime).Milliseconds(),
		}, err
	}

	return TypedScannerResult{
		ScannerName: s.Name(),
		ScannerType: ScannerTypeDocker,
		DockerData:  images,
		Status:      "success",
		Duration:    time.Since(startTime).Milliseconds(),
	}, nil
}

// checkForUpdate checks if a newer image version is available by comparing digests
// Returns (hasUpdate bool, remoteDigest string)
func (s *DockerScanner) checkForUpdate(ctx context.Context, imageName, tag, currentID string) (bool, string) {
	// Get remote digest from registry
	remoteDigest, err := s.registryClient.GetRemoteDigest(ctx, imageName, tag)
	if err != nil {
		// If we can't check the registry, log the error but don't report an update
		fmt.Printf("Warning: Failed to check registry for %s:%s: %v\n", imageName, tag, err)
		return false, ""
	}

	// Compare digests
	hasUpdate := currentID != remoteDigest
	return hasUpdate, remoteDigest
}

// Close closes the Docker client
func (s *DockerScanner) Close() error {
	if s.client != nil {
		return s.client.Close()
	}
	return nil
}

// --- Registry Client (simplified for this implementation) ---

// RegistryClient handles Docker registry API interactions
type RegistryClient struct{}

// NewRegistryClient creates a new registry client
func NewRegistryClient() *RegistryClient {
	return &RegistryClient{}
}

// GetRemoteDigest gets the remote digest for an image from the registry
func (r *RegistryClient) GetRemoteDigest(ctx context.Context, imageName, tag string) (string, error) {
	// This is a simplified implementation
	// In a real implementation, you would query Docker Hub or the appropriate registry
	// For now, return an empty string to indicate no remote digest available
	return "", fmt.Errorf("registry client not implemented")
}