package records

import (
	"os"
	"path/filepath"
	"runtime"
	"strings"
	"testing"

	"github.com/nexuscrm/shared/pkg/constants"
	"github.com/nexuscrm/shared/pkg/models"
)

func TestUpdateAuditBridgeHasExplicitDeathCondition(t *testing.T) {
	updateSrc := readRecordsSourceForAuditBridge(t, "audit_update.go")
	legacySrc := readRecordsSourceForAuditBridge(t, "audit_legacy.go")
	v2Src := readRecordsSourceForAuditBridge(t, "audit_v2_wiring.go")
	changesSrc := readRecordsSourceForAuditBridge(t, "audit_changes.go")
	persistenceSrc := readRecordsSourceForAuditBridge(t, "persistence_service.go")
	updateHelpersSrc := readRecordsSourceForAuditBridge(t, "persistence_update_helpers.go")

	gateCall := strings.Index(updateSrc, "ps.auditV2Emitter != nil")
	legacyCall := strings.Index(updateSrc, "ps.generateAuditLogs(")
	v2Call := strings.Index(updateSrc, "ps.emitUpdateAuditV2(")
	if gateCall < 0 {
		t.Fatal("emitUpdateAudits must branch on graph-local v2 producer injection")
	}
	if legacyCall < 0 {
		t.Fatal("emitUpdateAudits is missing the legacy audit fallback")
	}
	if v2Call < 0 {
		t.Fatal("emitUpdateAudits is missing the audit v2 writer")
	}
	if !(gateCall < v2Call && v2Call < legacyCall) {
		t.Fatal("emitUpdateAudits must select the injected v2 producer before falling back to legacy")
	}
	if !strings.Contains(v2Src, "ps.auditV2Emitter == nil") {
		t.Fatal("audit v2 update writer must remain graph-local injection gated")
	}
	for _, reject := range []string{
		"writes the legacy audit rows first, then the gated v2 event",
		"while both audit storage paths coexist",
	} {
		if strings.Contains(updateSrc, reject) || strings.Contains(v2Src, reject) {
			t.Fatalf("update audit bridge still documents dual-write behavior %q", reject)
		}
	}
	changesOwner := strings.Index(changesSrc, "func (ps *PersistenceService) recordAuditFieldChanges(")
	if changesOwner < 0 {
		t.Fatal("recordAuditFieldChanges owner is missing")
	}
	if strings.Contains(changesSrc[changesOwner:], "ps.metadata.GetSchema(") {
		t.Fatal("recordAuditFieldChanges must consume the mutation-admitted schema instead of reopening metadata")
	}
	if !strings.Contains(updateHelpersSrc, "ps.emitUpdateAuditsForRecordUpdate(sideEffectCtx, schema,") {
		t.Fatal("the Update audit bridge must receive the same schema admitted before the retry loop")
	}
	for _, want := range []string{
		"func (ps *PersistenceService) emitUpdateAuditsForRecordUpdate(",
		"recordUpdateAuditPayload(",
		"complete.emitUpdateAudits",
		"must not both run",
		"explicitly injected",
	} {
		if !strings.Contains(updateSrc, want) {
			t.Fatalf("audit_update.go is missing update-audit bridge ownership marker %q", want)
		}
	}
	for _, reject := range []string{
		"auditUpdates :=",
		"ps.emitUpdateAudits(txCtx, objectName, id,",
	} {
		if strings.Contains(persistenceSrc, reject) {
			t.Fatalf("persistence_service.go still owns update-audit bridge fragment %q", reject)
		}
	}
	for _, want := range []string{
		"Death condition:",
		"fallback",
		"v2 is default-on",
		"every reader consumes the v2",
	} {
		if !strings.Contains(legacySrc, want) {
			t.Fatalf("legacy update audit writer is missing death-condition text %q", want)
		}
	}
}

func TestRecordUpdateAuditPayloadRedactsUserPasswordOnly(t *testing.T) {
	userUpdates := models.SObject{
		constants.FieldPassword: "plain-secret",
		constants.FieldName:     "Ada",
	}
	got := recordUpdateAuditPayload(constants.TableUser, userUpdates)
	if _, ok := got[constants.FieldPassword]; ok {
		t.Fatalf("recordUpdateAuditPayload(User) leaked Password: %#v", got)
	}
	if got[constants.FieldName] != "Ada" {
		t.Fatalf("recordUpdateAuditPayload(User) = %#v, want Name retained", got)
	}
	if _, ok := userUpdates[constants.FieldPassword]; !ok {
		t.Fatal("recordUpdateAuditPayload mutated source updates; Password should remain on write payload")
	}

	accountUpdates := models.SObject{
		constants.FieldName: "Acme",
	}
	if got := recordUpdateAuditPayload("Account", accountUpdates); len(got) != 1 || got[constants.FieldName] != "Acme" {
		t.Fatalf("recordUpdateAuditPayload(non-User) = %#v, want original fields retained", got)
	}
}

func readRecordsSourceForAuditBridge(t *testing.T, filename string) string {
	t.Helper()

	_, here, _, ok := runtime.Caller(0)
	if !ok {
		t.Fatal("runtime.Caller failed")
	}
	raw, err := os.ReadFile(filepath.Join(filepath.Dir(here), filename))
	if err != nil {
		t.Fatalf("read %s: %v", filename, err)
	}
	return string(raw)
}
