package xplan

import (
	"strings"
	"time"

	"github.com/tech/sendico/payments/storage/model"
	"github.com/tech/sendico/pkg/merrors"
	"github.com/tech/sendico/pkg/mlogger"
	paymenttypes "github.com/tech/sendico/pkg/payments/types"
	"go.uber.org/zap"
)

type svc struct {
	logger mlogger.Logger
}

type normalizedHop struct {
	index      uint32
	rail       model.Rail
	gateway    string
	instanceID string
	network    string
	role       paymenttypes.QuoteRouteHopRole
	pos        int
}

type expansion struct {
	steps            []Step
	lastMainRef      string
	refSeq           map[string]int
	externalObserved map[string]string
	feeHops          map[string]struct{}
	feeEmitted       map[string]struct{}
}

func newExpansion(feeHops map[string]struct{}) *expansion {
	return &expansion{
		refSeq:           map[string]int{},
		externalObserved: map[string]string{},
		feeHops:          feeHops,
		feeEmitted:       map[string]struct{}{},
	}
}

func (s *svc) Compile(in Input) (graph *Graph, err error) {
	logger := s.logger
	logger.Debug("Starting Compile",
		zap.String("intent_ref", strings.TrimSpace(in.IntentSnapshot.Ref)),
		zap.String("quote_ref", quoteRef(in.QuoteSnapshot)),
		zap.Int("policies_count", len(in.Policies)),
	)
	defer func(start time.Time) {
		fields := []zap.Field{zap.Int64("duration_ms", time.Since(start).Milliseconds())}
		if graph != nil {
			fields = append(fields,
				zap.String("route_ref", strings.TrimSpace(graph.RouteRef)),
				zap.String("readiness", strings.TrimSpace(string(graph.Readiness))),
				zap.Int("steps_count", len(graph.Steps)),
			)
		}
		if err != nil {
			logger.Warn("Failed to compile", append(fields, zap.Error(err))...)
			return
		}
		logger.Debug("Completed Compile", fields...)
	}(time.Now())

	if isEmptyIntentSnapshot(in.IntentSnapshot) {
		return nil, merrors.InvalidArgument("intent_snapshot is required")
	}
	if in.QuoteSnapshot == nil {
		return nil, merrors.InvalidArgument("quote_snapshot is required")
	}
	if in.QuoteSnapshot.Route == nil {
		return nil, merrors.InvalidArgument("quote_snapshot.route is required")
	}

	conditions := in.QuoteSnapshot.ExecutionConditions
	readiness := paymenttypes.QuoteExecutionReadinessUnspecified
	if conditions != nil {
		readiness = conditions.Readiness
	}
	if readiness == paymenttypes.QuoteExecutionReadinessIndicative {
		return nil, ErrNotExecutable
	}

	hops, err := normalizeRouteHops(in.QuoteSnapshot.Route, in.IntentSnapshot)
	if err != nil {
		return nil, err
	}

	feeHops, err := planFeeHops(hops, in.IntentSnapshot, in.QuoteSnapshot)
	if err != nil {
		return nil, err
	}

	ex := newExpansion(feeHops)
	appendGuards(ex, conditions)

	if len(hops) == 1 {
		if err := s.expandSingleHop(ex, hops[0], in.IntentSnapshot); err != nil {
			return nil, err
		}
	} else {
		for i := 0; i < len(hops)-1; i++ {
			from := hops[i]
			to := hops[i+1]

			policy := selectPolicy(from, to, in.Policies)
			if policy != nil {
				if err := s.applyPolicy(ex, *policy, from, to, in.IntentSnapshot); err != nil {
					return nil, err
				}
				continue
			}

			if err := s.applyDefaultBoundary(ex, from, to, in.IntentSnapshot); err != nil {
				return nil, err
			}
		}
	}

	if len(ex.steps) == 0 {
		return nil, merrors.InvalidArgument("compiled graph is empty")
	}

	graph = &Graph{
		RouteRef:  strings.TrimSpace(in.QuoteSnapshot.Route.RouteRef),
		Readiness: readiness,
		Steps:     ex.steps,
	}
	return graph, nil
}

func quoteRef(snapshot *model.PaymentQuoteSnapshot) string {
	if snapshot == nil {
		return ""
	}
	return strings.TrimSpace(snapshot.QuoteRef)
}
