package records

import (
	"context"
	"strings"
	"testing"

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

func TestPrepareUpdateRequestScrubsPolymorphicDiscriminator(t *testing.T) {
	schema := polymorphicTestSchema()
	ps := &PersistenceService{schemaValidator: validation.NewSchemaValidator()}
	input := models.SObject{
		"PolyId":      "005-user",
		"PolyId_type": "Group",
	}

	got, _, err := ps.prepareUpdateRequest(context.Background(), "ProbeObject", "a00-row", schema, input, nil)
	if err != nil {
		t.Fatalf("prepareUpdateRequest: %v", err)
	}
	if _, ok := got["PolyId_type"]; ok {
		t.Fatalf("prepareUpdateRequest preserved caller-supplied PolyId_type: %#v", got)
	}
	if got["PolyId"] != "005-user" {
		t.Fatalf("PolyId = %v, want 005-user", got["PolyId"])
	}
}

func TestRestorePolymorphicDiscriminatorsFromOldRecordDropsFlowInjection(t *testing.T) {
	schema := polymorphicTestSchema()
	recordToValidate := models.SObject{
		"Id":          "04i-row",
		"PolyId":      "005-user",
		"PolyId_type": "Group",
	}
	effectiveUpdates := models.SObject{
		"PolyId_type": "Group",
		"polyid_type": "Group",
	}
	oldRecord := models.SObject{
		"Id":          "04i-row",
		"PolyId":      "005-user",
		"PolyId_type": "User",
	}

	restorePolymorphicDiscriminatorsFromOldRecord(schema, recordToValidate, effectiveUpdates, oldRecord)

	if recordToValidate["PolyId_type"] != "User" {
		t.Fatalf("recordToValidate PolyId_type = %v, want User", recordToValidate["PolyId_type"])
	}
	if _, ok := effectiveUpdates["PolyId_type"]; ok {
		t.Fatalf("effectiveUpdates retained fake PolyId_type: %#v", effectiveUpdates)
	}
	if _, ok := effectiveUpdates["polyid_type"]; ok {
		t.Fatalf("effectiveUpdates retained fake polyid_type: %#v", effectiveUpdates)
	}
}

func TestReconcilePolymorphicDiscriminatorPreservesUnchangedLookup(t *testing.T) {
	schema := polymorphicTestSchema()
	ps := &PersistenceService{}
	recordToValidate := models.SObject{
		"Id":          "04i-row",
		"PolyId":      "005-user",
		"PolyId_type": "Group",
	}
	effectiveUpdates := models.SObject{
		"Name":        "updated",
		"PolyId_type": "Group",
	}
	oldRecord := models.SObject{
		"Id":          "04i-row",
		"PolyId":      "005-user",
		"PolyId_type": "User",
	}

	restorePolymorphicDiscriminatorsFromOldRecord(schema, recordToValidate, effectiveUpdates, oldRecord)
	if err := ps.reconcilePolymorphicDiscriminatorUpdates(context.Background(), schema, recordToValidate, effectiveUpdates, oldRecord); err != nil {
		t.Fatalf("reconcilePolymorphicDiscriminatorUpdates: %v", err)
	}

	if recordToValidate["PolyId_type"] != "User" {
		t.Fatalf("recordToValidate PolyId_type = %v, want User", recordToValidate["PolyId_type"])
	}
	if _, ok := effectiveUpdates["PolyId_type"]; ok {
		t.Fatalf("effectiveUpdates wrote PolyId_type for unchanged lookup: %#v", effectiveUpdates)
	}
}

func TestReconcilePolymorphicDiscriminatorClearsNilLookup(t *testing.T) {
	schema := polymorphicTestSchema()
	ps := &PersistenceService{}
	recordToValidate := models.SObject{
		"Id":          "04i-row",
		"PolyId":      nil,
		"PolyId_type": "Group",
	}
	effectiveUpdates := models.SObject{
		"PolyId":      nil,
		"PolyId_type": "Group",
	}
	oldRecord := models.SObject{
		"Id":          "04i-row",
		"PolyId":      "005-user",
		"PolyId_type": "User",
	}

	restorePolymorphicDiscriminatorsFromOldRecord(schema, recordToValidate, effectiveUpdates, oldRecord)
	if err := ps.reconcilePolymorphicDiscriminatorUpdates(context.Background(), schema, recordToValidate, effectiveUpdates, oldRecord); err != nil {
		t.Fatalf("reconcilePolymorphicDiscriminatorUpdates: %v", err)
	}

	if val, ok := recordToValidate["PolyId_type"]; !ok || val != nil {
		t.Fatalf("recordToValidate PolyId_type = %#v (present=%v), want nil", val, ok)
	}
	if val, ok := effectiveUpdates["PolyId_type"]; !ok || val != nil {
		t.Fatalf("effectiveUpdates PolyId_type = %#v (present=%v), want nil", val, ok)
	}
}

func TestReconcilePolymorphicDiscriminatorClearsEmptyLookup(t *testing.T) {
	schema := polymorphicTestSchema()
	ps := &PersistenceService{}
	recordToValidate := models.SObject{
		"Id":          "04i-row",
		"PolyId":      "",
		"PolyId_type": "Group",
	}
	effectiveUpdates := models.SObject{
		"PolyId": "",
	}
	oldRecord := models.SObject{
		"Id":          "04i-row",
		"PolyId":      "005-user",
		"PolyId_type": "User",
	}

	restorePolymorphicDiscriminatorsFromOldRecord(schema, recordToValidate, effectiveUpdates, oldRecord)
	if err := ps.reconcilePolymorphicDiscriminatorUpdates(context.Background(), schema, recordToValidate, effectiveUpdates, oldRecord); err != nil {
		t.Fatalf("reconcilePolymorphicDiscriminatorUpdates: %v", err)
	}

	if val, ok := recordToValidate["PolyId_type"]; !ok || val != nil {
		t.Fatalf("recordToValidate PolyId_type = %#v (present=%v), want nil", val, ok)
	}
	if val, ok := effectiveUpdates["PolyId_type"]; !ok || val != nil {
		t.Fatalf("effectiveUpdates PolyId_type = %#v (present=%v), want nil", val, ok)
	}
}

func TestReconcilePolymorphicDiscriminatorResolvesChangedLookup(t *testing.T) {
	schema := polymorphicTestSchema()
	ps := &PersistenceService{
		repo: &polymorphicExistsRepo{
			exists: map[string]map[string]bool{
				"User": {
					"005-user": true,
				},
			},
		},
	}
	recordToValidate := models.SObject{
		"Id":          "04i-row",
		"PolyId":      "005-user",
		"PolyId_type": "Group",
	}
	effectiveUpdates := models.SObject{
		"PolyId": "005-user",
	}
	oldRecord := models.SObject{
		"Id":          "04i-row",
		"PolyId":      "00G-group",
		"PolyId_type": "Group",
	}

	restorePolymorphicDiscriminatorsFromOldRecord(schema, recordToValidate, effectiveUpdates, oldRecord)
	if err := ps.reconcilePolymorphicDiscriminatorUpdates(context.Background(), schema, recordToValidate, effectiveUpdates, oldRecord); err != nil {
		t.Fatalf("reconcilePolymorphicDiscriminatorUpdates: %v", err)
	}

	if recordToValidate["PolyId_type"] != "User" {
		t.Fatalf("recordToValidate PolyId_type = %v, want User", recordToValidate["PolyId_type"])
	}
	if effectiveUpdates["PolyId_type"] != "User" {
		t.Fatalf("effectiveUpdates PolyId_type = %v, want User", effectiveUpdates["PolyId_type"])
	}
}

func TestValidatePolymorphicLookupsRejectsUserContextTargetOutsideWritableReferenceTo(t *testing.T) {
	schema := polymorphicTestSchema()
	schema.Fields[0].WritableReferenceTo = []string{"User"}
	ps := &PersistenceService{
		repo: &polymorphicExistsRepo{
			exists: map[string]map[string]bool{
				"Group": {
					"00G-group": true,
				},
			},
		},
	}

	_, err := ps.validatePolymorphicLookups(context.Background(), models.SObject{"PolyId": "00G-group"}, schema)
	if err == nil {
		t.Fatal("validatePolymorphicLookups accepted Group target outside WritableReferenceTo")
	}
	if !strings.Contains(err.Error(), "not writable") {
		t.Fatalf("error = %v, want writable-target rejection", err)
	}

	trustedCtx := constants.WithSecurityContext(context.Background(), constants.ServiceContext, "trusted reference-target write test")
	resolved, err := ps.validatePolymorphicLookups(trustedCtx, models.SObject{"PolyId": "00G-group"}, schema)
	if err != nil {
		t.Fatalf("trusted validatePolymorphicLookups: %v", err)
	}
	if resolved["PolyId"] != "Group" {
		t.Fatalf("resolved PolyId = %q, want Group", resolved["PolyId"])
	}
}

func TestPreparedInternalUpdatesScrubPolymorphicDiscriminator(t *testing.T) {
	schema := polymorphicTestSchema()
	ps := &PersistenceService{schemaValidator: validation.NewSchemaValidator()}
	input := models.SObject{
		"PolyId":      "005-user",
		"PolyId_type": "Group",
	}

	got, err := ps.normalizePreparedInternalUpdates(schema, input)
	if err != nil {
		t.Fatalf("normalizePreparedInternalUpdates: %v", err)
	}
	if _, ok := got["PolyId_type"]; ok {
		t.Fatalf("normalizePreparedInternalUpdates preserved caller-supplied PolyId_type: %#v", got)
	}
	if got["PolyId"] != "005-user" {
		t.Fatalf("PolyId = %v, want 005-user", got["PolyId"])
	}
}

type polymorphicExistsRepo struct {
	stubUpsertRecordRepo
	exists map[string]map[string]bool
}

func (r *polymorphicExistsRepo) Exists(ctx context.Context, tableName string, id string) (bool, error) {
	if r.exists == nil {
		return false, nil
	}
	ids := r.exists[tableName]
	if ids == nil {
		return false, nil
	}
	return ids[id], nil
}

func polymorphicTestSchema() *models.ObjectMetadata {
	return &models.ObjectMetadata{
		APIName: "ProbeObject",
		Fields: []models.FieldMetadata{
			{
				APIName:       "PolyId",
				Type:          constants.FieldTypeReference,
				ReferenceTo:   []string{"User", "Group"},
				IsPolymorphic: true,
				Createable:    true,
				Updateable:    true,
			},
			{
				APIName:    "PolyId_type",
				Type:       constants.FieldTypeString,
				Createable: false,
				Updateable: false,
			},
		},
	}
}
