package services

import (
	"context"
	"encoding/json"
	"log"

	"gitea.hostxtra.co.uk/mrhid6/vantage/server/internal/bus"
	"gitea.hostxtra.co.uk/mrhid6/vantage/server/internal/grpc/pb"
)

// Step results travel back over the bus for the same reason commands travel out
// over it: the pod driving a workflow run and the pod holding the agent's
// stream are two different processes, and a map in one of them cannot be read
// by the other.
//
// Await subscribes before the command is dispatched (see dispatchAndWait),
// which is what stops a fast agent from answering into a channel nobody is
// listening on yet.

type stepResultRegistry struct{}

var StepResults = &stepResultRegistry{}

// Await subscribes to a command's result channel. The returned cancel function
// must be called once the caller is done, whether a result arrived or not —
// it is what releases the Redis subscription.
func (r *stepResultRegistry) Await(commandID string) (<-chan *pb.StepResult, func()) {
	out := make(chan *pb.StepResult, 1)

	ctx, cancel := context.WithCancel(context.Background())
	raw, unsub, err := bus.Subscribe(ctx, bus.ResultChannel+commandID)
	if err != nil {
		log.Printf("step results: subscribe for %s: %v", commandID, err)
		cancel()
		close(out)
		return out, func() {}
	}

	go func() {
		defer close(out)
		select {
		case <-ctx.Done():
			return
		case b, ok := <-raw:
			if !ok {
				return
			}
			var res pb.StepResult
			if err := json.Unmarshal(b, &res); err != nil {
				log.Printf("step results: undecodable result for %s: %v", commandID, err)
				return
			}
			out <- &res
		}
	}()

	return out, func() {
		cancel()
		unsub()
	}
}

// Deliver publishes a result received from an agent. Called on the pod holding
// that agent's stream, which is not usually the pod waiting for it.
func (r *stepResultRegistry) Deliver(res *pb.StepResult) {
	if res == nil || res.CommandId == "" {
		return
	}
	ctx, cancel := context.WithTimeout(context.Background(), dispatchAckTimeout)
	defer cancel()
	if _, err := bus.Publish(ctx, bus.ResultChannel+res.CommandId, res); err != nil {
		log.Printf("step results: publish for %s: %v", res.CommandId, err)
	}
}
