package psvc

import (
	"time"

	"github.com/tech/sendico/payments/orchestrator/internal/service/orchestrationv2/agg"
	"github.com/tech/sendico/payments/orchestrator/internal/service/orchestrationv2/erecon"
	"github.com/tech/sendico/payments/orchestrator/internal/service/orchestrationv2/idem"
	"github.com/tech/sendico/payments/orchestrator/internal/service/orchestrationv2/opagg"

	"github.com/tech/sendico/payments/orchestrator/internal/service/orchestrationv2/oobs"
	"github.com/tech/sendico/payments/orchestrator/internal/service/orchestrationv2/ostate"
	"github.com/tech/sendico/payments/orchestrator/internal/service/orchestrationv2/pquery"
	"github.com/tech/sendico/payments/orchestrator/internal/service/orchestrationv2/prepo"
	"github.com/tech/sendico/payments/orchestrator/internal/service/orchestrationv2/prmap"
	"github.com/tech/sendico/payments/orchestrator/internal/service/orchestrationv2/qsnap"
	"github.com/tech/sendico/payments/orchestrator/internal/service/orchestrationv2/reqval"
	"github.com/tech/sendico/payments/orchestrator/internal/service/orchestrationv2/sexec"
	"github.com/tech/sendico/payments/orchestrator/internal/service/orchestrationv2/ssched"
	"github.com/tech/sendico/payments/orchestrator/internal/service/orchestrationv2/xplan"
	"github.com/tech/sendico/pkg/merrors"
	"github.com/tech/sendico/pkg/mlogger"
	"go.uber.org/zap"
)

const (
	defaultMaxTicks = 32
)

type svc struct {
	logger mlogger.Logger

	quoteStore qsnap.Store

	validator      reqval.Validator
	idempotency    idem.Service
	quote          qsnap.Resolver
	batchOptimizer BatchOptimizer
	aggregate      agg.Factory
	planner        xplan.Compiler
	state          ostate.StateMachine
	scheduler      ssched.Runtime
	executors      sexec.Registry
	reconciler     erecon.Reconciler
	repository     prepo.Repository
	query          pquery.Service
	mapper         prmap.Mapper
	observer       oobs.Observer
	statuses       paymentStatusPublisher

	retryPolicy ssched.RetryPolicy
	now         func() time.Time
	maxTicks    int
}

func newService(deps Dependencies) (Service, error) {
	if deps.QuoteStore == nil {
		return nil, merrors.InvalidArgument("quote store is required")
	}
	if deps.Repository == nil {
		return nil, merrors.InvalidArgument("payment repository v2 is required")
	}

	logger := deps.Logger
	if logger == nil {
		logger = zap.NewNop()
	}
	logger = logger.Named("imp")

	observer := deps.Observer
	if observer == nil {
		var err error
		observer, err = oobs.New(oobs.Dependencies{Logger: logger})
		if err != nil {
			return nil, err
		}
	}

	query := deps.Query
	if query == nil {
		var err error
		query, err = pquery.New(pquery.Dependencies{
			Repository: deps.Repository,
			Logger:     logger,
		})
		if err != nil {
			return nil, err
		}
	}

	out := &svc{
		logger: logger,

		quoteStore: deps.QuoteStore,

		validator:      firstValidator(deps.Validator, logger),
		idempotency:    firstIdempotency(deps.Idempotency, logger),
		quote:          firstQuoteResolver(deps.Quote, logger),
		batchOptimizer: firstBatchOptimizer(deps.BatchOptimizer, deps.BatchOptimizationPolicy, deps.Aggregator, logger),
		aggregate:      firstAggregateFactory(deps.Aggregate, logger),
		planner:        firstPlanCompiler(deps.Planner, logger),
		state:          firstStateMachine(deps.State, logger),
		scheduler:      firstScheduler(deps.Scheduler, logger),
		executors:      firstExecutors(deps.Executors, logger),
		reconciler:     firstReconciler(deps.Reconciler, logger),
		repository:     deps.Repository,
		query:          query,
		mapper:         firstMapper(deps.Mapper, logger),
		observer:       observer,
		statuses:       newPaymentStatusPublisher(logger, deps.Producer),

		retryPolicy: deps.RetryPolicy,
		now:         deps.Now,
		maxTicks:    deps.MaxTicks,
	}
	if out.now == nil {
		out.now = func() time.Time { return time.Now().UTC() }
	}
	if out.maxTicks <= 0 {
		out.maxTicks = defaultMaxTicks
	}
	if out.retryPolicy.MaxAttempts == 0 {
		out.retryPolicy.MaxAttempts = 2
	}
	return out, nil
}

func firstValidator(v reqval.Validator, logger mlogger.Logger) reqval.Validator {
	if v != nil {
		return v
	}
	return reqval.New(reqval.Dependencies{Logger: logger})
}

func firstIdempotency(v idem.Service, logger mlogger.Logger) idem.Service {
	if v != nil {
		return v
	}
	return idem.New(idem.Dependencies{Logger: logger})
}

func firstQuoteResolver(v qsnap.Resolver, logger mlogger.Logger) qsnap.Resolver {
	if v != nil {
		return v
	}
	return qsnap.New(qsnap.Dependencies{Logger: logger})
}

func firstAggregateFactory(v agg.Factory, logger mlogger.Logger) agg.Factory {
	if v != nil {
		return v
	}
	return agg.New(agg.Dependencies{Logger: logger})
}

func firstBatchOptimizer(v BatchOptimizer, policy BatchOptimizationPolicy, aggregator opagg.Aggregator, logger mlogger.Logger) BatchOptimizer {
	if v != nil {
		return v
	}
	return NewPolicyBatchOptimizer(PolicyBatchOptimizerDependencies{
		Logger:     logger,
		Aggregator: firstAggregator(aggregator, logger),
		Policy:     policy,
	})
}

func firstAggregator(v opagg.Aggregator, logger mlogger.Logger) opagg.Aggregator {
	if v != nil {
		return v
	}
	return opagg.New(opagg.Dependencies{Logger: logger})
}

func firstPlanCompiler(v xplan.Compiler, logger mlogger.Logger) xplan.Compiler {
	if v != nil {
		return v
	}
	return xplan.New(xplan.Dependencies{Logger: logger})
}

func firstStateMachine(v ostate.StateMachine, logger mlogger.Logger) ostate.StateMachine {
	if v != nil {
		return v
	}
	return ostate.New(ostate.Dependencies{Logger: logger})
}

func firstScheduler(v ssched.Runtime, logger mlogger.Logger) ssched.Runtime {
	if v != nil {
		return v
	}
	return ssched.New(ssched.Dependencies{Logger: logger})
}

func firstExecutors(v sexec.Registry, logger mlogger.Logger) sexec.Registry {
	if v != nil {
		return v
	}
	return NewDefaultExecutors(logger, sexec.Dependencies{})
}

func firstReconciler(v erecon.Reconciler, logger mlogger.Logger) erecon.Reconciler {
	if v != nil {
		return v
	}
	return erecon.New(erecon.Dependencies{Logger: logger})
}

func firstMapper(v prmap.Mapper, logger mlogger.Logger) prmap.Mapper {
	if v != nil {
		return v
	}
	return prmap.New(prmap.Dependencies{Logger: logger})
}

func (s *svc) nowUTC() time.Time {
	return s.now().UTC()
}
