package worker

import (
	"bytes"
	"context"
	"encoding/json"
	"fmt"
	"io"
	"net/http"
	"strings"
	"time"
)

// HTTPInvoker dispatches agent runs to an external IOP HTTP service.
// The endpoint must be the full base URL; the invoker POSTs to /invoke.
// Raw upstream errors are never surfaced to callers; only stable sanitized
// messages are returned so secrets and internal details cannot leak through
// operation events.
type HTTPInvoker struct {
	endpoint string
	client   *http.Client
}

func NewHTTPInvoker(endpoint string) *HTTPInvoker {
	return &HTTPInvoker{
		endpoint: strings.TrimRight(strings.TrimSpace(endpoint), "/"),
		client:   &http.Client{Timeout: 5 * time.Minute},
	}
}

type iopInvokeRequest struct {
	OperationID      string         `json:"operation_id"`
	RepoID           string         `json:"repo_id"`
	Branch           string         `json:"branch"`
	WorkspacePath    string         `json:"workspace_path"`
	Instruction      string         `json:"instruction"`
	PolicyContext    map[string]any `json:"policy_context,omitempty"`
	ExpectedRevision string         `json:"expected_revision,omitempty"`
}

type iopInvokeResponse struct {
	State   string `json:"state"`
	Message string `json:"message,omitempty"`
}

func (h *HTTPInvoker) InvokeAgentRun(ctx context.Context, input AgentRunInput) (AgentRunResult, error) {
	req := iopInvokeRequest{
		OperationID:      input.OperationID,
		RepoID:           input.RepoID,
		Branch:           input.Branch,
		WorkspacePath:    input.WorkspacePath,
		Instruction:      input.Instruction,
		PolicyContext:    input.PolicyContext,
		ExpectedRevision: input.ExpectedRevision,
	}

	body, err := json.Marshal(req)
	if err != nil {
		return AgentRunResult{}, fmt.Errorf("marshal iop invoke request: %w", err)
	}

	httpReq, err := http.NewRequestWithContext(ctx, http.MethodPost, h.endpoint+"/invoke", bytes.NewReader(body))
	if err != nil {
		return AgentRunResult{}, fmt.Errorf("build iop invoke request: %w", err)
	}
	httpReq.Header.Set("Content-Type", "application/json")

	resp, err := h.client.Do(httpReq)
	if err != nil {
		return AgentRunResult{
			State:   AgentRunStateFailed,
			Message: "agent run invocation transport error",
		}, nil
	}
	defer resp.Body.Close()

	raw, err := io.ReadAll(io.LimitReader(resp.Body, 1<<20))
	if err != nil {
		return AgentRunResult{
			State:   AgentRunStateFailed,
			Message: "agent run invocation read error",
		}, nil
	}

	if resp.StatusCode < 200 || resp.StatusCode >= 300 {
		return AgentRunResult{
			State:   AgentRunStateFailed,
			Message: fmt.Sprintf("agent run invocation failed with status %d", resp.StatusCode),
		}, nil
	}

	var invokeResp iopInvokeResponse
	if err := json.Unmarshal(raw, &invokeResp); err != nil {
		return AgentRunResult{
			State:   AgentRunStateFailed,
			Message: "agent run invocation returned invalid response",
		}, nil
	}

	state, err := parseAgentRunState(invokeResp.State)
	if err != nil {
		return AgentRunResult{
			State:   AgentRunStateFailed,
			Message: "agent run invocation returned unsupported state",
		}, nil
	}

	return AgentRunResult{
		State:   state,
		Message: invokeResp.Message,
	}, nil
}

func parseAgentRunState(s string) (AgentRunState, error) {
	switch AgentRunState(s) {
	case AgentRunStateSucceeded, AgentRunStateFailed, AgentRunStateCancelled:
		return AgentRunState(s), nil
	default:
		return "", fmt.Errorf("unsupported agent run state %q", s)
	}
}
