package catalogreconcile

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

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

const appReleaseInvocableActionComponentKind = "metadata.pack.invocable-action.v1"

type EffectiveStandardActionMetadataReader interface {
	GetEffectiveStandardActions(context.Context, []string) ([]seedrows.StandardActionDef, error)
}

var effectiveStandardActionKnownFields = []string{
	"name", "label", "description", "flowDeveloperName",
	"previewFlowDeveloperName", "category", "requiredPermissions",
	"rendererId", "targetObjects", "headerFallbackEligible", "isAtomic",
	"sortOrder", "inputs", "outputs", "fieldDefaults", "inputGuards",
	"visibilityCondition", "internalAccessScopes",
}

func decodeStandardActionBehavior(
	behavior []byte,
) ([]map[string]json.RawMessage, []string, error) {
	decoder := json.NewDecoder(bytes.NewReader(behavior))
	decoder.UseNumber()
	var definitions []map[string]json.RawMessage
	if err := decoder.Decode(&definitions); err != nil || definitions == nil {
		return nil, nil, fmt.Errorf("invocable-action behavior must be a JSON array")
	}
	var trailing any
	if err := decoder.Decode(&trailing); err != io.EOF {
		return nil, nil, fmt.Errorf("invocable-action behavior contains trailing JSON")
	}
	names := make([]string, 0, len(definitions))
	seen := make(map[string]struct{}, len(definitions))
	for index, definition := range definitions {
		var name string
		if err := json.Unmarshal(definition["name"], &name); err != nil ||
			strings.TrimSpace(name) == "" || name != strings.TrimSpace(name) {
			return nil, nil, fmt.Errorf("invocable-action behavior[%d] has invalid name", index)
		}
		key := strings.ToLower(name)
		if _, duplicate := seen[key]; duplicate {
			return nil, nil, fmt.Errorf("invocable-action behavior has duplicate name %q", name)
		}
		seen[key] = struct{}{}
		names = append(names, name)
	}
	return definitions, names, nil
}

func mergeEffectiveStandardActionBehavior(
	base []map[string]json.RawMessage,
	effective []seedrows.StandardActionDef,
) ([]byte, error) {
	baseByName := make(map[string]map[string]json.RawMessage, len(base))
	for _, definition := range base {
		var name string
		if err := json.Unmarshal(definition["name"], &name); err != nil {
			return nil, err
		}
		baseByName[strings.ToLower(name)] = definition
	}
	merged := make([]map[string]json.RawMessage, 0, len(effective))
	seen := make(map[string]struct{}, len(effective))
	for _, definition := range effective {
		key := strings.ToLower(strings.TrimSpace(definition.Name))
		if key == "" {
			return nil, fmt.Errorf("effective standard action has empty name")
		}
		if _, duplicate := seen[key]; duplicate {
			return nil, fmt.Errorf("effective standard actions contain duplicate name %q", definition.Name)
		}
		seen[key] = struct{}{}
		value := make(map[string]json.RawMessage)
		if authored, ok := baseByName[key]; ok {
			for field, raw := range authored {
				value[field] = append(json.RawMessage(nil), raw...)
			}
		}
		for _, field := range effectiveStandardActionKnownFields {
			delete(value, field)
		}
		encoded, err := json.Marshal(definition)
		if err != nil {
			return nil, err
		}
		var projected map[string]json.RawMessage
		if err := json.Unmarshal(encoded, &projected); err != nil {
			return nil, err
		}
		for field, raw := range projected {
			value[field] = raw
		}
		merged = append(merged, value)
	}
	sort.Slice(merged, func(left, right int) bool {
		var leftName, rightName string
		_ = json.Unmarshal(merged[left]["name"], &leftName)
		_ = json.Unmarshal(merged[right]["name"], &rightName)
		return strings.ToLower(leftName) < strings.ToLower(rightName)
	})
	encoded, err := json.Marshal(merged)
	if err != nil {
		return nil, err
	}
	return componentbehavior.CanonicalJSONV1(encoded)
}

func buildEffectiveStandardActionComponents(
	ctx context.Context,
	reader EffectiveStandardActionMetadataReader,
	release *appauthority.VerifiedPackage,
) ([]catalogapp.EffectiveMetadataComponent, error) {
	if reader == nil || release == nil {
		return nil, fmt.Errorf("effective standard-action publication requires reader and verified AppRelease")
	}
	verified, err := appauthority.VerifyPackage(release.Package)
	if err != nil {
		return nil, fmt.Errorf("verify effective standard-action AppRelease: %w", err)
	}
	if verified.AppReleaseDigest != release.AppReleaseDigest {
		return nil, fmt.Errorf("effective standard-action AppRelease identity disagrees with its verified package")
	}
	var result []catalogapp.EffectiveMetadataComponent
	for index, component := range verified.Bundle.Components {
		if component.ComponentKind != appReleaseInvocableActionComponentKind {
			continue
		}
		if component.ComponentFormatVersion != 1 {
			return nil, fmt.Errorf("invocable-action component %q has unsupported format version %d", component.ComponentID, component.ComponentFormatVersion)
		}
		base, names, err := decodeStandardActionBehavior(
			verified.ComponentBlobs[index].CanonicalBehavior,
		)
		if err != nil {
			return nil, fmt.Errorf("read AppRelease invocable actions: %w", err)
		}
		effective, err := reader.GetEffectiveStandardActions(ctx, names)
		if err != nil {
			return nil, fmt.Errorf("read effective standard actions: %w", err)
		}
		behavior, err := mergeEffectiveStandardActionBehavior(base, effective)
		if err != nil {
			return nil, fmt.Errorf("encode effective standard actions: %w", err)
		}
		baseBehavior, err := componentbehavior.CanonicalJSONV1(
			verified.ComponentBlobs[index].CanonicalBehavior,
		)
		if err != nil {
			return nil, fmt.Errorf("canonicalize AppRelease invocable actions: %w", err)
		}
		if bytes.Equal(behavior, baseBehavior) {
			continue
		}
		if component.OverridePolicy != appauthority.OverridePolicyPreserveAuthorized {
			return nil, fmt.Errorf("invocable-action component %q does not authorize an effective override", component.BusinessKey)
		}
		result = append(result, catalogapp.EffectiveMetadataComponent{
			ComponentKind:          consumerv1.EffectiveStandardActionComponentKind,
			ComponentFormatVersion: 1,
			BusinessKey:            component.BusinessKey,
			ContentDigest: consumerv1.ComputeEffectiveMetadataComponentDigest(
				behavior,
			),
			CanonicalBehavior: behavior,
		})
	}
	if len(result) > 1 {
		return nil, fmt.Errorf("AppRelease contains multiple effective invocable-action registries")
	}
	return result, nil
}
