package releaseartifact

import (
	"errors"
	"fmt"
	"strings"

	"github.com/pingcap-inc/nexuscrm-contract/pkg/appauthority"
)

type SupportedProtocol struct {
	ProtocolID string
	Major      uint32
	Minor      uint32
}

type SupportedComponentKind struct {
	ComponentKind          string
	ComponentFormatVersion uint32
}

type HostCompatibility struct {
	Protocols          []SupportedProtocol
	EngineCapabilities []string
	ComponentKinds     []SupportedComponentKind
}

// VerifyCompatibility evaluates the verified AppManifest and MetadataBundle
// requirements. It does not author compatibility facts or choose a serving
// binding.
func VerifyCompatibility(pkg appauthority.VerifiedPackage, host HostCompatibility) error {
	if host.Protocols == nil || host.EngineCapabilities == nil || host.ComponentKinds == nil {
		return errors.New("host compatibility facts must be explicit, not nil")
	}
	manifest := pkg.Manifest
	protocols := make(map[string]SupportedProtocol, len(host.Protocols))
	for _, supported := range host.Protocols {
		if supported.ProtocolID == "" || len(supported.ProtocolID) > 128 || strings.TrimSpace(supported.ProtocolID) != supported.ProtocolID {
			return fmt.Errorf("supported protocol ID %q is not canonical", supported.ProtocolID)
		}
		if supported.Major == 0 {
			return fmt.Errorf("supported protocol %q has zero major", supported.ProtocolID)
		}
		if _, duplicate := protocols[supported.ProtocolID]; duplicate {
			return fmt.Errorf("supported protocols duplicate %q", supported.ProtocolID)
		}
		protocols[supported.ProtocolID] = supported
	}
	capabilities := make(map[string]struct{}, len(host.EngineCapabilities))
	for _, capability := range host.EngineCapabilities {
		if err := appauthority.ValidateCapabilityID(capability); err != nil {
			return err
		}
		if _, duplicate := capabilities[capability]; duplicate {
			return fmt.Errorf("supported engine capabilities duplicate %q", capability)
		}
		capabilities[capability] = struct{}{}
	}
	for _, required := range manifest.RequiredProtocols {
		supported, ok := protocols[required.ProtocolID]
		if !ok || supported.Major != required.Major || supported.Minor < required.MinimumMinor {
			return fmt.Errorf("required protocol %s major %d minor >= %d is incompatible", required.ProtocolID, required.Major, required.MinimumMinor)
		}
	}
	for _, required := range manifest.RequiredEngineCapabilities {
		if _, ok := capabilities[required]; !ok {
			return fmt.Errorf("required engine capability %s is unavailable", required)
		}
	}
	type componentKey struct {
		kind    string
		version uint32
	}
	componentKinds := make(map[componentKey]struct{}, len(host.ComponentKinds))
	for _, supported := range host.ComponentKinds {
		if err := appauthority.ValidateCapabilityID(supported.ComponentKind); err != nil {
			return fmt.Errorf("supported component kind: %w", err)
		}
		if supported.ComponentFormatVersion == 0 {
			return fmt.Errorf("supported component kind %q has zero format version", supported.ComponentKind)
		}
		key := componentKey{kind: supported.ComponentKind, version: supported.ComponentFormatVersion}
		if _, duplicate := componentKinds[key]; duplicate {
			return fmt.Errorf("supported component kinds duplicate %q format %d", supported.ComponentKind, supported.ComponentFormatVersion)
		}
		componentKinds[key] = struct{}{}
	}
	for _, component := range pkg.Bundle.Components {
		key := componentKey{kind: component.ComponentKind, version: component.ComponentFormatVersion}
		if _, ok := componentKinds[key]; !ok {
			return fmt.Errorf("component kind %s format %d is incompatible", component.ComponentKind, component.ComponentFormatVersion)
		}
	}
	return nil
}
