package orchestrator

import (
	"context"

	ledgerclient "github.com/tech/sendico/ledger/client"
	"github.com/tech/sendico/payments/orchestrator/internal/service/orchestrationv2/prepo"
	"github.com/tech/sendico/payments/orchestrator/internal/service/orchestrationv2/psvc"
	"github.com/tech/sendico/payments/storage"
	"github.com/tech/sendico/pkg/api/routers"
	msg "github.com/tech/sendico/pkg/messaging"
	mb "github.com/tech/sendico/pkg/messaging/broker"
	"github.com/tech/sendico/pkg/mlogger"
	orchestrationv2 "github.com/tech/sendico/pkg/proto/payments/orchestration/v2"
	"go.uber.org/zap"
	"google.golang.org/grpc"
)

// Service is a v2-only payment orchestrator gRPC adapter.
type Service struct {
	logger      mlogger.Logger
	repo        storage.Repository
	v2          psvc.Service
	paymentRepo prepo.Repository
	producer    msg.Producer

	ledgerClient            ledgerclient.Client
	gatewayInvokeResolver   GatewayInvokeResolver
	gatewayRegistry         GatewayRegistry
	cardGatewayRoutes       map[string]CardGatewayRoute
	batchOptimizationPolicy psvc.BatchOptimizationPolicy
	paymentGatewayBroker    mb.Broker
	gatewayConsumers        []msg.Consumer
	stopExternalWorkers     context.CancelFunc
}

// NewService constructs the v2 orchestrator service.
func NewService(logger mlogger.Logger, repo storage.Repository, producer msg.Producer, opts ...Option) (*Service, error) {
	if logger == nil {
		logger = zap.NewNop()
	}

	svc := &Service{
		logger:   logger.Named("service"),
		repo:     repo,
		producer: producer,
	}

	for _, opt := range opts {
		if opt != nil {
			opt(svc)
		}
	}

	var err error
	svc.v2, svc.paymentRepo, err = newOrchestrationV2Service(svc.logger, repo, v2RuntimeDeps{
		LedgerClient:            svc.ledgerClient,
		GatewayInvokeResolver:   svc.gatewayInvokeResolver,
		GatewayRegistry:         svc.gatewayRegistry,
		CardGatewayRoutes:       svc.cardGatewayRoutes,
		BatchOptimizationPolicy: svc.batchOptimizationPolicy,
		Producer:                svc.producer,
	})
	svc.startExternalRuntime()
	if err != nil {
		svc.logger.Error("Failed to initialize", zap.Error(err))
	}
	return svc, err
}

// Register attaches the service to the supplied gRPC router.
func (s *Service) Register(router routers.GRPC) error {
	if s == nil || s.v2 == nil {
		return nil
	}
	return router.Register(func(reg grpc.ServiceRegistrar) {
		orchestrationv2.RegisterPaymentOrchestratorServiceServer(reg, newV2GRPCServer(s.v2, s.logger))
	})
}

// Shutdown releases runtime resources.
func (s *Service) Shutdown() {
	if s == nil {
		return
	}
	if s.stopExternalWorkers != nil {
		s.stopExternalWorkers()
		s.stopExternalWorkers = nil
	}
	for i := range s.gatewayConsumers {
		if s.gatewayConsumers[i] != nil {
			s.gatewayConsumers[i].Close()
		}
	}
	s.gatewayConsumers = nil
}
