package orchestrator

import (
	"context"
	"sort"
	"strings"

	"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"
	"go.uber.org/zap"
)

func selectPlanTemplate(ctx context.Context, logger mlogger.Logger, templates PlanTemplateStore, sourceRail, destRail model.Rail, network string) (*model.PaymentPlanTemplate, error) {
	if templates == nil {
		return nil, merrors.InvalidArgument("plan builder: plan templates store is required")
	}

	logger = logger.With(
		zap.String("source_rail", string(sourceRail)),
		zap.String("dest_rail", string(destRail)),
		zap.String("network", network),
	)
	logger.Debug("Selecting plan template")

	enabled := true
	result, err := templates.List(ctx, &model.PaymentPlanTemplateFilter{
		FromRail:  sourceRail,
		ToRail:    destRail,
		IsEnabled: &enabled,
	})
	if err != nil {
		logger.Warn("Failed to list plan templates", zap.Error(err))
		return nil, err
	}
	if result == nil || len(result.Items) == 0 {
		logger.Warn("No plan templates found for route")
		return nil, merrors.InvalidArgument("plan builder: plan template missing")
	}

	logger.Debug("Fetched plan templates", zap.Int("total", len(result.Items)))

	candidates := make([]*model.PaymentPlanTemplate, 0, len(result.Items))
	for _, tpl := range result.Items {
		if tpl == nil || !tpl.IsEnabled {
			continue
		}
		if tpl.FromRail != sourceRail || tpl.ToRail != destRail {
			continue
		}
		if !templateMatchesNetwork(tpl, network) {
			logger.Debug("Template network mismatch, skipping",
				mzap.StorableRef(tpl),
				zap.String("template_network", tpl.Network))
			continue
		}
		if err := validatePlanTemplate(logger, tpl); err != nil {
			return nil, err
		}
		candidates = append(candidates, tpl)
	}
	if len(candidates) == 0 {
		logger.Warn("No valid plan template candidates after filtering")
		return nil, merrors.InvalidArgument("plan builder: plan template missing")
	}

	logger.Debug("Plan template candidates filtered", zap.Int("candidates", len(candidates)))

	sort.Slice(candidates, func(i, j int) bool {
		pi := templatePriority(candidates[i], network)
		pj := templatePriority(candidates[j], network)
		if pi != pj {
			return pi < pj
		}
		if candidates[i].Network != candidates[j].Network {
			return candidates[i].Network < candidates[j].Network
		}
		return candidates[i].ID.Hex() < candidates[j].ID.Hex()
	})

	selected := candidates[0]
	logger.Debug("Plan template selected",
		mzap.StorableRef(selected),
		zap.String("template_network", selected.Network),
		zap.Int("steps", len(selected.Steps)),
		zap.Int("priority", templatePriority(selected, network)))

	return selected, nil
}

func templateMatchesNetwork(template *model.PaymentPlanTemplate, network string) bool {
	if template == nil {
		return false
	}
	templateNetwork := strings.ToUpper(strings.TrimSpace(template.Network))
	net := strings.ToUpper(strings.TrimSpace(network))
	if templateNetwork == "" {
		return true
	}
	if net == "" {
		return false
	}
	return strings.EqualFold(templateNetwork, net)
}

func templatePriority(template *model.PaymentPlanTemplate, network string) int {
	if template == nil {
		return 2
	}
	templateNetwork := strings.ToUpper(strings.TrimSpace(template.Network))
	net := strings.ToUpper(strings.TrimSpace(network))
	if net != "" && strings.EqualFold(templateNetwork, net) {
		return 0
	}
	if templateNetwork == "" {
		return 1
	}
	return 2
}

func validatePlanTemplate(logger mlogger.Logger, template *model.PaymentPlanTemplate) error {
	if template == nil {
		return merrors.InvalidArgument("plan builder: plan template is required")
	}

	logger = logger.With(
		mzap.StorableRef(template),
		zap.String("from_rail", string(template.FromRail)),
		zap.String("to_rail", string(template.ToRail)),
		zap.String("network", template.Network),
	)
	logger.Debug("Validating plan template")

	if len(template.Steps) == 0 {
		logger.Warn("Plan template has no steps")
		return merrors.InvalidArgument("plan builder: plan template steps are required")
	}

	seen := map[string]struct{}{}
	for idx, step := range template.Steps {
		id := strings.TrimSpace(step.StepID)
		if id == "" {
			logger.Warn("Plan template step missing ID", zap.Int("step_index", idx))
			return merrors.InvalidArgument("plan builder: plan template step id is required")
		}
		if _, exists := seen[id]; exists {
			logger.Warn("Duplicate plan template step ID", zap.String("step_id", id))
			return merrors.InvalidArgument("plan builder: plan template step id must be unique")
		}
		seen[id] = struct{}{}
		if strings.TrimSpace(step.Operation) == "" {
			logger.Warn("Plan template step missing operation", zap.String("step_id", id),
				zap.Int("step_index", idx))
			return merrors.InvalidArgument("plan builder: plan template operation is required")
		}
		action, err := actionForOperation(step.Operation)
		if err != nil {
			logger.Warn("Plan template step has invalid operation", zap.String("step_id", id),
				zap.String("operation", step.Operation), zap.Error(err))
			return err
		}
		if step.Rail == model.RailLedger && action == model.RailOperationMove {
			if step.FromRole == nil || strings.TrimSpace(string(*step.FromRole)) == "" {
				logger.Warn("Ledger move step missing fromRole", zap.String("step_id", id),
					zap.String("operation", step.Operation))
				return merrors.InvalidArgument("plan builder: ledger.move fromRole is required")
			}
			if step.ToRole == nil || strings.TrimSpace(string(*step.ToRole)) == "" {
				logger.Warn("Ledger move step missing toRole", zap.String("step_id", id),
					zap.String("operation", step.Operation))
				return merrors.InvalidArgument("plan builder: ledger.move toRole is required")
			}
			from := strings.ToLower(strings.TrimSpace(string(*step.FromRole)))
			to := strings.ToLower(strings.TrimSpace(string(*step.ToRole)))
			if from == "" || to == "" || strings.EqualFold(from, to) {
				logger.Warn("Ledger move step has invalid roles", zap.String("step_id", id),
					zap.String("from_role", from), zap.String("to_role", to))
				return merrors.InvalidArgument("plan builder: ledger.move fromRole and toRole must differ")
			}
		}
	}

	for _, step := range template.Steps {
		for _, dep := range step.DependsOn {
			depID := strings.TrimSpace(dep)
			if _, ok := seen[depID]; !ok {
				logger.Warn("Plan template step has missing dependency", zap.String("step_id", step.StepID),
					zap.String("missing_dependency", depID))
				return merrors.InvalidArgument("plan builder: plan template dependency missing")
			}
		}
		for _, dep := range step.CommitAfter {
			depID := strings.TrimSpace(dep)
			if _, ok := seen[depID]; !ok {
				logger.Warn("Plan template step has missing commit dependency", zap.String("step_id", step.StepID),
					zap.String("missing_commit_dependency", depID))
				return merrors.InvalidArgument("plan builder: plan template commit dependency missing")
			}
		}
	}

	logger.Debug("Plan template validation successful", zap.Int("steps", len(template.Steps)))
	return nil
}
