package persistence

import (
	"context"
	"fmt"
	"strings"

	domainschema "github.com/nexuscrm/kernel/pkg/schema"
	"github.com/nexuscrm/shared/pkg/models"
)

func (r *SchemaRepository) prepareBatchField(tableName string, field models.FieldMetadata) (models.FieldMetadata, domainschema.ColumnDefinition, error) {
	prepared := field
	sqlType := r.MapFieldTypeToSQL(string(field.Type))

	if domainschema.IsRollupSummaryType(field.Type) {
		if field.RollupConfig == nil {
			return models.FieldMetadata{}, domainschema.ColumnDefinition{}, fmt.Errorf("rollupsummary fields require a rollupConfig")
		}

		sourceFieldSQLType, err := r.resolveRollupSourceFieldSQLType(context.Background(), field.RollupConfig)
		if err != nil {
			return models.FieldMetadata{}, domainschema.ColumnDefinition{}, fmt.Errorf("cannot resolve source field SQL type for rollupsummary: %v", err)
		}
		resolved, err := domainschema.ResolveRollup(tableName, *field.RollupConfig, r.rollupSchemaLookupFunc(context.Background()), sourceFieldSQLType)
		if err != nil {
			return models.FieldMetadata{}, domainschema.ColumnDefinition{}, fmt.Errorf("invalid rollupsummary config: %v", err)
		}
		prepared.RollupConfig = domainschema.CloneRollupConfig(&resolved.CanonicalConfig)
		resultType := resolved.Result.Type
		prepared.ReturnType = &resultType
		prepared.Precision = cloneBatchRollupInt(resolved.Result.Precision)
		prepared.Scale = cloneBatchRollupInt(resolved.Result.Scale)
		sqlType = resolved.SQLType
	}

	colDef := r.mapFieldToColumnDef(prepared)
	colDef.Type = sqlType
	colDef.RollupConfig = domainschema.CloneRollupConfig(prepared.RollupConfig)

	return prepared, colDef, nil
}

func cloneBatchRollupInt(value *int) *int {
	if value == nil {
		return nil
	}
	cloned := *value
	return &cloned
}

func (r *SchemaRepository) rollupSchemaLookupFunc(ctx context.Context) domainschema.SchemaLookupFunc {
	return func(objectAPIName string) (*models.ObjectMetadata, error) {
		return r.loadRollupObjectSchema(ctx, objectAPIName)
	}
}

func (r *SchemaRepository) resolveRollupSourceFieldSQLType(ctx context.Context, cfg *models.RollupConfig) (string, error) {
	return r.resolveRollupSourceFieldSQLTypeSeen(ctx, cfg, nil)
}

func (r *SchemaRepository) resolveRollupSourceFieldSQLTypeSeen(ctx context.Context, cfg *models.RollupConfig, seen map[string]struct{}) (string, error) {
	if cfg == nil {
		return "", nil
	}
	if strings.EqualFold(cfg.CalcType, "COUNT") {
		return "", nil
	}

	summaryObjectName := strings.TrimSpace(cfg.SummaryObject)
	summaryFieldName := strings.TrimSpace(cfg.SummaryField)
	if summaryObjectName == "" || summaryFieldName == "" {
		return "", fmt.Errorf("summaryObject and summaryField are required for %s calcType", cfg.CalcType)
	}

	objMeta, err := r.loadRollupObjectSchema(ctx, summaryObjectName)
	if err != nil {
		return "", fmt.Errorf("cannot look up summary object %q: %w", summaryObjectName, err)
	}
	if objMeta == nil {
		return "", fmt.Errorf("summary object %q not found", summaryObjectName)
	}

	for _, field := range objMeta.Fields {
		if !strings.EqualFold(field.APIName, summaryFieldName) {
			continue
		}
		if domainschema.IsRollupSummaryType(field.Type) {
			if field.RollupConfig == nil {
				return "", fmt.Errorf("rollupsummary source field %q on object %q is missing rollupConfig", field.APIName, objMeta.APIName)
			}
			seenKey := strings.ToLower(strings.TrimSpace(objMeta.APIName)) + "." + strings.ToLower(strings.TrimSpace(field.APIName))
			if seen != nil {
				if _, ok := seen[seenKey]; ok {
					return "", fmt.Errorf("detected cycle while resolving source field %s.%s", objMeta.APIName, field.APIName)
				}
			}

			nextSeen := make(map[string]struct{}, len(seen)+1)
			for key := range seen {
				nextSeen[key] = struct{}{}
			}
			nextSeen[seenKey] = struct{}{}

			nestedSourceSQLType, err := r.resolveRollupSourceFieldSQLTypeSeen(ctx, field.RollupConfig, nextSeen)
			if err != nil {
				return "", err
			}
			resolved, err := domainschema.ResolveRollup(objMeta.APIName, *field.RollupConfig, r.rollupSchemaLookupFunc(ctx), nestedSourceSQLType)
			if err != nil {
				return "", err
			}
			return resolved.SQLType, nil
		}

		return domainschema.ResolveSchemaFieldSQLType(&field, r.MapFieldTypeToSQL)
	}

	return "", fmt.Errorf("source field %q not found on object %q", summaryFieldName, objMeta.APIName)
}

func (r *SchemaRepository) loadRollupObjectSchema(ctx context.Context, objectAPIName string) (*models.ObjectMetadata, error) {
	trimmed := strings.TrimSpace(objectAPIName)
	if trimmed == "" {
		return nil, nil
	}
	return NewMetadataRepository(r.db).GetSchemaByAPIName(ctx, trimmed)
}
