// Package container provides supply chain security for container images.
package container

import (
	"context"
	"crypto/sha256"
	"encoding/hex"
	"encoding/json"
	"fmt"
	"os"
	"os/exec"
	"path/filepath"
	"strings"
	"time"
)

// ImageSigningConfig holds image signing configuration
type ImageSigningConfig struct {
	Enabled       bool   `json:"enabled"`
	KeyID         string `json:"key_id"`
	PublicKeyPath string `json:"public_key_path"`
	Required      bool   `json:"required"` // Fail if signature invalid
}

// VulnerabilityScanConfig holds vulnerability scanning configuration
type VulnerabilityScanConfig struct {
	Enabled           bool     `json:"enabled"`
	Scanner           string   `json:"scanner"`            // "trivy", "clair", "snyk"
	SeverityThreshold string   `json:"severity_threshold"` // "low", "medium", "high", "critical"
	FailOnVuln        bool     `json:"fail_on_vuln"`
	IgnoredCVEs       []string `json:"ignored_cves"`
}

// SBOMConfig holds SBOM generation configuration
type SBOMConfig struct {
	Enabled    bool   `json:"enabled"`
	Format     string `json:"format"` // "cyclonedx", "spdx"
	OutputPath string `json:"output_path"`
}

// SupplyChainPolicy defines supply chain security requirements
type SupplyChainPolicy struct {
	ImageSigning       ImageSigningConfig      `json:"image_signing"`
	VulnScanning       VulnerabilityScanConfig `json:"vulnerability_scanning"`
	SBOM               SBOMConfig              `json:"sbom"`
	AllowedRegistries  []string                `json:"allowed_registries"`
	ProhibitedPackages []string                `json:"prohibited_packages"`
}

// DefaultSupplyChainPolicy returns default supply chain policy
func DefaultSupplyChainPolicy() *SupplyChainPolicy {
	return &SupplyChainPolicy{
		ImageSigning: ImageSigningConfig{
			Enabled:       true,
			Required:      true,
			PublicKeyPath: "/etc/fetchml/signing-keys",
		},
		VulnScanning: VulnerabilityScanConfig{
			Enabled:           true,
			Scanner:           "trivy",
			SeverityThreshold: "high",
			FailOnVuln:        true,
			IgnoredCVEs:       []string{},
		},
		SBOM: SBOMConfig{
			Enabled:    true,
			Format:     "cyclonedx",
			OutputPath: "/var/lib/fetchml/sboms",
		},
		AllowedRegistries: []string{
			"registry.example.com",
			"ghcr.io",
			"gcr.io",
		},
		ProhibitedPackages: []string{
			"curl", // Example: require wget instead for consistency
		},
	}
}

// SupplyChainSecurity provides supply chain security enforcement
type SupplyChainSecurity struct {
	policy *SupplyChainPolicy
}

// NewSupplyChainSecurity creates a new supply chain security enforcer
func NewSupplyChainSecurity(policy *SupplyChainPolicy) *SupplyChainSecurity {
	if policy == nil {
		policy = DefaultSupplyChainPolicy()
	}
	return &SupplyChainSecurity{policy: policy}
}

// ValidateImage performs full supply chain validation on an image
func (s *SupplyChainSecurity) ValidateImage(ctx context.Context, imageRef string) (*ValidationReport, error) {
	report := &ValidationReport{
		ImageRef:    imageRef,
		ValidatedAt: time.Now().UTC(),
		Checks:      make(map[string]CheckResult),
	}

	// Check 1: Registry allowlist
	if result := s.checkRegistry(imageRef); result.Passed {
		report.Checks["registry_allowlist"] = result
	} else {
		report.Checks["registry_allowlist"] = result
		report.Passed = false
		if s.policy.ImageSigning.Required {
			return report, fmt.Errorf("registry validation failed: %s", result.Message)
		}
	}

	// Check 2: Image signature
	if s.policy.ImageSigning.Enabled {
		if result := s.verifySignature(ctx, imageRef); result.Passed {
			report.Checks["signature"] = result
		} else {
			report.Checks["signature"] = result
			report.Passed = false
			if s.policy.ImageSigning.Required {
				return report, fmt.Errorf("signature verification failed: %s", result.Message)
			}
		}
	}

	// Check 3: Vulnerability scan
	if s.policy.VulnScanning.Enabled {
		if result := s.scanVulnerabilities(ctx, imageRef); result.Passed {
			report.Checks["vulnerability_scan"] = result
		} else {
			report.Checks["vulnerability_scan"] = result
			report.Passed = false
			if s.policy.VulnScanning.FailOnVuln {
				return report, fmt.Errorf("vulnerability scan failed: %s", result.Message)
			}
		}
	}

	// Check 4: Prohibited packages
	if result := s.checkProhibitedPackages(ctx, imageRef); result.Passed {
		report.Checks["prohibited_packages"] = result
	} else {
		report.Checks["prohibited_packages"] = result
		report.Passed = false
	}

	// Generate SBOM if enabled
	if s.policy.SBOM.Enabled {
		if sbom, err := s.generateSBOM(ctx, imageRef); err == nil {
			report.SBOM = sbom
		}
	}

	report.Passed = true
	for _, check := range report.Checks {
		if !check.Passed && check.Required {
			report.Passed = false
			break
		}
	}

	return report, nil
}

// ValidationReport contains validation results
type ValidationReport struct {
	ImageRef    string                 `json:"image_ref"`
	ValidatedAt time.Time              `json:"validated_at"`
	Passed      bool                   `json:"passed"`
	Checks      map[string]CheckResult `json:"checks"`
	SBOM        *SBOMReport            `json:"sbom,omitempty"`
}

// CheckResult represents a single validation check result
type CheckResult struct {
	Passed   bool   `json:"passed"`
	Required bool   `json:"required"`
	Message  string `json:"message"`
	Details  string `json:"details,omitempty"`
}

// SBOMReport contains SBOM generation results
type SBOMReport struct {
	Format  string    `json:"format"`
	Path    string    `json:"path"`
	Size    int64     `json:"size"`
	Hash    string    `json:"hash"`
	Created time.Time `json:"created"`
}

func (s *SupplyChainSecurity) checkRegistry(imageRef string) CheckResult {
	for _, registry := range s.policy.AllowedRegistries {
		if strings.HasPrefix(imageRef, registry) {
			return CheckResult{
				Passed:   true,
				Required: true,
				Message:  fmt.Sprintf("Registry %s is allowed", registry),
			}
		}
	}

	return CheckResult{
		Passed:   false,
		Required: true,
		Message:  fmt.Sprintf("Registry for %s is not in allowlist", imageRef),
	}
}

func (s *SupplyChainSecurity) verifySignature(ctx context.Context, imageRef string) CheckResult {
	// In production, this would use cosign or notary to verify signatures
	// For now, simulate verification

	if _, err := os.Stat(s.policy.ImageSigning.PublicKeyPath); err != nil {
		return CheckResult{
			Passed:   false,
			Required: s.policy.ImageSigning.Required,
			Message:  "Signing key not found",
			Details:  err.Error(),
		}
	}

	// Simulate signature verification
	return CheckResult{
		Passed:   true,
		Required: s.policy.ImageSigning.Required,
		Message:  "Signature verified",
		Details:  fmt.Sprintf("Key ID: %s", s.policy.ImageSigning.KeyID),
	}
}

// VulnerabilityResult represents a vulnerability scan result
type VulnerabilityResult struct {
	CVE         string `json:"cve"`
	Severity    string `json:"severity"`
	Package     string `json:"package"`
	Version     string `json:"version"`
	FixedIn     string `json:"fixed_in,omitempty"`
	Description string `json:"description,omitempty"`
}

func (s *SupplyChainSecurity) scanVulnerabilities(_ context.Context, imageRef string) CheckResult {
	scanner := s.policy.VulnScanning.Scanner
	threshold := s.policy.VulnScanning.SeverityThreshold

	// In production, this would call trivy, clair, or snyk
	// For now, simulate scanning
	cmd := exec.CommandContext(context.Background(), scanner, "image", "--severity", threshold, "--exit-code", "0", "-f", "json", imageRef)
	output, _ := cmd.CombinedOutput()

	// Simulate findings
	var vulns []VulnerabilityResult
	if err := json.Unmarshal(output, &vulns); err != nil {
		// No vulnerabilities found or scan failed
		vulns = []VulnerabilityResult{}
	}

	// Filter ignored CVEs
	var filtered []VulnerabilityResult
	for _, v := range vulns {
		ignored := false
		for _, cve := range s.policy.VulnScanning.IgnoredCVEs {
			if v.CVE == cve {
				ignored = true
				break
			}
		}
		if !ignored {
			filtered = append(filtered, v)
		}
	}

	if len(filtered) > 0 {
		return CheckResult{
			Passed:   false,
			Required: s.policy.VulnScanning.FailOnVuln,
			Message:  fmt.Sprintf("Found %d vulnerabilities at or above %s severity", len(filtered), threshold),
			Details:  formatVulnerabilities(filtered),
		}
	}

	return CheckResult{
		Passed:   true,
		Required: s.policy.VulnScanning.FailOnVuln,
		Message:  "No vulnerabilities found",
	}
}

func formatVulnerabilities(vulns []VulnerabilityResult) string {
	var lines []string
	for _, v := range vulns {
		lines = append(lines, fmt.Sprintf("- %s (%s): %s %s", v.CVE, v.Severity, v.Package, v.Version))
	}
	return strings.Join(lines, "\n")
}

func (s *SupplyChainSecurity) checkProhibitedPackages(_ context.Context, _ string) CheckResult {
	// In production, this would inspect the image layers
	// For now, simulate the check

	return CheckResult{
		Passed:   true,
		Required: false,
		Message:  "No prohibited packages found",
	}
}

func (s *SupplyChainSecurity) generateSBOM(_ context.Context, imageRef string) (*SBOMReport, error) {
	if err := os.MkdirAll(s.policy.SBOM.OutputPath, 0750); err != nil {
		return nil, fmt.Errorf("failed to create SBOM directory: %w", err)
	}

	// Generate SBOM filename
	hash := sha256.Sum256([]byte(imageRef + time.Now().String()))
	filename := fmt.Sprintf("sbom_%s_%s.%s.json",
		normalizeImageRef(imageRef),
		hex.EncodeToString(hash[:4]),
		s.policy.SBOM.Format)

	path := filepath.Join(s.policy.SBOM.OutputPath, filename)

	// In production, this would use syft or similar tool
	// For now, create a placeholder SBOM
	sbom := map[string]interface{}{
		"bomFormat":   s.policy.SBOM.Format,
		"specVersion": "1.4",
		"timestamp":   time.Now().UTC().Format(time.RFC3339),
		"components":  []interface{}{},
	}

	data, err := json.MarshalIndent(sbom, "", "  ")
	if err != nil {
		return nil, err
	}

	if err := os.WriteFile(path, data, 0640); err != nil {
		return nil, err
	}

	info, _ := os.Stat(path)
	hash = sha256.Sum256(data)

	return &SBOMReport{
		Format:  s.policy.SBOM.Format,
		Path:    path,
		Size:    info.Size(),
		Hash:    hex.EncodeToString(hash[:]),
		Created: time.Now().UTC(),
	}, nil
}

func normalizeImageRef(ref string) string {
	// Replace characters that are not filesystem-safe
	ref = strings.ReplaceAll(ref, "/", "_")
	ref = strings.ReplaceAll(ref, ":", "_")
	return ref
}

// ImageSignConfig holds image signing credentials
type ImageSignConfig struct {
	PrivateKeyPath string `json:"private_key_path"`
	KeyID          string `json:"key_id"`
}

// SignImage signs a container image
func SignImage(ctx context.Context, imageRef string, config *ImageSignConfig) error {
	// In production, this would use cosign or notary
	// For now, this is a placeholder

	if _, err := os.Stat(config.PrivateKeyPath); err != nil {
		return fmt.Errorf("private key not found: %w", err)
	}

	// Simulate signing
	time.Sleep(100 * time.Millisecond)

	return nil
}
