package services

import (
	"errors"

	"gitea.hostxtra.co.uk/mrhid6/vantage/server/internal/models"
)

// ErrNoTargets means a workflow named no servers and matched none. Handlers
// map it to 400: a workflow that matches nothing must say so rather than
// report success over zero servers.
var ErrNoTargets = errors.New("workflow has no target servers")

// MatchesTags reports whether srv carries every pair in sel — AND across keys.
// An empty selector matches nothing. That is deliberate: the alternative,
// "matches everything", turns a cleared field in the workflow designer into a
// fleet-wide run.
func MatchesTags(srv models.Server, sel map[string]string) bool {
	if len(sel) == 0 {
		return false
	}
	for k, v := range sel {
		if srv.Tags[k] != v {
			return false
		}
	}
	return true
}

// UnionTargets returns the distinct union of the servers named by ids and
// those matching sel, in the order they appear in all.
//
// Order comes from the fleet rather than the arguments so that two workflows
// naming the same servers in a different order still run them in the same
// order, which makes two runs comparable line by line.
//
// Offline servers are NOT filtered out. The dispatcher already answers 503 per
// server, and a patch run that silently omits an unreachable machine is worse
// than one that visibly fails on it.
func UnionTargets(all []models.Server, ids []string, sel map[string]string) []models.Server {
	named := make(map[string]bool, len(ids))
	for _, id := range ids {
		named[id] = true
	}

	out := make([]models.Server, 0, len(ids)+len(all))
	for _, s := range all {
		if named[s.ServerID] || MatchesTags(s, sel) {
			out = append(out, s)
		}
	}
	return out
}

// ResolveTargets is the database-backed wrapper around UnionTargets. It is the
// single answer to "which servers does this workflow touch", used by the run
// path and by validation alike, so the two cannot disagree.
func ResolveTargets(instanceID string, ids []string, sel map[string]string) ([]models.Server, error) {
	all, err := ListServers(instanceID)
	if err != nil {
		return nil, err
	}
	matched := UnionTargets(all, ids, sel)
	if len(matched) == 0 {
		return nil, ErrNoTargets
	}
	return matched, nil
}
