// White-box (package packs) DEEP-COMPARE + MUTATION route test for the
// href_rewrite_fields presentation kind in the SF-skin migration.
//
// cp.HrefRewriteField is field-for-field 1:1 with files.HrefRewriteField, so the
// reconstruction is loss-free. This is the richest of the four kinds: its internal
// component DOES expose ExpectedProjection() = the parsed []files.HrefRewriteField,
// and the builder also emits one runtime-startup shallow declaration per field.
// The deep-compare therefore bites on parsed-content drift (not just raw bytes),
// and a mutation sub-test corrupts one declared FieldApiName to prove it.
package packs

import (
	"encoding/json"
	"testing"

	"github.com/nexuscrm/files"
	cp "github.com/pingcap-inc/nexuscrm-contract/pkg/controlplane"
)

var fakeHrefRewriteFieldsHandlerFires int

// fakeHrefRewriteFieldsHandler is the inline contract-only stand-in for the real
// nexuscrm-app-packs provider.go:buildHrefRewriteFields.
func fakeHrefRewriteFieldsHandler(ctx cp.ComponentBuildContext, req cp.ComponentBuildRequest) error {
	pctx, ok := ctx.(cp.PresentationComponentBuildContext)
	if !ok {
		return errNotPresentationContext
	}
	fields, err := hrefRewriteFieldDeclarationsFromPayloadForTest(req.Payload)
	if err != nil {
		return err
	}
	fakeHrefRewriteFieldsHandlerFires++
	return pctx.AddHrefRewriteFieldsComponent(req, fields)
}

func hrefRewriteFieldDeclarationsFromPayloadForTest(payload []byte) ([]cp.HrefRewriteField, error) {
	var parsed struct {
		Fields []cp.HrefRewriteField `json:"fields"`
	}
	if err := json.Unmarshal(payload, &parsed); err != nil {
		return nil, err
	}
	return parsed.Fields, nil
}

func hrefRewriteFieldsRoutes(t *testing.T, handler cp.ComponentBuildHandler) map[ComponentKind]externalBuildRoute {
	t.Helper()
	routes := newExternalBuildRoutes(
		[]cp.ComponentKindRegistration{{
			Contract:     cp.ComponentKindContract{Kind: cp.ComponentKindHrefRewriteFields},
			Capability:   cp.SourceBackedComponentCapability{Kind: cp.ComponentKindHrefRewriteFields, Installable: true},
			BuildHandler: handler,
		}},
		map[ComponentKind]struct{}{ComponentKindHrefRewriteFields: {}},
	)
	if _, ok := routes[ComponentKindHrefRewriteFields]; !ok {
		t.Fatalf("expected href_rewrite_fields to be routed externally; install gate dropped it")
	}
	return routes
}

func hrefRewriteFieldsComponentFromPack(t *testing.T, pack *SourceBackedPack) PackComponent {
	t.Helper()
	comps := componentsOfKindForRouteTest(t, pack, ComponentKindHrefRewriteFields)
	if len(comps) != 1 {
		t.Fatalf("expected exactly one href_rewrite_fields component, found %d", len(comps))
	}
	for _, c := range comps {
		return c
	}
	return nil
}

func TestExternalHrefRewriteFieldsRouteDeepCompare(t *testing.T) {
	root := officialAppPacksRootForRouteTest(t)
	if root == "" {
		t.Skip("no nexuscrm-app-packs checkout found; skipping external href_rewrite_fields deep-compare test")
	}
	setupSFRouteTestHost(t)

	routes := hrefRewriteFieldsRoutes(t, fakeHrefRewriteFieldsHandler)

	control := buildSFPackForRouteTest(t, root, nil)
	fakeHrefRewriteFieldsHandlerFires = 0
	routed := buildSFPackForRouteTest(t, root, routes)
	if fakeHrefRewriteFieldsHandlerFires == 0 {
		t.Fatalf("external href_rewrite_fields handler never fired; route did not engage (the deep-compare below would be a false-green)")
	}

	controlComp := hrefRewriteFieldsComponentFromPack(t, control)
	routedComp := hrefRewriteFieldsComponentFromPack(t, routed)

	// Guard: the projection MUST carry parsed field content; otherwise the
	// deep-compare would be as blind as the raw-byte payload and the mutation
	// test below could not bite.
	proj, err := controlComp.ExpectedProjection()
	if err != nil {
		t.Fatalf("control projection: %v", err)
	}
	fields, ok := proj.([]files.HrefRewriteField)
	if !ok {
		t.Fatalf("href_rewrite_fields ExpectedProjection type %T, want []files.HrefRewriteField", proj)
	}
	if len(fields) == 0 {
		t.Fatalf("href_rewrite_fields projection is empty; nothing to deep-compare")
	}

	if mismatch := assertRouteComponentsEqual(t, controlComp, routedComp); mismatch != "" {
		t.Errorf("DRIFT: external href_rewrite_fields route is lossy across the parsed surface.\n%s", mismatch)
	}
}

// TestExternalHrefRewriteFieldsRouteMutationCaught corrupts ONE parsed
// FieldApiName in the external declaration path. Unlike ui_action's free-form
// Label, an href field is content-bound: the engine sink validates every declared
// (parent, field) pair against the pack's object_spec, so a corrupted FieldApiName
// must be REJECTED at build time. That rejection IS the proof that the sink reads
// the declaration content (not just the raw payload bytes, which stay untouched).
// The control build (internal path, honest declaration) must still succeed, so the
// failure is attributable to the mutation alone.
func TestExternalHrefRewriteFieldsRouteMutationCaught(t *testing.T) {
	root := officialAppPacksRootForRouteTest(t)
	if root == "" {
		t.Skip("no nexuscrm-app-packs checkout found; skipping external href_rewrite_fields mutation test")
	}
	setupSFRouteTestHost(t)

	mutated := false
	mutatingHandler := func(ctx cp.ComponentBuildContext, req cp.ComponentBuildRequest) error {
		pctx, ok := ctx.(cp.PresentationComponentBuildContext)
		if !ok {
			return errNotPresentationContext
		}
		fields, err := hrefRewriteFieldDeclarationsFromPayloadForTest(req.Payload)
		if err != nil {
			return err
		}
		if len(fields) > 0 {
			fields[0].FieldApiName = fields[0].FieldApiName + "_corrupted"
			mutated = true
		}
		return pctx.AddHrefRewriteFieldsComponent(req, fields)
	}

	routes := hrefRewriteFieldsRoutes(t, mutatingHandler)

	// Control (honest internal path) must build cleanly.
	if _, err := buildSFPackOrErrForRouteTest(t, root, nil); err != nil {
		t.Fatalf("control SF pack build failed without any mutation: %v", err)
	}

	// Routed build with the corrupted declaration must FAIL — proving the sink is
	// content-bound, not raw-byte-blind.
	_, err := buildSFPackOrErrForRouteTest(t, root, routes)
	if !mutated {
		t.Fatalf("mutating handler never corrupted a field; nothing was tested")
	}
	if err == nil {
		t.Fatalf("MUTATION NOT CAUGHT: a corrupted FieldApiName produced a clean build; the content surface is blind")
	}
}
