package metadata

import (
	"context"
	"log"
	"slices"
	"time"

	apperrors "github.com/nexuscrm/backend/pkg/errors"
	sharedEvents "github.com/nexuscrm/shared/pkg/events"
	"github.com/nexuscrm/shared/pkg/models"
)

// --- Schema CRUD Methods ---

// CreateSchema creates a new custom object schema and physical table
func (ms *MetadataService) CreateSchema(ctx context.Context, schema *models.ObjectMetadata) error {
	ms.mu.Lock()
	defer ms.mu.Unlock()

	// Capture original labels before any processing/defaults
	originalPlural := schema.PluralLabel
	originalLabel := schema.Label

	// Validate Object Metadata
	if ms.validationSvc != nil {
		if err := ms.validationSvc.ValidateObjectMetadata(schema); err != nil {
			return err
		}
	}

	// Prepare Table Definition (Validation, Enrichment, Mapping)
	def, _, err := ms.PrepareTableDefinition(schema)
	if err != nil {
		return err
	}

	// Check if metadata exists in DB
	existing, err := ms.repo.GetSchemaByAPIName(ctx, schema.APIName)
	if err == nil && existing != nil {
		return apperrors.NewConflictError("Object Metadata", "api_name", schema.APIName)
	}

	// Restore original labels for strict insert (if provided/valid)
	// This ensures we persist exactly what the user provided, overriding any "Humanization" done by PrepareTableDefinition
	if originalLabel != "" {
		schema.Label = originalLabel
	}
	if originalPlural != "" {
		schema.PluralLabel = originalPlural
	}

	// Create Table (Strict Mode: Fails if metadata conflicts)
	if err := ms.schemaMgr.CreateTableWithStrictMetadata(ctx, def, schema); err != nil {
		return apperrors.NewInternalError("failed to create schema via SchemaManager", err)
	}

	// --- AUTO-GENERATE DEFAULT LAYOUT ---
	defaultLayout := ms.GenerateDefaultLayout(schema)

	// Persist Layout to _System_Layout via Repo
	if err := ms.repo.UpsertLayout(ctx, &defaultLayout); err != nil {
		log.Printf("⚠️ Failed to auto-create default layout for %s: %v", schema.APIName, err)
	} else {
		log.Printf("✅ Auto-created default layout for %s", schema.APIName)
	}

	ms.invalidateCacheLocked()

	// Publish Event
	ms.publishMetadataEvent(ctx, sharedEvents.ObjectCreated, sharedEvents.SchemaEventPayload{
		EventType:     sharedEvents.ObjectCreated,
		ObjectAPIName: schema.APIName,
		Timestamp:     time.Now().Format(time.RFC3339),
	})

	return nil
}

// CreateSchemaOptimized creates a new object schema using batch metadata registration
// This is faster than CreateSchema for objects with many fields
func (ms *MetadataService) CreateSchemaOptimized(ctx context.Context, schema *models.ObjectMetadata) error {
	ms.mu.Lock()
	defer ms.mu.Unlock()

	// Validate Object Metadata
	if ms.validationSvc != nil {
		if err := ms.validationSvc.ValidateObjectMetadata(schema); err != nil {
			return err
		}
	}

	// Prepare Table Definition
	def, _, err := ms.PrepareTableDefinition(schema)
	if err != nil {
		return err
	}

	// Check if metadata exists in DB
	existing, err := ms.repo.GetSchemaByAPIName(ctx, schema.APIName)
	if err == nil && existing != nil {
		return apperrors.NewConflictError("Object Metadata", "api_name", schema.APIName)
	}

	// Create table with STRICT metadata registration (Fails on Unique Constraint)
	// Note: PrepareTableDefinition updates schema with defaults, so we pass it back
	if err := ms.schemaMgr.CreateTableWithStrictMetadata(ctx, def, schema); err != nil {
		return apperrors.NewInternalError("failed to create schema", err)
	}

	// Auto-generate default layout (same as CreateSchema)
	defaultLayout := ms.GenerateDefaultLayout(schema)
	if err := ms.repo.UpsertLayout(ctx, &defaultLayout); err != nil {
		log.Printf("⚠️ Failed to insert default layout for %s: %v", schema.APIName, err)
	}

	ms.invalidateCacheLocked()

	// Publish Event
	ms.publishMetadataEvent(ctx, sharedEvents.ObjectCreated, sharedEvents.SchemaEventPayload{
		EventType:     sharedEvents.ObjectCreated,
		ObjectAPIName: schema.APIName,
		Timestamp:     time.Now().Format(time.RFC3339),
	})

	return nil
}

// InvalidateCache/invalidateCacheLocked moved to metadata_service.go

// UpdateSchema updates an existing object schema
func (ms *MetadataService) UpdateSchema(ctx context.Context, apiName string, updates *models.ObjectMetadata) error {
	ms.mu.Lock()
	defer ms.mu.Unlock()

	// Check if exists
	obj, err := ms.repo.GetSchemaByAPIName(ctx, apiName)
	if err != nil || obj == nil {
		return apperrors.NewNotFoundError("Object", apiName)
	}
	apiName = obj.APIName

	// Prepare updates by modifying the existing object in-memory
	if updates.Label != "" && obj.Label != updates.Label {
		obj.Label = updates.Label
		obj.IsCustomized = true
	}
	if updates.PluralLabel != "" && obj.PluralLabel != updates.PluralLabel {
		obj.PluralLabel = updates.PluralLabel
		obj.IsCustomized = true
	}
	if updates.Description != nil {
		if obj.Description == nil || *obj.Description != *updates.Description {
			obj.IsCustomized = true
		}
		obj.Description = updates.Description
	}
	if updates.PathField != nil {
		if obj.PathField == nil || *obj.PathField != *updates.PathField {
			obj.IsCustomized = true
		}
		obj.PathField = updates.PathField
	}
	if len(updates.ListFields) > 0 {
		if !slices.Equal(obj.ListFields, updates.ListFields) {
			obj.IsCustomized = true
		}
		obj.ListFields = updates.ListFields
	}
	if updates.Icon != "" && obj.Icon != updates.Icon {
		obj.Icon = updates.Icon
		obj.IsCustomized = true
	}
	if updates.AppID != nil {
		if obj.AppID == nil || *obj.AppID != *updates.AppID {
			obj.IsCustomized = true
		}
		obj.AppID = updates.AppID
	}
	if updates.SharingModel != "" {
		if obj.SharingModel != updates.SharingModel {
			obj.SharingModel = updates.SharingModel
			// Object-level customization currently protects managed OWD reconcile.
			// Only mark customized when the sharing model itself actually changes.
			obj.IsCustomized = true
		}
	}
	if updates.ThemeColor != nil {
		if obj.ThemeColor == nil || *obj.ThemeColor != *updates.ThemeColor {
			obj.IsCustomized = true
		}
		obj.ThemeColor = updates.ThemeColor
	}

	// Use helper to persist changes
	if err := ms.schemaMgr.SaveObjectMetadata(ctx, obj, nil); err != nil {
		return apperrors.NewInternalError("failed to update object", err)
	}

	ms.invalidateCacheLocked()

	// Publish Event
	ms.publishMetadataEvent(ctx, sharedEvents.ObjectUpdated, sharedEvents.SchemaEventPayload{
		EventType:     sharedEvents.ObjectUpdated,
		ObjectAPIName: apiName,
		Timestamp:     time.Now().Format(time.RFC3339),
	})

	return nil
}

// DeleteSchema deletes a custom object schema and drops its table
func (ms *MetadataService) DeleteSchema(ctx context.Context, apiName string) error {
	ms.mu.Lock()
	defer ms.mu.Unlock()

	// Check if exists
	obj, err := ms.repo.GetSchemaByAPIName(ctx, apiName)
	if err != nil || obj == nil {
		return apperrors.NewNotFoundError("Object", apiName)
	}
	apiName = obj.APIName

	// Physical table drop and Unregister
	if err := ms.schemaMgr.DropTable(apiName); err != nil {
		return apperrors.NewInternalError("failed to drop table and schema", err)
	}

	// Delete from _System_Object and _System_Field is handled by DropTable internally
	ms.invalidateCacheLocked()

	// Publish Event
	ms.publishMetadataEvent(ctx, sharedEvents.ObjectDeleted, sharedEvents.SchemaEventPayload{
		EventType:     sharedEvents.ObjectDeleted,
		ObjectAPIName: apiName,
		Timestamp:     time.Now().Format(time.RFC3339),
	})

	return nil
}

// Field CRUD methods are split across:
// - field_create.go (CreateField)
// - field_update.go (UpdateField)
// - field_delete.go (DeleteField)
// - field_batch.go  (BatchCreateFields)
