// handler.go 暴露编排器模块的 HTTP 端点：/api/v1/orchestrator/runs（POST 启动、
// GET 列表）、/runs/{id}（GET）、/runs/{id}/pause|resume|cancel（POST）。
package orchestrator

import (
	"context"
	"encoding/json"
	"net/http"
	"time"

	"github.com/go-chi/chi/v5"
	"github.com/google/uuid"

	"github.com/yan1h/agent-coding-workflow/internal/infra/errs"
	"github.com/yan1h/agent-coding-workflow/internal/project"
	httpx "github.com/yan1h/agent-coding-workflow/internal/transport/http"
	"github.com/yan1h/agent-coding-workflow/internal/transport/http/middleware"
)

// AdminLookup 解析某用户是否为 admin（与 acp/chat/workspace 一致）。
type AdminLookup interface {
	IsAdmin(ctx context.Context, userID uuid.UUID) (bool, error)
}

// Handler 暴露编排器 HTTP 端点。
type Handler struct {
	svc         Service
	resolver    middleware.SessionResolver
	adminLookup AdminLookup
}

// NewHandler 构造 Handler。
func NewHandler(svc Service, resolver middleware.SessionResolver, al AdminLookup) *Handler {
	return &Handler{svc: svc, resolver: resolver, adminLookup: al}
}

// Mount 注册 /api/v1/orchestrator/* 路由。
func (h *Handler) Mount(r chi.Router) {
	r.Route("/api/v1/orchestrator/runs", func(r chi.Router) {
		r.Use(middleware.Auth(h.resolver, middleware.AuthOptions{}))
		r.Post("/", h.startRun)
		r.Get("/", h.listRuns)
		r.Get("/{id}", h.getRun)
		r.Post("/{id}/pause", h.pauseRun)
		r.Post("/{id}/resume", h.resumeRun)
		r.Post("/{id}/cancel", h.cancelRun)
	})
}

func (h *Handler) caller(r *http.Request) (Caller, error) {
	uid, ok := middleware.UserIDFromContext(r.Context())
	if !ok {
		return Caller{}, errs.New(errs.CodeUnauthorized, "not authenticated")
	}
	admin, err := h.adminLookup.IsAdmin(r.Context(), uid)
	if err != nil {
		return Caller{}, err
	}
	return Caller{UserID: uid, IsAdmin: admin}, nil
}

// ===== DTO =====

type startRunReq struct {
	ProjectSlug         string            `json:"project_slug"`
	RequirementNumber   int               `json:"requirement_number"`
	StartPhase          string            `json:"start_phase,omitempty"`
	PhaseAgentKinds     map[string]string `json:"phase_agent_kinds,omitempty"`
	PromptOverrides     map[string]string `json:"prompt_overrides,omitempty"`
	MaxAttemptsPerPhase int               `json:"max_attempts_per_phase,omitempty"`
	MaxDepth            int               `json:"max_depth,omitempty"`
	FanOutLimit         int               `json:"fan_out_limit,omitempty"`
}

type runDTO struct {
	ID            string     `json:"id"`
	ProjectID     string     `json:"project_id"`
	RequirementID string     `json:"requirement_id"`
	WorkspaceID   string     `json:"workspace_id"`
	OwnerID       string     `json:"owner_id"`
	Status        string     `json:"status"`
	CurrentPhase  string     `json:"current_phase"`
	LastError     string     `json:"last_error,omitempty"`
	CreatedAt     time.Time  `json:"created_at"`
	UpdatedAt     time.Time  `json:"updated_at"`
	EndedAt       *time.Time `json:"ended_at,omitempty"`
}

func runToDTO(r *Run) runDTO {
	d := runDTO{
		ID:            r.ID.String(),
		ProjectID:     r.ProjectID.String(),
		RequirementID: r.RequirementID.String(),
		WorkspaceID:   r.WorkspaceID.String(),
		OwnerID:       r.OwnerID.String(),
		Status:        string(r.Status),
		CurrentPhase:  string(r.CurrentPhase),
		CreatedAt:     r.CreatedAt,
		UpdatedAt:     r.UpdatedAt,
		EndedAt:       r.EndedAt,
	}
	if r.LastError != nil {
		d.LastError = *r.LastError
	}
	return d
}

// ===== handlers =====

func (h *Handler) startRun(w http.ResponseWriter, r *http.Request) {
	c, err := h.caller(r)
	if err != nil {
		h.writeErr(w, r, err)
		return
	}
	var body startRunReq
	if derr := json.NewDecoder(r.Body).Decode(&body); derr != nil {
		h.writeErr(w, r, errs.New(errs.CodeInvalidInput, "bad json"))
		return
	}
	if body.ProjectSlug == "" || body.RequirementNumber <= 0 {
		h.writeErr(w, r, errs.New(errs.CodeInvalidInput, "project_slug and requirement_number are required"))
		return
	}

	cfg := RunConfig{
		MaxAttemptsPerPhase: body.MaxAttemptsPerPhase,
		MaxDepth:            body.MaxDepth,
		FanOutLimit:         body.FanOutLimit,
	}
	if len(body.PhaseAgentKinds) > 0 {
		cfg.PhaseAgentKinds = map[project.Phase]uuid.UUID{}
		for ph, idStr := range body.PhaseAgentKinds {
			id, perr := uuid.Parse(idStr)
			if perr != nil {
				h.writeErr(w, r, errs.Wrap(perr, errs.CodeInvalidInput, "phase_agent_kinds id parse"))
				return
			}
			cfg.PhaseAgentKinds[project.Phase(ph)] = id
		}
	}
	if len(body.PromptOverrides) > 0 {
		cfg.PromptOverrides = map[project.Phase]string{}
		for ph, p := range body.PromptOverrides {
			cfg.PromptOverrides[project.Phase(ph)] = p
		}
	}

	run, err := h.svc.StartRun(r.Context(), c, StartRunInput{
		ProjectSlug:       body.ProjectSlug,
		RequirementNumber: body.RequirementNumber,
		StartPhase:        project.Phase(body.StartPhase),
		Config:            cfg,
	})
	if err != nil {
		h.writeErr(w, r, err)
		return
	}
	httpx.WriteJSON(w, http.StatusCreated, runToDTO(run))
}

func (h *Handler) getRun(w http.ResponseWriter, r *http.Request) {
	c, err := h.caller(r)
	if err != nil {
		h.writeErr(w, r, err)
		return
	}
	id, perr := uuid.Parse(chi.URLParam(r, "id"))
	if perr != nil {
		h.writeErr(w, r, errs.New(errs.CodeInvalidInput, "bad id"))
		return
	}
	run, err := h.svc.GetRun(r.Context(), c, id)
	if err != nil {
		h.writeErr(w, r, err)
		return
	}
	httpx.WriteJSON(w, http.StatusOK, runToDTO(run))
}

func (h *Handler) listRuns(w http.ResponseWriter, r *http.Request) {
	c, err := h.caller(r)
	if err != nil {
		h.writeErr(w, r, err)
		return
	}
	f := RunFilter{Status: RunStatus(r.URL.Query().Get("status"))}
	if rid := r.URL.Query().Get("requirement_id"); rid != "" {
		if id, perr := uuid.Parse(rid); perr == nil {
			f.RequirementID = &id
		}
	}
	if pid := r.URL.Query().Get("project_id"); pid != "" {
		if id, perr := uuid.Parse(pid); perr == nil {
			f.ProjectID = &id
		}
	}
	runs, err := h.svc.ListRuns(r.Context(), c, f)
	if err != nil {
		h.writeErr(w, r, err)
		return
	}
	out := make([]runDTO, 0, len(runs))
	for _, run := range runs {
		out = append(out, runToDTO(run))
	}
	httpx.WriteJSON(w, http.StatusOK, out)
}

func (h *Handler) pauseRun(w http.ResponseWriter, r *http.Request)  { h.runAction(w, r, h.svc.Pause) }
func (h *Handler) resumeRun(w http.ResponseWriter, r *http.Request) { h.runAction(w, r, h.svc.Resume) }
func (h *Handler) cancelRun(w http.ResponseWriter, r *http.Request) { h.runAction(w, r, h.svc.Cancel) }

func (h *Handler) runAction(w http.ResponseWriter, r *http.Request, fn func(context.Context, Caller, uuid.UUID) error) {
	c, err := h.caller(r)
	if err != nil {
		h.writeErr(w, r, err)
		return
	}
	id, perr := uuid.Parse(chi.URLParam(r, "id"))
	if perr != nil {
		h.writeErr(w, r, errs.New(errs.CodeInvalidInput, "bad id"))
		return
	}
	if aerr := fn(r.Context(), c, id); aerr != nil {
		h.writeErr(w, r, aerr)
		return
	}
	run, gerr := h.svc.GetRun(r.Context(), c, id)
	if gerr != nil {
		w.WriteHeader(http.StatusNoContent)
		return
	}
	httpx.WriteJSON(w, http.StatusOK, runToDTO(run))
}

func (h *Handler) writeErr(w http.ResponseWriter, r *http.Request, err error) {
	httpx.WriteError(w, middleware.RequestIDFromContext(r.Context()), err)
}
