package records

import (
	"context"
	"fmt"
	"strings"
	"testing"

	eventFeature "github.com/nexuscrm/events"
	"github.com/nexuscrm/shared/pkg/constants"
	sharedEvents "github.com/nexuscrm/shared/pkg/events"
	"github.com/nexuscrm/shared/pkg/models"
)

func TestCreatePreservesTrustedDefaultsWithoutAllowingCallerOrFlowWrites(t *testing.T) {
	for _, bulk := range []bool{false, true} {
		for _, test := range []struct {
			name      string
			supplied  bool
			flow      string
			wantError bool
		}{
			{name: "metadata default survives"},
			{name: "explicit default is still a forbidden write", supplied: true, wantError: true},
			{name: "flow cannot replace default", flow: "replace", wantError: true},
			{name: "flow cannot remove default", flow: "remove", wantError: true},
			{name: "flow cannot introduce another protected value", flow: "introduce", wantError: true},
		} {
			t.Run(fmt.Sprintf("bulk=%t/%s", bulk, test.name), func(t *testing.T) {
				defaultValue := "1"
				schema := lifecycleSpikeSchema()
				schema.Fields = append(schema.Fields,
					models.FieldMetadata{APIName: "Revision", Type: constants.FieldTypeInt, DefaultValue: &defaultValue, WriteProtectionPolicy: constants.FieldWriteProtectionTrustedOnly},
					models.FieldMetadata{APIName: "OtherProtectedValue", Type: constants.FieldTypeInt, WriteProtectionPolicy: constants.FieldWriteProtectionTrustedOnly},
				)
				makeFieldsMap(schema)
				repo := &captureRecordRepo{}
				ps := newRecordEntrypointTestService(schema, repo)
				ps.eventBus = lifecycleSpikeEventBus{publish: func(_ context.Context, kind sharedEvents.EventType, payload interface{}) error {
					if kind != sharedEvents.RecordBeforeCreate {
						return nil
					}
					record := payload.(eventFeature.RecordEventPayload).Record
					if fmt.Sprint(record["Revision"]) != "1" {
						t.Fatalf("flow did not see metadata default: %v", record)
					}
					switch test.flow {
					case "replace":
						record["Revision"] = 2
					case "remove":
						delete(record, "Revision")
					case "introduce":
						record["OtherProtectedValue"] = 3
					}
					return nil
				}}
				input := models.SObject{constants.FieldName: "trusted default probe"}
				if test.supplied {
					input["Revision"] = 1
				}
				if bulk {
					result, err := ps.BulkInsert(context.Background(), schema.APIName, []models.SObject{input}, recordEntrypointUser(), BulkInsertOptions{})
					if !test.wantError && err != nil {
						t.Fatal(err)
					}
					if (result.SuccessCount == 0) != test.wantError {
						t.Fatalf("BulkInsert = %+v, wantError=%t", result, test.wantError)
					}
					if test.wantError && (len(result.Errors) != 1 || !strings.Contains(result.Errors[0], "write-protected")) {
						t.Fatalf("BulkInsert rejection = %+v, error=%v; want protected-field rejection", result, err)
					}
					if !test.wantError && (len(repo.bulkInserted) != 1 || fmt.Sprint(repo.bulkInserted[0]["Revision"]) != "1") {
						t.Fatalf("persisted bulk rows lost the default: %#v", repo.bulkInserted)
					}
				} else {
					_, err := ps.Insert(context.Background(), schema.APIName, input, recordEntrypointUser())
					if (err != nil) != test.wantError {
						t.Fatalf("Insert error=%v, wantError=%t", err, test.wantError)
					}
					if test.wantError && !strings.Contains(err.Error(), "write-protected") {
						t.Fatalf("Insert rejection=%v; want protected-field rejection", err)
					}
					if !test.wantError && fmt.Sprint(repo.inserted["Revision"]) != "1" {
						t.Fatalf("persisted row lost the default: %#v", repo.inserted)
					}
				}
				if test.wantError && (repo.inserted != nil || len(repo.bulkInserted) != 0) {
					t.Fatalf("rejected write reached persistence: %#v / %#v", repo.inserted, repo.bulkInserted)
				}
			})
		}
	}
}

func TestProtectedCreateBaselineCannotAbsorbInPlaceMutation(t *testing.T) {
	schema := &models.ObjectMetadata{Fields: []models.FieldMetadata{{
		APIName: "ProtectedValue", WriteProtectionPolicy: constants.FieldWriteProtectionTrustedOnly,
	}}}
	value := map[string]interface{}{"nested": []interface{}{"initial"}}
	record := models.SObject{"ProtectedValue": value}
	baseline, err := captureProtectedCreateBaseline(schema, record)
	if err != nil {
		t.Fatal(err)
	}
	if err := baseline.validate(context.Background(), schema, record); err != nil {
		t.Fatalf("unchanged protected value rejected: %v", err)
	}
	value["nested"].([]interface{})[0] = "changed"
	if err := baseline.validate(context.Background(), schema, record); err == nil {
		t.Fatal("in-place mutation changed both the live value and its protection baseline")
	}
}

func TestProtectedCreateBaselineDoesNotExemptCanonicalAuthority(t *testing.T) {
	schema := &models.ObjectMetadata{
		APIName:    "FieldMetadata",
		ReadSource: &models.ObjectReadSourceConfig{Kind: "physical_table", PhysicalTable: constants.TableField},
		Fields: []models.FieldMetadata{{
			APIName:               constants.FieldSysField_WriteProtectionPolicy,
			Type:                  constants.FieldTypeString,
			WriteProtectionPolicy: constants.FieldWriteProtectionTrustedOnly,
		}},
	}
	record := models.SObject{constants.FieldSysField_WriteProtectionPolicy: "trusted_only"}
	baseline, err := captureProtectedCreateBaseline(schema, record)
	if err != nil {
		t.Fatal(err)
	}
	systemCtx := constants.WithSecurityContext(context.Background(), constants.SystemContext, "canonical authority probe")
	if err := baseline.validate(systemCtx, schema, record); err == nil || !strings.Contains(err.Error(), "authority_only") {
		t.Fatalf("unchanged canonical authority field must still reject: %v", err)
	}
}

func TestRejectWriteProtectedFieldWritesImmutableAfterCreate(t *testing.T) {
	schema := &models.ObjectMetadata{Fields: []models.FieldMetadata{{
		APIName:               "InitialValue__c",
		Type:                  constants.FieldTypeString,
		WriteProtectionPolicy: constants.FieldWriteProtectionImmutableAfterCreate,
	}}}
	data := models.SObject{"InitialValue__c": "first"}

	if err := rejectWriteProtectedFieldWrites(context.Background(), schema, data, protectedFieldWriteOnCreate); err != nil {
		t.Fatalf("create rejected immutable-after-create value: %v", err)
	}
	err := rejectWriteProtectedFieldWrites(context.Background(), schema, data, protectedFieldWriteOnUpdate)
	if err == nil {
		t.Fatal("update accepted immutable-after-create value")
	}
	if !strings.Contains(err.Error(), "immutable_after_create") {
		t.Fatalf("update error = %v, want immutable_after_create policy", err)
	}
}

func TestRejectWriteProtectedFieldWritesTrustedOnlyRejectsCreateAndUpdate(t *testing.T) {
	schema := &models.ObjectMetadata{Fields: []models.FieldMetadata{{
		APIName:               "ProtectedValue__c",
		Type:                  constants.FieldTypeString,
		WriteProtectionPolicy: constants.FieldWriteProtectionTrustedOnly,
	}}}
	data := models.SObject{"ProtectedValue__c": "value"}

	for _, phase := range []protectedFieldWritePhase{protectedFieldWriteOnCreate, protectedFieldWriteOnUpdate} {
		if err := rejectWriteProtectedFieldWrites(context.Background(), schema, data, phase); err == nil {
			t.Fatalf("trusted-only policy accepted phase %d", phase)
		}
	}
}

func TestRejectWriteProtectedFieldWritesCatalogAuthorityRejectsCreateAndUpdate(t *testing.T) {
	schema := &models.ObjectMetadata{Fields: []models.FieldMetadata{{
		APIName:               "CatalogOwnedValue",
		Type:                  constants.FieldTypeString,
		WriteProtectionPolicy: constants.FieldWriteProtectionAuthorityOnly,
	}}}
	data := models.SObject{"CatalogOwnedValue": "value"}

	contexts := map[string]context.Context{
		"user":     context.Background(),
		"service":  constants.WithSecurityContext(context.Background(), constants.ServiceContext, "authority-only test"),
		"elevated": constants.WithSecurityContext(context.Background(), constants.ElevatedContext, "authority-only test"),
		"system":   constants.WithSecurityContext(context.Background(), constants.SystemContext, "authority-only test"),
	}
	for contextName, ctx := range contexts {
		for _, phase := range []protectedFieldWritePhase{protectedFieldWriteOnCreate, protectedFieldWriteOnUpdate} {
			if err := rejectWriteProtectedFieldWrites(ctx, schema, data, phase); err == nil {
				t.Fatalf("%s context accepted catalog authority policy in phase %d", contextName, phase)
			}
		}
		if isTrustedWriteProtectedFieldWrite(ctx, schema, "CatalogOwnedValue") {
			t.Fatalf("%s context classified authority-only field as a trusted write", contextName)
		}
	}
}

func TestRejectWriteProtectedFieldWritesUsesCatalogBeforeTrustedBypass(t *testing.T) {
	schema := &models.ObjectMetadata{
		APIName: "FieldMetadata",
		ReadSource: &models.ObjectReadSourceConfig{
			Kind:          "physical_table",
			PhysicalTable: constants.TableField,
		},
		Fields: []models.FieldMetadata{{
			APIName: constants.FieldAPIName,
			Type:    constants.FieldTypeString,
		}},
	}
	systemCtx := constants.WithSecurityContext(context.Background(), constants.SystemContext, "stale catalog policy test")

	err := rejectWriteProtectedFieldWrites(
		systemCtx,
		schema,
		models.SObject{constants.FieldSysField_WriteProtectionPolicy: constants.FieldWriteProtectionTrustedOnly},
		protectedFieldWriteOnUpdate,
	)
	if err == nil {
		t.Fatal("SystemContext accepted a catalog authority field omitted from the live schema snapshot")
	}
	if !strings.Contains(err.Error(), constants.FieldWriteProtectionAuthorityOnly) {
		t.Fatalf("error = %v, want authority-only policy", err)
	}
}

func TestUpdateRejectsImmutableAfterCreateThroughWritePath(t *testing.T) {
	const objectAPIName = "GenericObject__c"
	const fieldAPIName = "InitialValue__c"

	ps, repo := newImmutableWritePathService(&models.ObjectMetadata{
		APIName: objectAPIName,
		Fields: []models.FieldMetadata{
			{APIName: constants.FieldID, Type: constants.FieldTypeString},
			{
				APIName:               fieldAPIName,
				Type:                  constants.FieldTypeString,
				Createable:            true,
				Updateable:            true,
				WriteProtectionPolicy: constants.FieldWriteProtectionImmutableAfterCreate,
			},
		},
	})
	repo.findOneFn = func(context.Context, string, string) (models.SObject, error) {
		return models.SObject{
			constants.FieldID: "record-1",
			fieldAPIName:      "initial",
		}, nil
	}

	err := ps.Update(context.Background(), objectAPIName, "record-1", models.SObject{
		fieldAPIName: "replacement",
	}, &models.UserSession{ID: "user-1", ProfileID: constants.ProfileStandardUser})
	if err == nil {
		t.Fatal("Update accepted an immutable-after-create field")
	}
	if !strings.Contains(err.Error(), constants.FieldWriteProtectionImmutableAfterCreate) {
		t.Fatalf("Update error = %v, want immutable-after-create protection", err)
	}
	if repo.updateCalled {
		t.Fatal("Update reached repo.Update, want write-protection rejection before persistence")
	}
}
