package quote_computation_service

import (
	"context"
	"github.com/tech/sendico/pkg/discovery"
	"strings"

	"github.com/tech/sendico/payments/quotation/internal/service/quotation/graph_path_finder"
	"github.com/tech/sendico/payments/storage/model"
	"github.com/tech/sendico/pkg/merrors"
	"go.uber.org/zap"
)

func (s *QuoteComputationService) resolveRouteRails(
	ctx context.Context,
	sourceRail model.Rail,
	destinationRail model.Rail,
	network string,
) ([]model.Rail, error) {
	sourceRail = model.ParseRail(string(sourceRail))
	destinationRail = model.ParseRail(string(destinationRail))

	s.logger.Debug("Resolving route rails",
		zap.String("source_rail", string(sourceRail)),
		zap.String("dest_rail", string(destinationRail)),
		zap.String("network", network),
	)

	if sourceRail == discovery.RailUnspecified {
		s.logger.Warn("Route rails resolution failed: source rail is unspecified")

		return nil, merrors.InvalidArgument("source rail is required")
	}

	if destinationRail == discovery.RailUnspecified {
		s.logger.Warn("Route rails resolution failed: destination rail is unspecified")

		return nil, merrors.InvalidArgument("destination rail is required")
	}

	if sourceRail == destinationRail {
		s.logger.Debug("Route rails resolved: same rail, no path finding needed",
			zap.String("rail", string(sourceRail)),
		)

		return []model.Rail{sourceRail}, nil
	}

	strictGraph := s != nil && s.routeStore != nil

	s.logger.Debug("Loading route graph edges",
		zap.Bool("strict_graph", strictGraph),
	)

	edges, err := s.routeGraphEdges(ctx)
	if err != nil {
		s.logger.Warn("Route rails resolution failed: route graph edges load error",
			zap.String("source_rail", string(sourceRail)),
			zap.String("dest_rail", string(destinationRail)),
			zap.Error(err),
		)

		return nil, err
	}

	s.logger.Debug("Route graph edges loaded",
		zap.Int("edge_count", len(edges)),
		zap.Bool("strict_graph", strictGraph),
	)

	if len(edges) == 0 {
		if strictGraph {
			s.logger.Warn("Route rails resolution failed: route graph has no edges",
				zap.String("source_rail", string(sourceRail)),
				zap.String("dest_rail", string(destinationRail)),
			)

			return nil, merrors.InvalidArgument("route graph has no edges")
		}

		s.logger.Debug("Route graph has no edges, using fallback path",
			zap.String("source_rail", string(sourceRail)),
			zap.String("dest_rail", string(destinationRail)),
		)

		return fallbackRouteRails(sourceRail, destinationRail), nil
	}

	pathFinder := s.pathFinder
	if pathFinder == nil {
		pathFinder = graph_path_finder.New()
	}

	s.logger.Debug("Finding route path",
		zap.String("source_rail", string(sourceRail)),
		zap.String("dest_rail", string(destinationRail)),
		zap.String("network", network),
	)

	path, findErr := pathFinder.Find(graph_path_finder.FindInput{
		SourceRail:      sourceRail,
		DestinationRail: destinationRail,
		Network:         strings.ToUpper(strings.TrimSpace(network)),
		Edges:           edges,
	})
	if findErr != nil {
		if strictGraph {
			s.logger.Warn("Route rails resolution failed: path finding error",
				zap.String("source_rail", string(sourceRail)),
				zap.String("dest_rail", string(destinationRail)),
				zap.String("network", network),
				zap.Error(findErr),
			)

			return nil, findErr
		}

		s.logger.Debug("Route path finding failed, using fallback path",
			zap.String("source_rail", string(sourceRail)),
			zap.String("dest_rail", string(destinationRail)),
			zap.String("network", network),
			zap.Error(findErr),
		)

		return fallbackRouteRails(sourceRail, destinationRail), nil
	}

	if path == nil || len(path.Rails) == 0 {
		if strictGraph {
			s.logger.Warn("Route rails resolution failed: path is empty",
				zap.String("source_rail", string(sourceRail)),
				zap.String("dest_rail", string(destinationRail)),
				zap.String("network", network),
			)

			return nil, merrors.InvalidArgument("route path is empty")
		}

		s.logger.Debug("Route path is empty, using fallback path",
			zap.String("source_rail", string(sourceRail)),
			zap.String("dest_rail", string(destinationRail)),
			zap.String("network", network),
		)

		return fallbackRouteRails(sourceRail, destinationRail), nil
	}

	s.logger.Debug("Route rails resolved",
		zap.String("source_rail", string(sourceRail)),
		zap.String("dest_rail", string(destinationRail)),
		zap.Int("rail_count", len(path.Rails)),
	)

	return append([]model.Rail(nil), path.Rails...), nil
}

func (s *QuoteComputationService) routeGraphEdges(ctx context.Context) ([]graph_path_finder.Edge, error) {
	if s == nil || s.routeStore == nil {
		s.logger.Debug("Route graph edges skipped: no route store configured")

		return nil, nil
	}

	enabled := true
	routes, err := s.routeStore.List(ctx, &model.PaymentRouteFilter{IsEnabled: &enabled})
	if err != nil {
		s.logger.Warn("Route graph edges load failed",
			zap.Error(err),
		)

		return nil, err
	}

	if routes == nil || len(routes.Items) == 0 {
		s.logger.Debug("Route graph edges: no routes found")

		return nil, nil
	}

	edges := make([]graph_path_finder.Edge, 0, len(routes.Items))
	for _, route := range routes.Items {
		if route == nil || !route.IsEnabled {
			continue
		}

		from := model.ParseRail(string(route.FromRail))
		to := model.ParseRail(string(route.ToRail))

		if from == discovery.RailUnspecified || to == discovery.RailUnspecified {
			continue
		}

		edges = append(edges, graph_path_finder.Edge{
			FromRail: from,
			ToRail:   to,
			Network:  strings.ToUpper(strings.TrimSpace(route.Network)),
		})
	}

	s.logger.Debug("Route graph edges built",
		zap.Int("route_count", len(routes.Items)),
		zap.Int("edge_count", len(edges)),
	)

	return edges, nil
}

func fallbackRouteRails(sourceRail, destinationRail model.Rail) []model.Rail {
	if sourceRail == destinationRail {
		return []model.Rail{sourceRail}
	}

	if requiresTransitBridgeStep(sourceRail, destinationRail) {
		return []model.Rail{sourceRail, discovery.RailLedger, destinationRail}
	}

	return []model.Rail{sourceRail, destinationRail}
}
