package packs

import (
	"encoding/json"
	"fmt"
	"sort"
	"strings"
)

const (
	HostPrimitiveKindProductDialect = "product-dialect"

	PackHostRequirementsDigestAlgorithmV1 = "nexus-hostreq-sha256-v1"
	PackHostRequirementsDigestV1Prefix    = PackHostRequirementsDigestAlgorithmV1 + ":"
)

// HostRequirementsProvider is implemented by manifest-backed packs that can
// declare runtime capabilities the host binary must provide.
type HostRequirementsProvider interface {
	HostRequirements() []HostPrimitiveRequirement
}

type packHostRequirementsDigestDocument struct {
	ManifestFormatVersion string                     `json:"manifestFormatVersion"`
	PackName              string                     `json:"packName"`
	PackVersion           string                     `json:"packVersion"`
	Category              Category                   `json:"category"`
	HostRequirements      []HostPrimitiveRequirement `json:"hostRequirements"`
}

// PackHostRequirements returns the manifest host requirements declared by pack.
func PackHostRequirements(pack Pack) []HostPrimitiveRequirement {
	provider, ok := pack.(HostRequirementsProvider)
	if !ok {
		return nil
	}
	return normalizeHostRequirementsForDigest(provider.HostRequirements())
}

// PackHostRequirementsDigest is the independent source-convergence signal for
// manifest hostRequirements. It intentionally does not participate in
// PackPayloadHash so component payload churn and host compatibility churn remain
// separate facts.
func PackHostRequirementsDigest(pack Pack) (string, error) {
	if pack == nil {
		return "", fmt.Errorf("pack is nil")
	}
	meta := PackHashMetadataForPack(pack)
	doc := packHostRequirementsDigestDocument{
		ManifestFormatVersion: meta.ManifestFormatVersion,
		PackName:              pack.Name(),
		PackVersion:           meta.PackVersion,
		Category:              pack.Category(),
		HostRequirements:      PackHostRequirements(pack),
	}
	encoded, err := json.Marshal(doc)
	if err != nil {
		return "", fmt.Errorf("marshal host requirements digest document: %w", err)
	}
	return PackHostRequirementsDigestV1Prefix + sha256Hex(encoded), nil
}

func IsPackHostRequirementsDigestV1(digest string) bool {
	if !strings.HasPrefix(digest, PackHostRequirementsDigestV1Prefix) {
		return false
	}
	return isLowerHex64(strings.TrimPrefix(digest, PackHostRequirementsDigestV1Prefix))
}

func normalizeHostRequirementsForDigest(requirements []HostPrimitiveRequirement) []HostPrimitiveRequirement {
	out := make([]HostPrimitiveRequirement, 0, len(requirements))
	for _, req := range requirements {
		kind := strings.ToLower(strings.TrimSpace(req.Kind))
		name := strings.TrimSpace(req.Name)
		if kind == "" || name == "" {
			continue
		}
		out = append(out, HostPrimitiveRequirement{
			Kind:    kind,
			Name:    name,
			Version: strings.TrimSpace(req.Version),
		})
	}
	sort.SliceStable(out, func(i, j int) bool {
		if out[i].Kind != out[j].Kind {
			return out[i].Kind < out[j].Kind
		}
		if out[i].Name != out[j].Name {
			return out[i].Name < out[j].Name
		}
		return out[i].Version < out[j].Version
	})
	return out
}
