package orchestrator

import (
	"context"

	"github.com/tech/sendico/payments/orchestrator/storage/model"
	"github.com/tech/sendico/pkg/merrors"
	"github.com/tech/sendico/pkg/mlogger"
	"github.com/tech/sendico/pkg/mutil/mzap"
	orchestratorv1 "github.com/tech/sendico/pkg/proto/payments/orchestrator/v1"
	"go.uber.org/zap"
)

type defaultPlanBuilder struct {
	logger mlogger.Logger
}

func newDefaultPlanBuilder(logger mlogger.Logger) *defaultPlanBuilder {
	return &defaultPlanBuilder{
		logger: logger.Named("plan_builder"),
	}
}

func (b *defaultPlanBuilder) Build(ctx context.Context, payment *model.Payment, quote *orchestratorv1.PaymentQuote, routes RouteStore, templates PlanTemplateStore, gateways GatewayRegistry) (*model.PaymentPlan, error) {
	if payment == nil {
		return nil, merrors.InvalidArgument("plan builder: payment is required")
	}
	if routes == nil {
		return nil, merrors.InvalidArgument("plan builder: routes store is required")
	}
	if templates == nil {
		return nil, merrors.InvalidArgument("plan builder: plan templates store is required")
	}

	logger := b.logger.With(
		zap.String("payment_ref", payment.PaymentRef),
		zap.String("payment_kind", string(payment.Intent.Kind)),
	)
	logger.Debug("Building payment plan")

	intent := payment.Intent
	if intent.Kind == model.PaymentKindFXConversion {
		logger.Debug("Building fx conversion plan")
		plan, err := buildFXConversionPlan(payment)
		if err != nil {
			logger.Warn("Failed to build fx conversion plan", zap.Error(err))
			return nil, err
		}
		logger.Info("fx conversion plan built", zap.Int("steps", len(plan.Steps)))
		return plan, nil
	}

	sourceRail, sourceNetwork, err := railFromEndpoint(intent.Source, intent.Attributes, true)
	if err != nil {
		logger.Warn("Failed to resolve source rail", zap.Error(err))
		return nil, err
	}
	destRail, destNetwork, err := railFromEndpoint(intent.Destination, intent.Attributes, false)
	if err != nil {
		logger.Warn("Failed to resolve destination rail", zap.Error(err))
		return nil, err
	}

	logger = logger.With(
		zap.String("source_rail", string(sourceRail)),
		zap.String("dest_rail", string(destRail)),
		zap.String("source_network", sourceNetwork),
		zap.String("dest_network", destNetwork),
	)

	if sourceRail == model.RailUnspecified || destRail == model.RailUnspecified {
		logger.Warn("Source and destination rails are required")
		return nil, merrors.InvalidArgument("plan builder: source and destination rails are required")
	}
	if sourceRail == destRail && sourceRail != model.RailLedger {
		logger.Warn("Unsupported same-rail payment")
		return nil, merrors.InvalidArgument("plan builder: unsupported same-rail payment")
	}

	network, err := resolveRouteNetwork(intent.Attributes, sourceNetwork, destNetwork)
	if err != nil {
		logger.Warn("Failed to resolve route network", zap.Error(err))
		return nil, err
	}
	logger = logger.With(zap.String("network", network))

	route, err := selectRoute(ctx, routes, sourceRail, destRail, network)
	if err != nil {
		logger.Warn("Failed to select route", zap.Error(err))
		return nil, err
	}
	logger.Debug("Route selected", mzap.StorableRef(route))

	template, err := selectPlanTemplate(ctx, logger, templates, sourceRail, destRail, network)
	if err != nil {
		logger.Warn("Failed to select plan template", zap.Error(err))
		return nil, err
	}
	logger.Debug("Plan template selected", mzap.StorableRef(template))

	return b.buildPlanFromTemplate(ctx, payment, quote, template, sourceRail, destRail, sourceNetwork, destNetwork, gateways)
}
