package catalogreconcile

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

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

// EffectiveObjectActionComponentKind is an opaque AppMetadataV3 component.
// It keeps object-scoped action metadata inside the same immutable Catalog
// revision as object schema and page layouts; serving never reads the live
// metadata repository.
const EffectiveObjectActionComponentKind = "metadata.effective.object-action.v1"

type EffectiveObjectActionMetadataReader interface {
	GetAllActions(context.Context) ([]*models.ActionMetadata, error)
}

type effectiveObjectActionDocument struct {
	ObjectAPIName string                        `json:"objectApiName"`
	Actions       []effectiveObjectActionRecord `json:"actions"`
}

type effectiveObjectActionRecord struct {
	ID           string         `json:"id"`
	Name         string         `json:"name"`
	Label        string         `json:"label"`
	Type         string         `json:"type"`
	Icon         string         `json:"icon,omitempty"`
	TargetObject string         `json:"targetObject,omitempty"`
	Config       map[string]any `json:"config,omitempty"`
}

func buildEffectiveObjectActionComponents(
	ctx context.Context,
	reader EffectiveObjectActionMetadataReader,
	objects []string,
) ([]catalogapp.EffectiveMetadataComponent, error) {
	if reader == nil {
		return nil, fmt.Errorf("effective object-action publication requires reader")
	}
	allActions, err := reader.GetAllActions(ctx)
	if err != nil {
		return nil, fmt.Errorf("read effective object actions: %w", err)
	}
	objectNames := make(map[string]string, len(objects))
	for _, objectName := range objects {
		objectNames[strings.ToLower(objectName)] = objectName
	}
	actionsByObject := make(map[string][]*models.ActionMetadata, len(objects))
	for index, action := range allActions {
		if action == nil {
			return nil, fmt.Errorf("effective object action[%d] is nil", index)
		}
		objectName, selected := objectNames[strings.ToLower(strings.TrimSpace(action.ObjectAPIName))]
		if selected {
			actionsByObject[objectName] = append(actionsByObject[objectName], action)
		}
	}
	result := make([]catalogapp.EffectiveMetadataComponent, 0, len(objects))
	for _, objectName := range objects {
		actions := actionsByObject[objectName]
		document := effectiveObjectActionDocument{
			ObjectAPIName: objectName,
			Actions:       make([]effectiveObjectActionRecord, 0, len(actions)),
		}
		seen := make(map[string]struct{}, len(actions))
		for index, action := range actions {
			if action == nil {
				return nil, fmt.Errorf("effective object actions %q[%d] is nil", objectName, index)
			}
			name := strings.TrimSpace(action.Name)
			if name == "" || strings.TrimSpace(action.Label) == "" ||
				strings.TrimSpace(action.Type) == "" ||
				!strings.EqualFold(strings.TrimSpace(action.ObjectAPIName), objectName) {
				return nil, fmt.Errorf("effective object action %q[%d] is invalid", objectName, index)
			}
			key := strings.ToLower(name)
			if _, duplicate := seen[key]; duplicate {
				return nil, fmt.Errorf("effective object actions %q contain duplicate %q", objectName, name)
			}
			seen[key] = struct{}{}
			target := ""
			if action.TargetObject != nil {
				target = strings.TrimSpace(*action.TargetObject)
			}
			document.Actions = append(document.Actions, effectiveObjectActionRecord{
				ID: strings.TrimSpace(action.ID), Name: name,
				Label: strings.TrimSpace(action.Label), Type: strings.TrimSpace(action.Type),
				Icon: strings.TrimSpace(action.Icon), TargetObject: target,
				Config: action.Config,
			})
		}
		sort.Slice(document.Actions, func(left, right int) bool {
			return document.Actions[left].Name < document.Actions[right].Name
		})
		encoded, err := json.Marshal(document)
		if err != nil {
			return nil, fmt.Errorf("encode effective object actions %q: %w", objectName, err)
		}
		canonical, err := componentbehavior.CanonicalJSONV1(encoded)
		if err != nil {
			return nil, fmt.Errorf("canonicalize effective object actions %q: %w", objectName, err)
		}
		result = append(result, catalogapp.EffectiveMetadataComponent{
			ComponentKind:          EffectiveObjectActionComponentKind,
			ComponentFormatVersion: 1,
			BusinessKey:            objectName,
			ContentDigest:          consumerv1.ComputeEffectiveMetadataComponentDigest(canonical),
			CanonicalBehavior:      canonical,
		})
	}
	return result, nil
}

var _ EffectiveObjectActionMetadataReader = (*RepositoryMetadataReader)(nil)
