package metadata

import (
	"fmt"
	"slices"

	"github.com/nexuscrm/shared/pkg/models"
	"github.com/pingcap-inc/nexuscrm-contract/pkg/metadatair"
)

// ObjectFieldIdentityShadowReport records both sides of an object/field
// identity shadow check. It is diagnostic output only; runtime reads remain on
// the existing metadata structs until a later cutover switches a consumer.
type ObjectFieldIdentityShadowReport struct {
	Runtime ObjectFieldIdentityProjection
	IR      ObjectFieldIdentityProjection
}

// FlowElementIdentityShadowReport records both sides of a Flow element identity
// shadow check. It is diagnostic output only; runtime execution still reads
// FlowDefinition/FlowVersion models directly.
type FlowElementIdentityShadowReport struct {
	Runtime FlowElementIdentityProjection
	IR      FlowElementIdentityProjection
}

// VerifyObjectFieldIdentityShadow compares the existing runtime object/field
// identity projection with the neutral IR header projection for the same source
// component. It does not publish or cache IR as a runtime read path.
func VerifyObjectFieldIdentityShadow(schema *models.ObjectMetadata, doc metadatair.Document) (ObjectFieldIdentityShadowReport, error) {
	runtime, err := runtimeObjectFieldIdentity(schema)
	if err != nil {
		return ObjectFieldIdentityShadowReport{}, err
	}
	ir, err := ProjectObjectFieldIdentityHeaders(doc)
	if err != nil {
		return ObjectFieldIdentityShadowReport{}, err
	}
	report := ObjectFieldIdentityShadowReport{
		Runtime: runtime,
		IR:      ir,
	}
	if runtime.ObjectAPIName != ir.ObjectAPIName || !slices.Equal(runtime.FieldAPINames, ir.FieldAPINames) {
		return report, fmt.Errorf("metadata IR object field shadow mismatch: runtime object %q fields %v, ir object %q fields %v", runtime.ObjectAPIName, runtime.FieldAPINames, ir.ObjectAPIName, ir.FieldAPINames)
	}
	return report, nil
}

// VerifyFlowElementIdentityShadow compares the existing runtime Flow element
// identity projection with the neutral IR header projection for the same
// definition/version context. It does not publish or cache IR as a runtime read
// path.
func VerifyFlowElementIdentityShadow(definition *models.FlowDefinition, version *models.FlowVersion, doc metadatair.Document) (FlowElementIdentityShadowReport, error) {
	runtime, err := runtimeFlowElementIdentity(definition, version)
	if err != nil {
		return FlowElementIdentityShadowReport{}, err
	}
	ir, err := ProjectFlowElementIdentityHeaders(doc, metadatair.NodeID(runtime.VersionNodeID))
	if err != nil {
		return FlowElementIdentityShadowReport{}, err
	}
	report := FlowElementIdentityShadowReport{
		Runtime: runtime,
		IR:      ir,
	}
	if runtime.VersionNodeID != ir.VersionNodeID || !slices.Equal(runtime.ElementNames, ir.ElementNames) {
		return report, fmt.Errorf("metadata IR flow element shadow mismatch: runtime version %q elements %v, ir version %q elements %v", runtime.VersionNodeID, runtime.ElementNames, ir.VersionNodeID, ir.ElementNames)
	}
	return report, nil
}

func runtimeFlowElementIdentity(definition *models.FlowDefinition, version *models.FlowVersion) (FlowElementIdentityProjection, error) {
	definitionName, versionID, err := flowDefinitionVersionIdentity(definition, version)
	if err != nil {
		return FlowElementIdentityProjection{}, err
	}
	elements, err := flowElementHeaders(version.Metadata)
	if err != nil {
		return FlowElementIdentityProjection{}, fmt.Errorf("metadata IR flow %s version %s elements invalid: %w", definitionName, versionID, err)
	}
	out := FlowElementIdentityProjection{
		VersionNodeID: string(versionID),
		ElementNames:  make([]string, 0, len(elements)),
	}
	for _, element := range elements {
		out.ElementNames = append(out.ElementNames, element.name)
	}
	return out, nil
}
