package service

import (
	"context"
	"fmt"
	"sort"
	"strings"
	"time"

	iop "iop/proto/gen/iop"
)

var providerCommandAllowlist = map[string]struct{}{
	"capabilities":     {},
	"transport_status": {},
	"ollama_api":       {},
}

func edgeCommandResponseFromNodeCommand(req *iop.EdgeCommandRequest, view NodeCommandView, err error) *iop.EdgeCommandResponse {
	if err != nil {
		return &iop.EdgeCommandResponse{
			Status: "error",
			Error:  err.Error(),
		}
	}
	keys := make([]string, 0, len(view.Result))
	for k := range view.Result {
		keys = append(keys, k)
	}
	sort.Strings(keys)
	summaries := make([]string, 0, len(keys))
	for _, k := range keys {
		summaries = append(summaries, fmt.Sprintf("%s=%s", k, view.Result[k]))
	}
	summaryStr := fmt.Sprintf("NodeCommand %s completed: %s", req.GetParameters()["command"], strings.Join(summaries, ", "))
	return &iop.EdgeCommandResponse{
		Status:  "completed",
		Summary: summaryStr,
	}
}

func unsupportedEdgeCommand(req *iop.EdgeCommandRequest, msg string) *iop.EdgeCommandResponse {
	return &iop.EdgeCommandResponse{
		Status: "unsupported",
		Error:  msg,
	}
}

func providerCommandEvent(req *iop.EdgeCommandRequest, phase, summary string) *iop.EdgeCommandEvent {
	return &iop.EdgeCommandEvent{
		CommandId:  req.CommandId,
		Phase:      phase,
		Summary:    summary,
		OccurredAt: time.Now().UnixNano(),
	}
}

// finishProviderCommand is the sole post-start terminal transition for a
// provider command. Keeping it central makes every started command observable
// as exactly one completed or failed event.
func finishProviderCommand(req *iop.EdgeCommandRequest, cmdName string, response *iop.EdgeCommandResponse, onEvent func(*iop.EdgeCommandEvent)) *iop.EdgeCommandResponse {
	phase := "completed"
	summary := fmt.Sprintf("Command %s completed successfully", cmdName)
	if response.Status != "completed" {
		phase = "failed"
		summary = fmt.Sprintf("Command %s failed: %s", cmdName, response.Error)
	}
	onEvent(providerCommandEvent(req, phase, summary))
	return response
}

func (s *Service) ExecuteCommand(ctx context.Context, req *iop.EdgeCommandRequest, onEvent func(*iop.EdgeCommandEvent)) (*iop.EdgeCommandResponse, error) {
	switch req.Operation {
	case "health.check":
		onEvent(&iop.EdgeCommandEvent{
			CommandId:  req.CommandId,
			Phase:      "started",
			Summary:    "Starting health check",
			OccurredAt: time.Now().UnixNano(),
		})
		status := s.HealthStatus()
		summary := fmt.Sprintf("Edge health check OK: %d node(s), %d capability(ies)", len(status.Nodes), len(status.Capabilities))
		onEvent(&iop.EdgeCommandEvent{
			CommandId:  req.CommandId,
			Phase:      "completed",
			Summary:    summary,
			OccurredAt: time.Now().UnixNano(),
		})
		return &iop.EdgeCommandResponse{
			Status:  "completed",
			Summary: summary,
		}, nil

	case "node.status":
		if req.TargetSelector == "" {
			return nil, fmt.Errorf("target_selector is required for node.status")
		}

		onEvent(&iop.EdgeCommandEvent{
			CommandId:  req.CommandId,
			Phase:      "started",
			Summary:    fmt.Sprintf("Resolving node status for %s", req.TargetSelector),
			OccurredAt: time.Now().UnixNano(),
		})

		entry, err := s.ResolveNode(req.TargetSelector)
		if err != nil {
			return &iop.EdgeCommandResponse{
				Status: "error",
				Error:  err.Error(),
			}, nil
		}

		snap := NodeEntrySnapshot(entry)
		onEvent(&iop.EdgeCommandEvent{
			CommandId:  req.CommandId,
			Phase:      "completed",
			Summary:    fmt.Sprintf("Resolved node status for %s", req.TargetSelector),
			OccurredAt: time.Now().UnixNano(),
		})

		return &iop.EdgeCommandResponse{
			Status:  "completed",
			Summary: fmt.Sprintf("node ID=%s Alias=%s State=%s", snap.NodeID, snap.Alias, snap.LifecycleState),
		}, nil

	case "provider.command":
		if strings.TrimSpace(req.GetTargetSelector()) == "" {
			return nil, fmt.Errorf("target_selector is required for provider.command")
		}
		cmdName := req.GetParameters()["command"]
		if _, ok := providerCommandAllowlist[cmdName]; !ok {
			return unsupportedEdgeCommand(req, fmt.Sprintf("unsupported provider.command command %q", cmdName)), nil
		}

		onEvent(providerCommandEvent(req, "started", fmt.Sprintf("Routing command %s to node %s", cmdName, req.TargetSelector)))

		entry, err := s.ResolveDispatchReady(req.TargetSelector)
		if err != nil {
			return finishProviderCommand(req, cmdName, &iop.EdgeCommandResponse{
				Status: "error",
				Error:  err.Error(),
			}, onEvent), nil
		}
		if entry.Client == nil {
			return finishProviderCommand(req, cmdName, &iop.EdgeCommandResponse{
				Status: "error",
				Error:  "node client not connected",
			}, onEvent), nil
		}

		switch cmdName {
		case "capabilities":
			view, err := s.Capabilities(ctx, NodeCommandRequestSpec{
				NodeRef:    req.TargetSelector,
				Adapter:    req.GetParameters()["adapter"],
				Target:     req.GetParameters()["target"],
				SessionID:  req.GetParameters()["session_id"],
				TimeoutSec: 10,
			})

			return finishProviderCommand(req, cmdName, edgeCommandResponseFromNodeCommand(req, view, err), onEvent), nil

		case "transport_status":
			view, err := s.TransportStatus(ctx, NodeCommandRequestSpec{
				NodeRef: req.TargetSelector, Adapter: req.GetParameters()["adapter"],
				Target: req.GetParameters()["target"], SessionID: req.GetParameters()["session_id"], TimeoutSec: 10,
			})
			return finishProviderCommand(req, cmdName, edgeCommandResponseFromNodeCommand(req, view, err), onEvent), nil

		case "ollama_api":
			view, err := s.OllamaAPI(ctx, OllamaAPIRequest{
				NodeRef: req.TargetSelector, Adapter: req.GetParameters()["adapter"], Target: req.GetParameters()["target"],
				Method: req.GetParameters()["method"], Path: req.GetParameters()["path"], Body: req.GetParameters()["body"], TimeoutSec: 10,
			})
			if err != nil {
				return finishProviderCommand(req, cmdName, &iop.EdgeCommandResponse{Status: "error", Error: err.Error()}, onEvent), nil
			}
			return finishProviderCommand(req, cmdName, &iop.EdgeCommandResponse{Status: "completed", Summary: fmt.Sprintf("provider response status=%d", view.StatusCode)}, onEvent), nil
		default:
			// The allowlist was checked before started; retain a defensive return
			// should the switch and allowlist ever diverge.
			return finishProviderCommand(req, cmdName, unsupportedEdgeCommand(req, fmt.Sprintf("unsupported provider.command command %q", cmdName)), onEvent), nil
		}

	default:
		return &iop.EdgeCommandResponse{
			Status: "unsupported",
			Error:  fmt.Sprintf("unsupported operation %s", req.Operation),
		}, nil
	}
}
