// Package packspec provides read access to the standard objects pack
// specification. It is a standalone package with no dependency on bootstrap
// or packs, breaking the import cycle between them.
package packspec

import (
	_ "embed"
	"encoding/json"
	"strings"

	domainschema "github.com/nexuscrm/backend/internal/domain/schema"
	"github.com/nexuscrm/shared/pkg/constants"
	"github.com/nexuscrm/shared/pkg/models"
)

//go:embed standard_objects_spec.json
var standardObjectsSpecJSON []byte

var tenantFieldManifestJSON []byte

// RegisterTenantFieldManifest is called only after an operator explicitly
// selects a tenant overlay directory. Platform code reads this via
// TenantFieldManifestJSON() to know which fields to skip during
// apply/verify/provenance. Default platform processes leave it empty.
func RegisterTenantFieldManifest(manifest []byte) {
	tenantFieldManifestJSON = manifest
}

// StandardObjectsSpecJSON returns the raw pack spec JSON for external consumers
// (e.g., the PlatformCore pack applier).
func StandardObjectsSpecJSON() []byte {
	return standardObjectsSpecJSON
}

// TenantFieldManifestJSON returns the tenant field ownership manifest selected
// by an explicit tenant overlay load. Default platform processes do not import
// tenant packages, so this is empty unless ApplyTenant or test setup registers
// an overlay manifest.
func TenantFieldManifestJSON() []byte {
	return tenantFieldManifestJSON
}

// objectSpec is deliberately narrower than packs.objectSpec — it lacks Fields.
// Do NOT reuse for tenant_objects_spec.json parsing; use packs.parseTenantObjectsSpec instead.
type objectSpec struct {
	APIName                string                     `json:"apiName"`
	SharingModel           string                     `json:"sharingModel"`
	EnableHierarchySharing bool                       `json:"enableHierarchySharing"`
	AccessPolicy           *models.ObjectAccessPolicy `json:"accessPolicy,omitempty"`
	ManagedRelationships   []relationshipSpec         `json:"managedRelationships,omitempty"`
}

type relationshipSpec struct {
	FieldAPIName    string `json:"fieldApiName"`
	ParentObject    string `json:"parentObject"`
	IsSharingParent *bool  `json:"isSharingParent,omitempty"`
	CascadeDelete   *bool  `json:"cascadeDelete,omitempty"`
}

type compoundObjectSpec struct {
	APIName string              `json:"apiName"`
	Fields  []compoundFieldSpec `json:"fields"`
}

type compoundFieldSpec struct {
	APIName           string `json:"apiName"`
	CompoundFieldName string `json:"compoundFieldName,omitempty"`
}

// StandardObjectPolicies returns the managed access policies for all standard
// objects, read from the pack spec (single source of truth).
func StandardObjectPolicies() map[string]domainschema.ManagedStandardObjectPolicy {
	var specs []objectSpec
	if err := json.Unmarshal(standardObjectsSpecJSON, &specs); err != nil {
		panic("packspec: corrupt embedded standard_objects_spec.json: " + err.Error())
	}
	policies := make(map[string]domainschema.ManagedStandardObjectPolicy, len(specs))
	for _, spec := range specs {
		if spec.SharingModel != "" {
			policies[strings.ToLower(spec.APIName)] = domainschema.ManagedStandardObjectPolicy{
				SharingModel:           spec.SharingModel,
				EnableHierarchySharing: spec.EnableHierarchySharing,
				AccessPolicy:           spec.AccessPolicy,
			}
		}
	}
	return policies
}

// StandardObjectPolicy returns the managed access policy for a single standard
// object, read from the pack spec.
func StandardObjectPolicy(apiName string) (domainschema.ManagedStandardObjectPolicy, bool) {
	policies := StandardObjectPolicies()
	p, ok := policies[strings.ToLower(apiName)]
	return p, ok
}

// StandardRelationshipSemantics returns the managed relationship semantics,
// read from the pack spec (single source of truth).
func StandardRelationshipSemantics() []domainschema.ManagedRelationshipSemantics {
	var specs []objectSpec
	if err := json.Unmarshal(standardObjectsSpecJSON, &specs); err != nil {
		panic("packspec: corrupt embedded standard_objects_spec.json: " + err.Error())
	}
	var result []domainschema.ManagedRelationshipSemantics
	for _, spec := range specs {
		for _, rel := range spec.ManagedRelationships {
			sem := domainschema.ManagedRelationshipSemantics{
				ChildObjectAPIName:  spec.APIName,
				ParentObjectAPIName: rel.ParentObject,
				FieldAPIName:        rel.FieldAPIName,
				IsSharingParent:     rel.IsSharingParent,
				CascadeDelete:       rel.CascadeDelete,
			}
			result = append(result, sem)
		}
	}
	return result
}

// LegacyStandardObjectSharingModels returns legacy sharing models that need
// special handling during reconciliation (backward-compat migration).
// These were previously hardcoded in managed_permissions.go.
func LegacyStandardObjectSharingModels() map[string]constants.SharingModel {
	return map[string]constants.SharingModel{
		"account":  constants.SharingModelPublicRead,
		"campaign": constants.SharingModelPublicReadWrite,
		"contract": constants.SharingModelPrivate,
		"order":    constants.SharingModelPrivate,
		"quote":    constants.SharingModelPrivate,
	}
}

// IsLegacyStandardObjectSharingModel checks if the current sharing model
// matches a known legacy default that should be auto-repaired.
func IsLegacyStandardObjectSharingModel(apiName string, currentModel constants.SharingModel) bool {
	legacy, ok := LegacyStandardObjectSharingModels()[strings.ToLower(apiName)]
	return ok && strings.EqualFold(string(legacy), string(currentModel))
}

// CompoundFieldComponents returns the ordered field API names that roll up into
// the given compound field for an object, using the embedded pack specs as the
// metadata source of truth. Tenant spec fields are appended only when the
// standard spec does not already declare the same field.
func CompoundFieldComponents(objectAPIName, compoundFieldAPIName string) []string {
	if objectAPIName == "" || compoundFieldAPIName == "" {
		return nil
	}

	merged := make([]compoundFieldSpec, 0)
	seen := make(map[string]bool)
	appendFields := func(specJSON []byte) {
		if len(specJSON) == 0 {
			return
		}
		var specs []compoundObjectSpec
		if err := json.Unmarshal(specJSON, &specs); err != nil {
			panic("packspec: corrupt compound field spec: " + err.Error())
		}
		for _, spec := range specs {
			if !strings.EqualFold(spec.APIName, objectAPIName) {
				continue
			}
			for _, field := range spec.Fields {
				key := strings.ToLower(field.APIName)
				if seen[key] {
					continue
				}
				seen[key] = true
				merged = append(merged, field)
			}
			return
		}
	}

	appendFields(standardObjectsSpecJSON)
	appendFields(tenantFieldManifestJSON)

	components := make([]string, 0)
	for _, field := range merged {
		if strings.EqualFold(field.CompoundFieldName, compoundFieldAPIName) {
			components = append(components, field.APIName)
		}
	}
	return components
}
