package catalogapp

import (
	"fmt"
	"slices"
	"sort"

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

func canonicalHandlerProjectionAuthority(
	provided []appauthority.ProvidedCapability,
	selected []string,
	admissions []WorkloadHandlerAdmission,
	authorizedWorkloads []string,
	grantedCapabilities []string,
) ([]string, []WorkloadHandlerAdmission, error) {
	available := make([]string, 0, len(provided))
	providedByID := make(map[string]appauthority.ProvidedCapability, len(provided))
	for _, capability := range provided {
		if capability.Kind != appauthority.CapabilityKindServiceHandler {
			continue
		}
		available = append(available, capability.CapabilityID)
		providedByID[capability.CapabilityID] = capability
	}
	sort.Strings(available)
	if len(selected) == 0 {
		if len(admissions) != 0 {
			return nil, nil, fmt.Errorf(
				"legacy all-handler selection cannot declare workload handler admissions",
			)
		}
		return nil, nil, nil
	}
	canonicalSelected, err := canonicalStrings(
		"selected handler capabilities", selected, false,
	)
	if err != nil {
		return nil, nil, err
	}
	if !isSubset(canonicalSelected, available) {
		return nil, nil, fmt.Errorf(
			"selected handler capability is not provided by the verified App release",
		)
	}
	if slices.Equal(canonicalSelected, available) {
		return nil, nil, fmt.Errorf(
			"complete handler selection must use the legacy empty representation",
		)
	}
	// App handler dependencies may compose both serving and workload Runtime
	// calls. They do not identify the concrete Runtime serving selection.
	// The config owner validates that selection; this durable owner validates
	// the exact handler partition, verified dependencies and workload grants.

	canonicalAdmissions := append([]WorkloadHandlerAdmission(nil), admissions...)
	sort.Slice(canonicalAdmissions, func(left, right int) bool {
		return canonicalAdmissions[left].CapabilityID < canonicalAdmissions[right].CapabilityID
	})
	admitted := make([]string, 0, len(canonicalAdmissions))
	for index, admission := range canonicalAdmissions {
		if err := appauthority.ValidateCapabilityID(admission.CapabilityID); err != nil {
			return nil, nil, fmt.Errorf("workload handler admission[%d]: %w", index, err)
		}
		if index > 0 && admission.CapabilityID == canonicalAdmissions[index-1].CapabilityID {
			return nil, nil, fmt.Errorf("workload handler admissions contain a duplicate capability")
		}
		if err := catalogconsumerauthority.ValidateWorkloadID(
			admission.AuthorizedWorkloadID,
		); err != nil {
			return nil, nil, fmt.Errorf(
				"workload handler admission[%d] workload: %w", index, err,
			)
		}
		if !slices.Contains(authorizedWorkloads, admission.AuthorizedWorkloadID) {
			return nil, nil, fmt.Errorf(
				"workload handler admission[%d] workload is not authorized by the App operation",
				index,
			)
		}
		capability, ok := providedByID[admission.CapabilityID]
		if !ok || slices.Contains(canonicalSelected, admission.CapabilityID) {
			return nil, nil, fmt.Errorf(
				"workload handler admission[%d] does not name an omitted verified App handler",
				index,
			)
		}
		if !slices.Equal(
			admission.RequiredRuntimeCapabilities,
			capability.RequiredRuntimeCapabilities,
		) || !isSubset(admission.RequiredRuntimeCapabilities, grantedCapabilities) {
			return nil, nil, fmt.Errorf(
				"workload handler admission[%d] does not prove the complete granted Runtime dependency closure",
				index,
			)
		}
		canonicalAdmissions[index].RequiredRuntimeCapabilities = append(
			[]string(nil), admission.RequiredRuntimeCapabilities...,
		)
		admitted = append(admitted, admission.CapabilityID)
	}
	partition := append(append([]string(nil), canonicalSelected...), admitted...)
	sort.Strings(partition)
	if !slices.Equal(partition, available) {
		return nil, nil, fmt.Errorf(
			"selected and workload-admitted handlers must exactly partition the verified App release",
		)
	}
	return canonicalSelected, canonicalAdmissions, nil
}

func equalWorkloadHandlerAdmissions(
	left []WorkloadHandlerAdmission,
	right []WorkloadHandlerAdmission,
) bool {
	return slices.EqualFunc(left, right, func(
		left WorkloadHandlerAdmission,
		right WorkloadHandlerAdmission,
	) bool {
		return left.CapabilityID == right.CapabilityID &&
			left.AuthorizedWorkloadID == right.AuthorizedWorkloadID &&
			slices.Equal(
				left.RequiredRuntimeCapabilities,
				right.RequiredRuntimeCapabilities,
			)
	})
}
