package engine

import (
	"context"
	"fmt"
	"log/slog"

	"git.cloudinit.dev/coreci/orca/internal/model"
	"git.cloudinit.dev/coreci/orca/internal/store"
)

type NodeRegistry struct {
	repo  *store.NodeRepo
	audit *Audit
	log   *slog.Logger
}

func NewNodeRegistry(repo *store.NodeRepo, audit *Audit, log *slog.Logger) *NodeRegistry {
	if log == nil {
		log = slog.Default()
	}
	return &NodeRegistry{repo: repo, audit: audit, log: log}
}

func (r *NodeRegistry) Join(ctx context.Context, n *model.Node) error {
	if err := r.repo.Insert(ctx, n); err != nil {
		r.audit.Record(ctx, "cli", "node.join", n.ID, "failure", err, map[string]any{
			"name":    n.Name,
			"address": n.Address,
		})
		return fmt.Errorf("join node: %w", err)
	}
	r.audit.Record(ctx, "cli", "node.join", n.ID, "success", nil, map[string]any{
		"name":    n.Name,
		"address": n.Address,
	})
	r.log.Info("node joined",
		slog.String("node_id", n.ID),
		slog.String("name", n.Name),
		slog.String("address", n.Address))
	return nil
}

func (r *NodeRegistry) Leave(ctx context.Context, id string) error {
	if err := r.repo.UpdateState(ctx, id, model.NodeStateLeft); err != nil {
		r.audit.Record(ctx, "cli", "node.leave", id, "failure", err, nil)
		return fmt.Errorf("leave node: %w", err)
	}
	r.audit.Record(ctx, "cli", "node.leave", id, "success", nil, nil)
	r.log.Info("node left", slog.String("node_id", id))
	return nil
}

func (r *NodeRegistry) Forget(ctx context.Context, id string) error {
	if err := r.repo.Delete(ctx, id); err != nil {
		r.audit.Record(ctx, "cli", "node.forget", id, "failure", err, nil)
		return fmt.Errorf("forget node: %w", err)
	}
	r.audit.Record(ctx, "cli", "node.forget", id, "success", nil, nil)
	r.log.Info("node removed from registry", slog.String("node_id", id))
	return nil
}

func (r *NodeRegistry) List(ctx context.Context) ([]*model.Node, error) {
	return r.repo.List(ctx)
}

func (r *NodeRegistry) Get(ctx context.Context, id string) (*model.Node, error) {
	return r.repo.Get(ctx, id)
}

// SetNodeState updates a node's state to the given raw string (REQ-061).
// Used by the drain commands for the draining/drained states. This is
// the registry-level wrapper around store.NodeRepo.SetNodeState so the
// cli package does not need to reach into the repo directly.
func (r *NodeRegistry) SetNodeState(ctx context.Context, id, state string) error {
	if err := r.repo.SetNodeState(ctx, id, state); err != nil {
		r.audit.Record(ctx, "cli", "node.set_state", id, "failure", err, map[string]any{"state": state})
		return fmt.Errorf("set node state: %w", err)
	}
	r.log.Info("node state set", slog.String("node_id", id), slog.String("state", state))
	return nil
}
