package runtimeapi

import (
	"context"
	"errors"
	"strings"

	appErrors "github.com/nexuscrm/kernel/pkg/errors"
	"github.com/nexuscrm/kernel/pkg/telemetry"
	"github.com/nexuscrm/shared/pkg/models"
	semantic "github.com/pingcap-inc/nexuscrm-contract/pkg/runtime/recorddefaultsv1"
	runtimev1 "github.com/pingcap-inc/nexuscrm-contract/wire-go/gen/runtime/v1"
	"github.com/pingcap-inc/nexuscrm-contract/wire-go/mapping"
)

// CreateDefaultPreviewEngine is the narrow L0 authority for dynamic defaults.
// Product-specific layouts and response grammar remain outside this seam.
type CreateDefaultPreviewEngine interface {
	PreviewCreateDefaults(context.Context, semantic.Request, *models.UserSession) ([]semantic.Field, error)
}

// CreateDefaultPreviewServer exposes Runtime-owned create defaults without
// making the preview authoritative for the eventual write.
type CreateDefaultPreviewServer struct {
	runtimev1.UnimplementedRecordCreateDefaultPreviewServiceServer
	engine   CreateDefaultPreviewEngine
	receipts ReceiptSource
	metrics  *recordsMetrics
}

func NewCreateDefaultPreviewServer(
	engine CreateDefaultPreviewEngine,
	receipts ReceiptSource,
	metrics *recordsMetrics,
) (*CreateDefaultPreviewServer, error) {
	if engine == nil {
		return nil, errors.New("runtime-api: create-default preview engine is required")
	}
	if metrics == nil {
		metrics = newRecordsMetrics(nil)
	}
	return &CreateDefaultPreviewServer{engine: engine, receipts: receipts, metrics: metrics}, nil
}

func (server *CreateDefaultPreviewServer) PreviewCreateDefaults(
	ctx context.Context,
	wireRequest *runtimev1.PreviewCreateDefaultsRequest,
) (*runtimev1.PreviewCreateDefaultsResponse, error) {
	trusted, ok := trustedCallFromContext(ctx)
	if !ok {
		return nil, mapping.ToCreateDefaultPreviewStatusError(&semantic.Failure{
			Category: semantic.FailureUnauthenticated,
		})
	}
	request, err := mapping.FromWirePreviewCreateDefaultsRequest(ctx, wireRequest)
	if err != nil {
		return nil, mapping.ToCreateDefaultPreviewStatusError(err)
	}
	receipts, err := createDefaultPreviewExecutionReceipts(ctx, server.receipts)
	if err != nil {
		return nil, mapping.ToCreateDefaultPreviewStatusError(err)
	}
	receipts = receiptsForCapability(receipts, semantic.CapabilityName)
	if request.MinCatalogRevision != nil && *request.MinCatalogRevision > receipts.CatalogRevision {
		return nil, mapping.ToCreateDefaultPreviewStatusError(&semantic.Failure{
			Category: semantic.FailureStaleState, Receipts: &receipts,
			StaleAxis: semantic.StaleAxisCatalog, RequiredRevision: *request.MinCatalogRevision,
			ServedRevision: receipts.CatalogRevision,
		})
	}
	if _, ok := RuntimeMetadataViewFromContext(ctx); !ok {
		return nil, mapping.ToCreateDefaultPreviewStatusError(&semantic.Failure{
			Category: semantic.FailureUnavailable,
		})
	}
	user := trusted.UserSession()
	repositoryCtx, sqlCounter := telemetry.WithSQLStatementCounter(ctx)
	fields, err := server.engine.PreviewCreateDefaults(repositoryCtx, request, &user)
	server.metrics.observeRepository(recordsOperationCreateDefaultPreview, sqlCounter.Count())
	if err != nil {
		return nil, mapping.ToCreateDefaultPreviewStatusError(classifyCreateDefaultPreviewEngineError(ctx, err))
	}
	response := semantic.Response{Fields: fields, Receipts: receipts}
	wireResponse, err := mapping.ToWirePreviewCreateDefaultsResponse(request, response)
	if err != nil {
		return nil, mapping.ToCreateDefaultPreviewStatusError(err)
	}
	return wireResponse, nil
}

func createDefaultPreviewExecutionReceipts(ctx context.Context, source ReceiptSource) (semantic.Receipts, error) {
	if scope, ok := runtimeExecutionScopeFromContext(ctx); ok {
		return scope.receipts, nil
	}
	if source == nil {
		return semantic.Receipts{}, &semantic.Failure{Category: semantic.FailureUnavailable}
	}
	return source.Receipts(ctx)
}

func classifyCreateDefaultPreviewEngineError(ctx context.Context, err error) error {
	classified := classifyPlatformEngineError(ctx, err, semantic.InvalidComponentFieldValue)
	var failure *semantic.Failure
	if !errors.As(classified, &failure) || failure.Category != semantic.FailureMissing {
		return classified
	}
	var notFound *appErrors.NotFoundError
	if errors.As(err, &notFound) {
		resource := strings.TrimSpace(notFound.Resource)
		if strings.EqualFold(resource, "Object") ||
			strings.EqualFold(resource, "Object Metadata") {
			return &semantic.Failure{
				Category: semantic.FailureInvalidRequest, InvalidComponent: semantic.InvalidComponentObjectID,
			}
		}
	}
	return classified
}
