package records

import (
	"sort"
	"testing"

	"github.com/nexuscrm/shared/pkg/contract"
)

// L0-D H3 guardrail: every write-validation (and write-canonicalization)
// capability DECLARED by a managed table definition must have a REGISTERED
// built-in validator (canonicalizer) in the universal, app-blind records
// registry.
//
// This is the systemic invariant that would have caught the D5 mis-carve. D5
// tried to move the SharingRule shape validator out of the app-blind records
// engine and into a CRM-only L1 registration while leaving the
// platform.security.write.sharing-rule-shape capability DECLARED for SharingRule
// in system_tables.json (which ships in ALL distributions). The effect: in any
// non-CRM distribution the capability stays declared but no universal registrar
// supplies the validator, so runRecordWriteValidators fails closed —
// "missing record write validator for <phase> capability ..." — hard-DENYing
// every SharingRule write. Local unit-green never caught it because the CRM
// test distribution registered the L1 validator, and the controlplane
// reconciliation check (AssertManagedWriteValidationCapabilitiesReconciled) only
// compares DECLARED capabilities against embedded metadata — it is metadata-only
// and never instantiates the runtime registry, so it cannot see a declared
// capability that lacks a universal registrar. This test closes that gap by
// asserting declaration <-> universal runtime registration coverage directly.
//
// It is deliberately app-blind / universal build (NO build tag): the built-in
// registrars it exercises (registerBuiltInSecurityWrite*, registerBuiltInFolderWrite*)
// are the ones every distribution wires in NewPersistenceService, and every
// capability the shipped system_tables.json declares must be covered by exactly
// those universal registrars. A capability that is only registered under a
// CRM-only build tag would (correctly) FAIL here, because it would hard-DENY the
// declared object's writes in non-CRM distributions — which is exactly the D5
// class of defect.

// declaredWriteCapability is a (object, phase, capability) triple declared by a
// managed table definition's write-validation access policy.
type declaredWriteCapability struct {
	object     string
	phase      contract.RecordWriteValidationPhase
	capability string
}

// declaredWriteValidationCapabilities collects every (object, phase, capability)
// validator triple declared across all managed table definitions.
func declaredWriteValidationCapabilities() []declaredWriteCapability {
	var out []declaredWriteCapability
	for _, def := range managedTableDefinitions() {
		if def.AccessPolicy == nil || def.AccessPolicy.WriteValidation == nil {
			continue
		}
		for _, v := range def.AccessPolicy.WriteValidation.Validators {
			if v.Capability == "" {
				continue
			}
			out = append(out, declaredWriteCapability{
				object:     def.TableName,
				phase:      contract.RecordWriteValidationPhase(v.Phase),
				capability: v.Capability,
			})
		}
	}
	return out
}

// declaredWriteCanonicalizationCapabilities collects every (object, phase,
// capability) canonicalizer triple declared across all managed table definitions.
func declaredWriteCanonicalizationCapabilities() []declaredWriteCapability {
	var out []declaredWriteCapability
	for _, def := range managedTableDefinitions() {
		if def.AccessPolicy == nil || def.AccessPolicy.WriteValidation == nil {
			continue
		}
		for _, c := range def.AccessPolicy.WriteValidation.Canonicalizers {
			if c.Capability == "" {
				continue
			}
			out = append(out, declaredWriteCapability{
				object:     def.TableName,
				phase:      contract.RecordWriteValidationPhase(c.Phase),
				capability: c.Capability,
			})
		}
	}
	return out
}

// universalBuiltInWriteRegistries instantiates a records PersistenceService and
// runs the universal (app-blind) built-in write validator/canonicalizer
// registrars — the same set NewPersistenceService wires for every distribution —
// then returns the populated registries. A bare &PersistenceService{} is
// sufficient: the registrars only need a non-nil service and lazily create the
// registries; the registered validator/canonicalizer closures are not invoked
// here.
func universalBuiltInWriteRegistries(t *testing.T) (*recordWriteValidators, *recordWriteCanonicalizers) {
	t.Helper()
	ps := &PersistenceService{}
	ps.registerBuiltInSecurityWriteValidators()
	ps.registerBuiltInSecurityWriteCanonicalizers()
	ps.registerBuiltInFolderWriteValidators()
	ps.registerBuiltInFolderWriteCanonicalizers()
	if ps.writeValidators == nil {
		t.Fatal("no built-in write validators were registered; the guardrail cannot verify coverage")
	}
	if ps.writeCanonicalizers == nil {
		t.Fatal("no built-in write canonicalizers were registered; the guardrail cannot verify coverage")
	}
	return ps.writeValidators, ps.writeCanonicalizers
}

// registeredValidatorMapForPhase returns the per-phase registered-validator map
// for phase, or (nil, false) if the phase is not a known validator phase.
func registeredValidatorMapForPhase(v *recordWriteValidators, phase contract.RecordWriteValidationPhase) (map[string]recordWriteValidator, bool) {
	switch phase {
	case recordWriteValidationBeforeCreate:
		return v.capabilityBeforeCreate, true
	case recordWriteValidationBeforeUpdate:
		return v.capabilityBeforeUpdate, true
	case recordWriteValidationBeforeDelete:
		return v.capabilityBeforeDelete, true
	case recordWriteValidationBeforeRestore:
		return v.capabilityBeforeRestore, true
	case recordWriteValidationBeforePurge:
		return v.capabilityBeforePurge, true
	default:
		return nil, false
	}
}

// registeredCanonicalizerMapForPhase returns the per-phase registered-
// canonicalizer map for phase, or (nil, false) if the phase is not a known
// canonicalizer phase.
func registeredCanonicalizerMapForPhase(c *recordWriteCanonicalizers, phase contract.RecordWriteValidationPhase) (map[string]recordWriteCanonicalizer, bool) {
	switch phase {
	case recordWriteValidationBeforeCreate:
		return c.capabilityBeforeCreate, true
	case recordWriteValidationBeforeUpdate:
		return c.capabilityBeforeUpdate, true
	case recordWriteValidationBeforeRestore:
		return c.capabilityBeforeRestore, true
	default:
		return nil, false
	}
}

// TestEveryDeclaredWriteValidationCapabilityHasRegisteredValidator asserts that
// every (object, phase, capability) write-validation capability declared by a
// managed table definition has a matching validator registered in the universal
// built-in records registry. A declared capability with no universal registrar
// is the D5 hole: it hard-DENYs the declared object's writes in every
// distribution that does not separately register the validator.
func TestEveryDeclaredWriteValidationCapabilityHasRegisteredValidator(t *testing.T) {
	validators, _ := universalBuiltInWriteRegistries(t)

	declared := declaredWriteValidationCapabilities()
	if len(declared) == 0 {
		t.Fatal("no write-validation capabilities are declared by any managed table definition; the guardrail has nothing to verify — this almost certainly means the declaration source could not be read")
	}

	var missing []string
	for _, d := range declared {
		phaseMap, known := registeredValidatorMapForPhase(validators, d.phase)
		if !known {
			missing = append(missing, formatMissingCapability(d, "unknown validator phase"))
			continue
		}
		if _, ok := phaseMap[recordWriteValidatorCapabilityKey(d.capability)]; !ok {
			missing = append(missing, formatMissingCapability(d, "no universally-registered validator"))
		}
	}
	reportMissingCoverage(t, "write-validation validator", missing)
}

// TestEveryDeclaredWriteCanonicalizationCapabilityHasRegisteredCanonicalizer is
// the canonicalizer counterpart of the validator coverage guardrail: a declared
// canonicalizer capability with no universal registrar hard-DENYs the declared
// object's writes in the same way (runRecordWriteCanonicalizers fails closed on
// a missing canonicalizer).
func TestEveryDeclaredWriteCanonicalizationCapabilityHasRegisteredCanonicalizer(t *testing.T) {
	_, canonicalizers := universalBuiltInWriteRegistries(t)

	declared := declaredWriteCanonicalizationCapabilities()
	if len(declared) == 0 {
		t.Fatal("no write-canonicalization capabilities are declared by any managed table definition; the guardrail has nothing to verify — this almost certainly means the declaration source could not be read")
	}

	var missing []string
	for _, d := range declared {
		phaseMap, known := registeredCanonicalizerMapForPhase(canonicalizers, d.phase)
		if !known {
			missing = append(missing, formatMissingCapability(d, "unknown canonicalizer phase"))
			continue
		}
		if _, ok := phaseMap[recordWriteCanonicalizerCapabilityKey(d.capability)]; !ok {
			missing = append(missing, formatMissingCapability(d, "no universally-registered canonicalizer"))
		}
	}
	reportMissingCoverage(t, "write-canonicalization canonicalizer", missing)
}

func formatMissingCapability(d declaredWriteCapability, reason string) string {
	return d.capability + " (object=" + d.object + " phase=" + string(d.phase) + "): " + reason
}

func reportMissingCoverage(t *testing.T, kind string, missing []string) {
	t.Helper()
	if len(missing) == 0 {
		return
	}
	sort.Strings(missing)
	for _, m := range missing {
		t.Errorf("declared %s capability lacks universal built-in registration: %s; a declared capability with no universal registrar hard-DENYs the object's writes in every distribution that does not separately register it (the D5 mis-carve class)", kind, m)
	}
}
