package catalogreconcile

import (
	"bytes"
	"context"
	"encoding/json"
	"fmt"
	"sort"
	"strconv"
	"strings"

	"github.com/nexuscrm/metadata/catalogsnapshot"
	"github.com/nexuscrm/shared/pkg/models"
	"github.com/pingcap-inc/nexuscrm-contract/pkg/appauthority"
	consumerv1 "github.com/pingcap-inc/nexuscrm-contract/pkg/catalog/consumerv1"
	"github.com/pingcap-inc/nexuscrm-controlplane/catalogapp"
	"github.com/pingcap-inc/nexuscrm-controlplane/componentbehavior"
)

const appReleaseStandardFlowComponentKind = "metadata.pack.standard-flow.v1"

type effectiveActiveFlow struct {
	definition *models.FlowDefinition
	version    *models.FlowVersion
}

// sealEffectiveFlowRecordLookupFieldClosures compiles the effective Flow once
// before it is consumed by both Runtime Catalog and AppMetadata publication.
// This single boundary is required: Runtime deliberately verifies that the two
// published Flow behaviors are byte-identical before execution.
func sealEffectiveFlowRecordLookupFieldClosures(
	activeFlows []effectiveActiveFlow,
	mappings []catalogsnapshot.FieldMappingRule,
) ([]effectiveActiveFlow, error) {
	if len(activeFlows) == 0 {
		return activeFlows, nil
	}
	sealed := make([]effectiveActiveFlow, 0, len(activeFlows))
	for index, flow := range activeFlows {
		if flow.version == nil {
			return nil, fmt.Errorf("effective active Flow[%d] has no version", index)
		}
		behavior, err := canonicalEffectiveFlowBehavior(flow.version)
		if err != nil {
			return nil, fmt.Errorf("canonicalize effective active Flow[%d]: %w", index, err)
		}
		projected, _, err := catalogsnapshot.SealFlowRecordLookupFieldClosures(
			behavior, mappings,
		)
		if err != nil {
			return nil, fmt.Errorf("seal effective active Flow[%d] RecordLookup closure: %w", index, err)
		}
		version := *flow.version
		version.MetadataRaw = append(json.RawMessage(nil), projected...)
		version.MetadataJSON = string(projected)
		version.Metadata = models.FlowMetadata{}
		version.HydrateMetadata()
		sealed = append(sealed, effectiveActiveFlow{
			definition: flow.definition,
			version:    &version,
		})
	}
	return sealed, nil
}

// canonicalEffectiveFlowBehavior preserves the stored Flow document as the
// authority. Metadata is only a typed read projection and cannot round-trip
// fields that the current Go model does not know yet.
func canonicalEffectiveFlowBehavior(version *models.FlowVersion) ([]byte, error) {
	var source []byte
	switch {
	case version != nil && len(bytes.TrimSpace(version.MetadataRaw)) != 0:
		source = version.MetadataRaw
	case version != nil && strings.TrimSpace(version.MetadataJSON) != "":
		source = []byte(version.MetadataJSON)
	case version != nil:
		encoded, err := json.Marshal(version.Metadata)
		if err != nil {
			return nil, err
		}
		source = encoded
	default:
		return nil, fmt.Errorf("effective Flow version is required")
	}
	return canonicalStandardFlowBehavior(source)
}

func canonicalStandardFlowBehavior(source []byte) ([]byte, error) {
	canonical, err := componentbehavior.CanonicalJSONV1(source)
	if err != nil {
		return nil, err
	}
	if len(canonical) == 0 || canonical[0] != '{' {
		return nil, fmt.Errorf("standard Flow behavior must be a JSON object")
	}
	return canonical, nil
}

// readEffectiveActiveFlows resolves the active Flow set once for every
// consumer that needs it. It is deliberately independent of mutation
// projection so AppMetadataView V2 cannot silently omit an authorized override
// merely because mutation is absent from the same selection.
func readEffectiveActiveFlows(
	ctx context.Context,
	reader EffectiveFlowMetadataReader,
) ([]effectiveActiveFlow, error) {
	flowDefinitions, err := reader.GetAllFlowDefinitions(ctx)
	if err != nil {
		return nil, fmt.Errorf("read effective Flow definitions: %w", err)
	}
	activeFlows := make([]effectiveActiveFlow, 0, len(flowDefinitions))
	for i, definition := range flowDefinitions {
		if definition == nil {
			return nil, fmt.Errorf("effective Flow definition[%d] is nil", i)
		}
		if definition.IsDeleted || strings.TrimSpace(definition.ActiveVersionId) == "" {
			continue
		}
		versions, err := reader.GetFlowVersions(ctx, definition.Id)
		if err != nil {
			return nil, fmt.Errorf("read effective Flow versions for %q: %w", definition.Id, err)
		}
		var active *models.FlowVersion
		for j, version := range versions {
			if version == nil {
				return nil, fmt.Errorf("effective Flow version %q[%d] is nil", definition.Id, j)
			}
			if version.Id != definition.ActiveVersionId {
				continue
			}
			if active != nil {
				return nil, fmt.Errorf("Flow definition %q has duplicate active-version rows %q", definition.Id, definition.ActiveVersionId)
			}
			active = version
		}
		if active == nil {
			return nil, fmt.Errorf("Flow definition %q references absent active version %q", definition.Id, definition.ActiveVersionId)
		}
		if active.DefinitionId != definition.Id || active.Status != models.FlowStatusActive || active.IsDeleted {
			return nil, fmt.Errorf("Flow definition %q active version %q is not an active canonical version", definition.Id, definition.ActiveVersionId)
		}
		// FlowDefinition owns pack/customization identity. Repository FlowVersion
		// reads cannot populate these runtime-only fields from the Flow row, but
		// the Catalog mutation compiler needs them to admit pack-owned actions
		// without treating the active version as ownerless. Assemble the joined
		// fact once from the same transaction snapshot instead of making each
		// downstream consumer rediscover the definition.
		activeWithDefinition := *active
		activeWithDefinition.DefinitionIsCustomized = definition.IsCustomized
		activeWithDefinition.DefinitionOwnerPack = strings.TrimSpace(definition.OwnerPack)
		activeFlows = append(activeFlows, effectiveActiveFlow{
			definition: definition,
			version:    &activeWithDefinition,
		})
	}
	return activeFlows, nil
}

// buildEffectiveStandardFlowComponents publishes only effective overrides of
// standard Flows already authorized by the verified AppRelease. App ownership
// stays in AppComponents; these components bind the installed effective bytes
// and version to the same immutable Catalog revision.
func buildEffectiveStandardFlowComponents(
	release *appauthority.VerifiedPackage,
	activeFlows []effectiveActiveFlow,
) ([]catalogapp.EffectiveMetadataComponent, error) {
	if len(activeFlows) == 0 {
		return nil, nil
	}
	if release == nil {
		return nil, fmt.Errorf("effective standard Flow publication requires the verified AppRelease")
	}
	verified, err := appauthority.VerifyPackage(release.Package)
	if err != nil {
		return nil, fmt.Errorf("verify effective standard Flow AppRelease: %w", err)
	}
	if verified.AppReleaseDigest != release.AppReleaseDigest {
		return nil, fmt.Errorf("effective standard Flow AppRelease identity disagrees with its verified package")
	}

	activeByName := make(map[string]effectiveActiveFlow, len(activeFlows))
	for index, flow := range activeFlows {
		if flow.definition == nil || flow.version == nil ||
			strings.TrimSpace(flow.definition.DeveloperName) == "" {
			return nil, fmt.Errorf("effective active Flow[%d] is incomplete", index)
		}
		if _, duplicate := activeByName[flow.definition.DeveloperName]; duplicate {
			return nil, fmt.Errorf("effective active Flows contain duplicate developer name %q", flow.definition.DeveloperName)
		}
		activeByName[flow.definition.DeveloperName] = flow
	}

	result := make([]catalogapp.EffectiveMetadataComponent, 0)
	for index, component := range verified.Bundle.Components {
		if component.ComponentKind != appReleaseStandardFlowComponentKind {
			continue
		}
		if component.ComponentFormatVersion != 1 {
			return nil, fmt.Errorf("standard Flow component %q has unsupported format version %d", component.ComponentID, component.ComponentFormatVersion)
		}
		flow, ok := activeByName[component.BusinessKey]
		if !ok {
			return nil, fmt.Errorf("standard Flow component %q has no active effective Flow", component.BusinessKey)
		}
		effectiveBehavior, err := canonicalEffectiveFlowBehavior(flow.version)
		if err != nil {
			return nil, fmt.Errorf("read effective standard Flow %q behavior: %w", component.BusinessKey, err)
		}
		appBehavior, err := canonicalStandardFlowBehavior(
			verified.ComponentBlobs[index].CanonicalBehavior,
		)
		if err != nil {
			return nil, fmt.Errorf("read AppRelease standard Flow %q behavior: %w", component.BusinessKey, err)
		}
		effectiveVersion := strconv.Itoa(flow.version.VersionNumber)
		if effectiveVersion == component.DefinitionVersion &&
			bytes.Equal(effectiveBehavior, appBehavior) {
			continue
		}
		if component.OverridePolicy != appauthority.OverridePolicyPreserveAuthorized {
			return nil, fmt.Errorf("standard Flow component %q does not authorize an effective override", component.BusinessKey)
		}
		envelope, err := consumerv1.EncodeEffectiveStandardFlowDocument(
			effectiveVersion,
			effectiveBehavior,
		)
		if err != nil {
			return nil, fmt.Errorf("encode effective standard Flow authority %q: %w", component.BusinessKey, err)
		}
		result = append(result, catalogapp.EffectiveMetadataComponent{
			ComponentKind:          consumerv1.EffectiveStandardFlowComponentKind,
			ComponentFormatVersion: 1,
			BusinessKey:            component.BusinessKey,
			ContentDigest: consumerv1.ComputeEffectiveMetadataComponentDigest(
				envelope,
			),
			CanonicalBehavior: envelope,
		})
	}
	sort.Slice(result, func(left, right int) bool {
		return result[left].BusinessKey < result[right].BusinessKey
	})
	return result, nil
}
