package runtimeapi

import (
	"context"
	"errors"
	"net/http"

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

// RecycleBinLister is the existing neutral records-engine read seam. Restore
// and purge remain mutation-owned and are deliberately absent.
type RecycleBinLister interface {
	ListRecycleBin(context.Context, *models.UserSession, contract.RecycleBinScope) (*contract.RecycleBinListResult, error)
}

// RecycleBinServer adapts one complete bounded listing to the real records
// engine. Product URL, aliases, and response/error envelopes remain in L1.
type RecycleBinServer struct {
	runtimev1.UnimplementedRecycleBinReadServiceServer
	lister   RecycleBinLister
	receipts ReceiptSource
	metrics  *recordsMetrics
}

func NewRecycleBinServer(lister RecycleBinLister, receipts ReceiptSource, metrics *recordsMetrics) (*RecycleBinServer, error) {
	if lister == nil {
		return nil, errors.New("runtime-api: recycle-bin lister is required")
	}
	if metrics == nil {
		metrics = newRecordsMetrics(nil)
	}
	return &RecycleBinServer{lister: lister, receipts: receipts, metrics: metrics}, nil
}

func (s *RecycleBinServer) ListRecycleBin(ctx context.Context, wireRequest *runtimev1.ListRecycleBinRequest) (*runtimev1.ListRecycleBinResponse, error) {
	trusted, ok := trustedCallFromContext(ctx)
	if !ok {
		return nil, mapping.ToRecycleBinStatusError(&semantic.Failure{Category: semantic.FailureUnauthenticated})
	}
	request, err := mapping.FromWireListRecycleBinRequest(ctx, wireRequest)
	if err != nil {
		return nil, mapping.ToRecycleBinStatusError(err)
	}
	receipts, err := recycleBinExecutionReceipts(ctx, s.receipts)
	if err != nil {
		return nil, mapping.ToRecycleBinStatusError(err)
	}
	receipts = receiptsForCapability(receipts, semantic.CapabilityName)
	if request.MinCatalogRevision != nil && *request.MinCatalogRevision > receipts.CatalogRevision {
		return nil, mapping.ToRecycleBinStatusError(&semantic.Failure{
			Category: semantic.FailureStaleState, Receipts: &receipts,
			StaleAxis: semantic.StaleAxisCatalog, RequiredRevision: *request.MinCatalogRevision,
			ServedRevision: receipts.CatalogRevision,
		})
	}
	if _, ok := RuntimeMetadataViewFromContext(ctx); !ok {
		return nil, mapping.ToRecycleBinStatusError(&semantic.Failure{Category: semantic.FailureUnavailable})
	}

	user := trusted.UserSession()
	repositoryCtx, sqlCounter := telemetry.WithSQLStatementCounter(ctx)
	result, err := s.lister.ListRecycleBin(repositoryCtx, &user, contractRecycleBinScope(request.Scope))
	s.metrics.observeRepository(recordsOperationRecycleBin, sqlCounter.Count())
	if err != nil {
		return nil, mapping.ToRecycleBinStatusError(classifyRecycleBinEngineError(err))
	}
	response, err := semanticRecycleBinResponse(result, receipts)
	if err != nil {
		return nil, mapping.ToRecycleBinStatusError(err)
	}
	wireResponse, err := mapping.ToWireListRecycleBinResponse(response)
	if err != nil {
		return nil, mapping.ToRecycleBinStatusError(err)
	}
	return wireResponse, nil
}

func contractRecycleBinScope(scope semantic.Scope) contract.RecycleBinScope {
	if scope == semantic.ScopeAll {
		return contract.RecycleBinScopeAll
	}
	return contract.RecycleBinScopeMine
}

func semanticRecycleBinResponse(result *contract.RecycleBinListResult, receipts semantic.Receipts) (semantic.Response, error) {
	if result == nil {
		return semantic.Response{}, &semantic.Failure{Category: semantic.FailureInternal}
	}
	response := semantic.Response{
		Items:         make([]semantic.Item, 0, len(result.Items)),
		FailedObjects: make([]semantic.ObjectFailure, 0, len(result.FailedObjects)),
		Receipts:      receipts,
	}
	for _, item := range result.Items {
		if item.ID != item.RecordID {
			return semantic.Response{}, &semantic.Failure{Category: semantic.FailureInternal}
		}
		response.Items = append(response.Items, semantic.Item{
			RecordID: item.RecordID, RecordName: item.RecordName, ObjectID: item.ObjectAPIName,
			DeletedBy: item.DeletedBy, DeletedDate: item.DeletedDate,
			CreatedDate: item.CreatedDate, LastModifiedDate: item.LastModifiedDate,
		})
	}
	for _, failure := range result.FailedObjects {
		response.FailedObjects = append(response.FailedObjects, semantic.ObjectFailure{
			ObjectID: failure.ObjectAPIName, Code: semanticRecycleBinFailureCode(failure.ErrorCode), Message: failure.Message,
		})
	}
	if err := semantic.ValidateResponse(response); err != nil {
		return semantic.Response{}, err
	}
	return response, nil
}

func semanticRecycleBinFailureCode(code string) semantic.ObjectFailureCode {
	switch contract.RecycleBinFailureCode(code) {
	case contract.RecycleBinFailureCodeForbidden:
		return semantic.ObjectFailureForbidden
	case contract.RecycleBinFailureCodeInvalid:
		return semantic.ObjectFailureInvalid
	case contract.RecycleBinFailureCodeInternal:
		return semantic.ObjectFailureInternal
	default:
		return semantic.ObjectFailureUnknown
	}
}

func recycleBinExecutionReceipts(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 classifyRecycleBinEngineError(err error) error {
	if errors.Is(err, context.Canceled) || errors.Is(err, context.DeadlineExceeded) {
		return err
	}
	if failure, ok := classifyRetriableEngineCondition(err); ok {
		return failure
	}
	var internal interface{ InternalCondition() bool }
	if errors.As(err, &internal) && internal.InternalCondition() {
		return &semantic.Failure{Category: semantic.FailureInternal}
	}
	var statuser interface{ HTTPStatus() int }
	if errors.As(err, &statuser) {
		switch statuser.HTTPStatus() {
		case http.StatusBadRequest:
			return &semantic.Failure{Category: semantic.FailureInvalidRequest, InvalidComponent: semantic.InvalidComponentOperation}
		case http.StatusUnauthorized:
			return &semantic.Failure{Category: semantic.FailureUnauthenticated}
		case http.StatusForbidden:
			return &semantic.Failure{Category: semantic.FailureForbidden}
		}
	}
	return &semantic.Failure{Category: semantic.FailureInternal}
}
