package catalogreconcile

import (
	"bytes"
	"encoding/json"
	"fmt"
	"io"

	"github.com/nexuscrm/kernel/pkg/formula"
	"github.com/nexuscrm/metadata/catalogsnapshot"
	"github.com/nexuscrm/shared/pkg/models"
	"github.com/pingcap-inc/nexuscrm-contract/pkg/appauthority"
	"github.com/pingcap-inc/nexuscrm-contract/pkg/approvalir"
	"github.com/pingcap-inc/nexuscrm-contract/pkg/runtime/approvalv1"
	"github.com/pingcap-inc/nexuscrm-contract/pkg/runtime/commandv1"
	"github.com/pingcap-inc/nexuscrm-contract/pkg/runtime/reportv1"
	"github.com/pingcap-inc/nexuscrm-controlplane/catalogapp"
	"github.com/pingcap-inc/nexuscrm-controlplane/strictjson"
)

const (
	// These are app-neutral immutable component grammars. L1 compilers lower
	// product report, command, and approval metadata into these shapes before AppRelease
	// publication; Catalog never reads a product repository or dialect.
	reportReleaseComponentKind   = "metadata.report.v1"
	commandReleaseComponentKind  = "metadata.runtime-command-registration.v1"
	approvalReleaseComponentKind = "metadata.approval-definition.v1"
	formulaReleaseComponentKind  = "metadata.pack.formula-function-bundle.v1"
)

type platformReleaseDefinitions struct {
	reports                []platformReleaseReport
	commands               []platformReleaseCommand
	approvals              []platformReleaseApproval
	formulaFunctionBundles []formula.FunctionBundle
}

type platformReleaseReport struct {
	componentID       string
	definitionVersion string
	contentDigest     string
	definition        *models.ReportDefinition
}

type platformReleaseCommand struct {
	componentID          string
	definitionVersion    string
	contentDigest        string
	commandKind          string
	payloadFormatVersion uint32
}

type platformReleaseApproval struct {
	componentID   string
	objectAPIName string
	developerName string
	envelope      []byte
}

type commandRegistrationIdentity struct {
	kind    string
	version uint32
}

func (command platformReleaseCommand) key() commandRegistrationIdentity {
	return commandRegistrationIdentity{
		kind: command.commandKind, version: command.payloadFormatVersion,
	}
}

type commandExecutionIdentity struct {
	registration         commandRegistrationIdentity
	authorizedWorkloadID string
	definitionRef        string
	definitionVersion    string
	contextNamespace     string
}

type commandReleaseDocument struct {
	CommandKind          string `json:"commandKind"`
	PayloadFormatVersion uint32 `json:"payloadFormatVersion"`
}

// approvalReleaseDocument is the app-neutral identity wrapper around the
// canonical approvalir sidecar authored by L1. Controlplane never decodes the
// product source grammar and never reads live ProcessDefinition rows.
type approvalReleaseDocument struct {
	ObjectAPIName      string          `json:"objectApiName"`
	DeveloperName      string          `json:"developerName"`
	DefinitionEnvelope json.RawMessage `json:"definitionEnvelope"`
}

func readPlatformReleaseDefinitions(
	release *appauthority.VerifiedPackage,
	selection MetadataSelection,
) (platformReleaseDefinitions, error) {
	needsReports := selectionIncludesCapability(selection, reportv1.CapabilityName)
	needsCommands := selectionIncludesCapability(selection, commandv1.CapabilityName)
	needsApprovals := selectionIncludesCapability(selection, approvalv1.ExecuteCapabilityName) ||
		selectionIncludesCapability(selection, approvalv1.ReadCapabilityName)
	needsFormulaFunctions := selectionIncludesMutationFacts(selection) &&
		platformReleaseContainsComponentKind(release, formulaReleaseComponentKind)
	if !needsReports && !needsCommands && !needsApprovals && !needsFormulaFunctions {
		return platformReleaseDefinitions{}, nil
	}
	if release == nil {
		return platformReleaseDefinitions{}, fmt.Errorf("Platform metadata requires the verified AppRelease")
	}
	verified, err := appauthority.VerifyPackage(release.Package)
	if err != nil {
		return platformReleaseDefinitions{}, fmt.Errorf("verify Platform AppRelease package: %w", err)
	}
	if verified.AppReleaseDigest != release.AppReleaseDigest {
		return platformReleaseDefinitions{}, fmt.Errorf("Platform AppRelease identity disagrees with its verified package")
	}

	var result platformReleaseDefinitions
	for index, component := range verified.Bundle.Components {
		switch component.ComponentKind {
		case reportReleaseComponentKind:
			if !needsReports {
				continue
			}
			if component.ComponentFormatVersion != 1 {
				return platformReleaseDefinitions{}, fmt.Errorf(
					"report component %q has unsupported format version %d",
					component.ComponentID,
					component.ComponentFormatVersion,
				)
			}
			var definition models.ReportDefinition
			if err := decodePlatformReleaseComponent(
				verified.ComponentBlobs[index].CanonicalBehavior,
				"report AppRelease component",
				&definition,
			); err != nil {
				return platformReleaseDefinitions{}, err
			}
			result.reports = append(result.reports, platformReleaseReport{
				componentID: component.ComponentID, definitionVersion: component.DefinitionVersion,
				contentDigest: component.ContentDigest, definition: &definition,
			})
		case commandReleaseComponentKind:
			if !needsCommands {
				continue
			}
			if component.ComponentFormatVersion != 1 {
				return platformReleaseDefinitions{}, fmt.Errorf(
					"command component %q has unsupported format version %d",
					component.ComponentID,
					component.ComponentFormatVersion,
				)
			}
			var document commandReleaseDocument
			if err := decodePlatformReleaseComponent(
				verified.ComponentBlobs[index].CanonicalBehavior,
				"command AppRelease component",
				&document,
			); err != nil {
				return platformReleaseDefinitions{}, err
			}
			result.commands = append(result.commands, platformReleaseCommand{
				componentID: component.ComponentID, definitionVersion: component.DefinitionVersion,
				contentDigest: component.ContentDigest, commandKind: document.CommandKind,
				payloadFormatVersion: document.PayloadFormatVersion,
			})
		case approvalReleaseComponentKind:
			if !needsApprovals {
				continue
			}
			if component.ComponentFormatVersion != 1 {
				return platformReleaseDefinitions{}, fmt.Errorf(
					"approval component %q has unsupported format version %d",
					component.ComponentID,
					component.ComponentFormatVersion,
				)
			}
			var document approvalReleaseDocument
			if err := decodePlatformReleaseComponent(
				verified.ComponentBlobs[index].CanonicalBehavior,
				"approval AppRelease component",
				&document,
			); err != nil {
				return platformReleaseDefinitions{}, err
			}
			if _, err := approvalir.DecodeDefinitionEnvelope(document.DefinitionEnvelope, nil); err != nil {
				return platformReleaseDefinitions{}, fmt.Errorf(
					"approval AppRelease component %q: %w",
					component.ComponentID,
					err,
				)
			}
			result.approvals = append(result.approvals, platformReleaseApproval{
				componentID: component.ComponentID, objectAPIName: document.ObjectAPIName,
				developerName: document.DeveloperName,
				envelope:      append([]byte(nil), document.DefinitionEnvelope...),
			})
		case formulaReleaseComponentKind:
			if !needsFormulaFunctions {
				continue
			}
			if component.ComponentFormatVersion != 1 {
				return platformReleaseDefinitions{}, fmt.Errorf(
					"formula-function component %q has unsupported format version %d",
					component.ComponentID,
					component.ComponentFormatVersion,
				)
			}
			var bundle formula.FunctionBundle
			if err := decodePlatformReleaseComponent(
				verified.ComponentBlobs[index].CanonicalBehavior,
				"formula-function AppRelease component",
				&bundle,
			); err != nil {
				return platformReleaseDefinitions{}, err
			}
			result.formulaFunctionBundles = append(result.formulaFunctionBundles, bundle)
		}
	}
	if err := formula.ValidateFunctionBundles(result.formulaFunctionBundles); err != nil {
		return platformReleaseDefinitions{}, fmt.Errorf(
			"validate formula-function AppRelease components: %w",
			err,
		)
	}
	return result, nil
}

func platformReleaseContainsComponentKind(
	release *appauthority.VerifiedPackage,
	kind string,
) bool {
	if release == nil {
		return false
	}
	for _, component := range release.Package.Bundle.Components {
		if component.ComponentKind == kind {
			return true
		}
	}
	return false
}

// validateApprovalDefinitionAvailability proves the complete immutable
// definition set before Catalog creates a durable operation. Definitions may
// be owned by the verified AppRelease, by the separately bound L1 publication,
// or by both; the optional sidecar is not a second requirement when the release
// already carries the capability's definitions.
func validateApprovalDefinitionAvailability(
	request Request,
	release *appauthority.VerifiedPackage,
) error {
	if !requestIncludesRuntimeCapability(request, approvalv1.ExecuteCapabilityName) {
		return nil
	}
	definitions, err := readPlatformReleaseDefinitions(release, MetadataSelection{
		RuntimeCapabilityID:  request.RuntimeCapabilityID,
		RuntimeCapabilityIDs: runtimeCapabilityIDs(request.Capabilities),
	})
	if err != nil {
		return fmt.Errorf("validate Approval definition sources before Catalog operation: %w", err)
	}
	definitionCount := len(definitions.approvals)
	if request.approvalPublication != nil && request.approvalPublication.bundle != nil {
		definitionCount += len(request.approvalPublication.bundle.Definitions)
	}
	if definitionCount == 0 {
		return fmt.Errorf("Approval execution requires at least one immutable definition")
	}
	return nil
}

func commandExecutionBindings(
	commands []platformReleaseCommand,
	selections []catalogapp.IntegrationBindingSelection,
) (map[commandRegistrationIdentity][]catalogsnapshot.CommandExecutionBinding, error) {
	registered := make(map[commandRegistrationIdentity]struct{}, len(commands))
	for _, command := range commands {
		registered[command.key()] = struct{}{}
	}
	result := make(
		map[commandRegistrationIdentity][]catalogsnapshot.CommandExecutionBinding,
	)
	seen := make(map[commandExecutionIdentity]struct{})
	for index, selection := range selections {
		command := selection.Binding.RuntimeCommand
		if command == nil {
			continue
		}
		registrationKey := commandRegistrationIdentity{
			kind: command.CommandKind, version: command.PayloadFormatVersion,
		}
		if _, ok := registered[registrationKey]; !ok {
			return nil, fmt.Errorf(
				"integration binding[%d] Runtime command %q version %d has no AppRelease registration",
				index,
				command.CommandKind,
				command.PayloadFormatVersion,
			)
		}
		binding := catalogsnapshot.CommandExecutionBinding{
			AuthorizedWorkloadID: selection.AuthorizedWorkloadID,
			DefinitionRef:        command.DefinitionRef,
			DefinitionVersion:    command.DefinitionVersion,
			// Connector workers already use the installed owner-pack namespace
			// as their trusted execution context. Catalog projects that same
			// authored fact; neither Runtime nor Integration may supply it in
			// the dynamic command payload.
			ContextNamespace: selection.Binding.OwnerPack,
		}
		bindingKey := commandExecutionIdentity{
			registration:         registrationKey,
			authorizedWorkloadID: binding.AuthorizedWorkloadID,
			definitionRef:        binding.DefinitionRef,
			definitionVersion:    binding.DefinitionVersion,
			contextNamespace:     binding.ContextNamespace,
		}
		if _, duplicate := seen[bindingKey]; duplicate {
			continue
		}
		seen[bindingKey] = struct{}{}
		result[registrationKey] = append(
			result[registrationKey],
			binding,
		)
	}
	return result, nil
}

func decodePlatformReleaseComponent(raw []byte, source string, target any) error {
	if err := strictjson.RejectDuplicateKeys(raw, source); err != nil {
		return fmt.Errorf("decode %s: %w", source, err)
	}
	decoder := json.NewDecoder(bytes.NewReader(raw))
	decoder.DisallowUnknownFields()
	if err := decoder.Decode(target); err != nil {
		return fmt.Errorf("decode %s: %w", source, err)
	}
	if err := decoder.Decode(&struct{}{}); err != io.EOF {
		if err == nil {
			err = fmt.Errorf("trailing JSON value")
		}
		return fmt.Errorf("decode %s: %w", source, err)
	}
	return nil
}

func (d platformReleaseDefinitions) reportComponentIDs() []string {
	result := make([]string, len(d.reports))
	for index := range d.reports {
		result[index] = d.reports[index].componentID
	}
	return result
}

func (d platformReleaseDefinitions) commandComponentIDs() []string {
	result := make([]string, len(d.commands))
	for index := range d.commands {
		result[index] = d.commands[index].componentID
	}
	return result
}

func (d platformReleaseDefinitions) approvalComponentIDs() []string {
	result := make([]string, len(d.approvals))
	for index := range d.approvals {
		result[index] = d.approvals[index].componentID
	}
	return result
}
