//go:build integration

package controlplane

import (
	"context"
	"errors"
	"strings"
	"testing"

	"github.com/nexuscrm/kernel/pkg/schema"
	"github.com/nexuscrm/metadata"
	"github.com/nexuscrm/persistence"
	apperrors "github.com/nexuscrm/kernel/pkg/errors"
	"github.com/nexuscrm/kernel/pkg/logging"
	"github.com/nexuscrm/shared/pkg/constants"
	"github.com/nexuscrm/shared/pkg/models"
)

func TestCreateFieldRollsBackPageLayoutAdmissionOnMetadataRegistrationFailure(t *testing.T) {
	ctx := context.Background()
	cfg, cleanup := newTemporaryIntegrationDatabase(t)
	defer cleanup()

	conn := initializeControlPlaneMetadata(t, ctx, cfg)
	defer conn.Close()

	r, err := NewRunner(conn)
	if err != nil {
		t.Fatalf("new runner: %v", err)
	}
	if err := r.RefreshCache(ctx); err != nil {
		t.Fatalf("initial cache refresh: %v", err)
	}

	const objectAPIName = "IssueLayoutAdmissionProbe__c"
	if err := r.SvcMgr.Metadata().CreateSchema(ctx, &models.ObjectMetadata{
		APIName:                objectAPIName,
		Label:                  "Issue Layout Admission Probe",
		PluralLabel:            "Issue Layout Admission Probes",
		IsCustom:               true,
		TableType:              constants.TableTypeCustomObject,
		SharingModel:           models.SharingModel("Private"),
		EnableHierarchySharing: false,
		Searchable:             true,
	}); err != nil {
		t.Fatalf("create probe object: %v", err)
	}

	before, err := r.SvcMgr.Metadata().GetAllLayouts(ctx, objectAPIName)
	if err != nil {
		t.Fatalf("get layouts before field create: %v", err)
	}
	if len(before) != 1 {
		t.Fatalf("layouts before field create = %d, want 1", len(before))
	}
	original := before[0]
	if layoutContainsField(original, "Score__c") {
		t.Fatalf("fresh default layout already contains Score__c: %+v", original.Sections)
	}

	registrationErr := errors.New("forced metadata registration failure after layout admission")
	schemaRepo := &layoutAdmissionFailingSchemaRepo{
		SchemaRepository: persistence.NewSchemaRepository(conn.DB(), logging.Default),
		registerOnlyErr:  registrationErr,
	}
	metadataSvc := metadata.NewMetadataService(
		persistence.NewMetadataRepository(conn.DB()),
		metadata.NewSchemaManager(schemaRepo),
		nil,
	)

	formula := "Name"
	mode := "active"
	returnType := constants.FieldTypeString
	err = metadataSvc.CreateField(ctx, objectAPIName, &models.FieldMetadata{
		APIName:     "Score__c",
		Label:       "Score",
		Type:        constants.FieldTypeFormula,
		Formula:     &formula,
		FormulaMode: &mode,
		ReturnType:  &returnType,
	})
	if err == nil {
		t.Fatal("CreateField = nil, want registration failure after layout admission")
	}
	if !strings.Contains(err.Error(), registrationErr.Error()) {
		t.Fatalf("CreateField error = %v, want registration failure detail", err)
	}
	var internalErr *apperrors.InternalError
	if !errors.As(err, &internalErr) {
		t.Fatalf("CreateField error type = %T, want InternalError", err)
	}
	if metadataFieldCount(t, conn.DB(), objectAPIName, "Score__c") != 0 {
		t.Fatal("failed field create left _System_Field row, want rollback/no metadata row")
	}

	after, err := r.SvcMgr.Metadata().GetAllLayouts(ctx, objectAPIName)
	if err != nil {
		t.Fatalf("get layouts after failed field create: %v", err)
	}
	if len(after) != 1 {
		t.Fatalf("layouts after field create = %d, want 1", len(after))
	}
	restored := after[0]
	if layoutContainsField(restored, "Score__c") {
		t.Fatalf("failed field create left admitted field in layout: %+v", restored.Sections)
	}
	if restored.ID != original.ID ||
		restored.Source != original.Source ||
		restored.IsCustomized != original.IsCustomized ||
		restored.GenerationVersion != original.GenerationVersion {
		t.Fatalf("layout owner tuple after rollback = id=%q source=%q customized=%v generation=%d, want id=%q source=%q customized=%v generation=%d",
			restored.ID, restored.Source, restored.IsCustomized, restored.GenerationVersion,
			original.ID, original.Source, original.IsCustomized, original.GenerationVersion)
	}
}

type layoutAdmissionFailingSchemaRepo struct {
	*persistence.SchemaRepository

	registerOnlyErr error
}

func (r *layoutAdmissionFailingSchemaRepo) RegisterFieldOnly(tableName string, col schema.ColumnDefinition) error {
	if r.registerOnlyErr != nil {
		return r.registerOnlyErr
	}
	return r.SchemaRepository.RegisterFieldOnly(tableName, col)
}

func layoutContainsField(layout *models.PageLayout, fieldAPIName string) bool {
	if layout == nil {
		return false
	}
	for _, section := range layout.Sections {
		for _, field := range section.Fields {
			if field == fieldAPIName {
				return true
			}
		}
	}
	return false
}
