package connector_outboundintent

import (
	"context"
	"fmt"
	"time"

	outboundintentv1 "github.com/pingcap-inc/nexuscrm-contract/pkg/runtime/outboundintentv1"
)

const outboundIntentRPCTimeout = 10 * time.Second

type IntentSource interface {
	ListPendingOutboundIntents(context.Context, outboundintentv1.ListRequest) (outboundintentv1.ListResponse, error)
	AcknowledgeOutboundIntent(context.Context, outboundintentv1.AcknowledgeRequest) (outboundintentv1.AcknowledgeResponse, error)
}

// intentGenerationSource is an additive scheduling capability rather than a
// new requirement on the established exported IntentSource contract. Acceptor
// consumers that only perform the durable handoff remain source-compatible;
// Service requires this capability explicitly because it owns generation-
// driven scheduling.
type intentGenerationSource interface {
	ReadOutboundIntentGeneration(context.Context, outboundintentv1.GenerationRequest) (outboundintentv1.GenerationResponse, error)
}

type intentStore interface {
	VerifySchema(context.Context) error
	Accept(context.Context, Scope, outboundintentv1.Intent) (Command, bool, error)
}

// Acceptor closes the durable handoff in the only safe order: commit the
// Integration-owned command first, then acknowledge Runtime. A lost response
// repeats both operations idempotently and never loses an intent.
type Acceptor struct {
	source IntentSource
	store  intentStore
	scope  Scope
}

func NewAcceptor(source IntentSource, store intentStore, scope Scope) (*Acceptor, error) {
	if source == nil || store == nil || scope.validate() != nil {
		return nil, ErrInvalidInput
	}
	return &Acceptor{source: source, store: store, scope: scope}, nil
}

func (a *Acceptor) VerifyReady(ctx context.Context) error {
	if a == nil || a.store == nil || ctx == nil {
		return ErrDependency
	}
	return a.store.VerifySchema(ctx)
}

func (a *Acceptor) ReadGeneration(ctx context.Context, after uint64) (uint64, error) {
	if a == nil || a.source == nil || ctx == nil {
		return 0, ErrDependency
	}
	generationSource, ok := a.source.(intentGenerationSource)
	if !ok {
		return 0, ErrDependency
	}
	request := outboundintentv1.GenerationRequest{AfterGeneration: after}
	readCtx, cancel := context.WithTimeout(ctx, outboundIntentRPCTimeout)
	defer cancel()
	if outboundintentv1.ValidateGenerationRequest(readCtx, request) != nil {
		return 0, ErrInvalidInput
	}
	response, err := generationSource.ReadOutboundIntentGeneration(readCtx, request)
	if err != nil {
		return 0, fmt.Errorf("outbound generation RPC: %w", err)
	}
	if outboundintentv1.ValidateGenerationResponse(request, response) != nil ||
		response.Receipts.CatalogRevision != a.scope.CatalogRevision {
		return 0, ErrIdentityConflict
	}
	return response.Generation, nil
}

func (a *Acceptor) ProcessBatch(ctx context.Context) (int, error) {
	if a == nil || a.source == nil || a.store == nil || ctx == nil {
		return 0, ErrDependency
	}
	revision := a.scope.CatalogRevision
	request := outboundintentv1.ListRequest{
		Limit:                  outboundintentv1.MaxIntentsPerList,
		MinCatalogRevision:     &revision,
		MaxOperationSetVersion: outboundintentv1.CurrentOperationSetVersion,
	}
	listCtx, cancelList := context.WithTimeout(ctx, outboundIntentRPCTimeout)
	if err := outboundintentv1.ValidateListRequest(listCtx, request); err != nil {
		cancelList()
		return 0, ErrInvalidInput
	}
	listed, err := a.source.ListPendingOutboundIntents(listCtx, request)
	cancelList()
	if err != nil {
		return 0, fmt.Errorf("outbound list RPC: %w", err)
	}
	if outboundintentv1.ValidateListResponseFor(request, listed) != nil ||
		listed.Receipts.CatalogRevision != revision {
		return 0, ErrIdentityConflict
	}
	processed := 0
	for _, intent := range listed.Intents {
		command, _, acceptErr := a.store.Accept(ctx, a.scope, intent)
		if acceptErr != nil {
			return processed, fmt.Errorf("connector outbound intent durable acceptance: %w", acceptErr)
		}
		request := outboundintentv1.AcknowledgeRequest{
			IntentID: intent.IntentID, PayloadDigest: intent.PayloadDigest,
			CatalogRevision: intent.CatalogRevision, Outcome: outboundintentv1.AdmissionAccepted,
			IntegrationCommandID: command.CommandID,
		}
		acknowledgeCtx, cancelAcknowledge := context.WithTimeout(ctx, outboundIntentRPCTimeout)
		if outboundintentv1.ValidateAcknowledgeRequest(acknowledgeCtx, request) != nil ||
			outboundintentv1.ValidateAcknowledgeRequestForIntent(intent, request) != nil {
			cancelAcknowledge()
			return processed, ErrIdentityConflict
		}
		response, acknowledgeErr := a.source.AcknowledgeOutboundIntent(acknowledgeCtx, request)
		cancelAcknowledge()
		if acknowledgeErr != nil {
			return processed, fmt.Errorf("connector outbound intent Runtime acknowledgement: %w", acknowledgeErr)
		}
		if outboundintentv1.ValidateAcknowledgeResponse(request, response) != nil {
			return processed, ErrIdentityConflict
		}
		processed++
	}
	return processed, nil
}
