// handler.go exposes the change-request HTTP endpoints under middleware.Auth,
// mirroring workspace/handler.go. Routes:
//
//	POST /api/v1/workspaces/{wsID}/change-requests   create
//	GET  /api/v1/workspaces/{wsID}/change-requests   list
//	GET  /api/v1/change-requests/{id}                get
//	POST /api/v1/change-requests/{id}/review         review (human approve/reject)
//	POST /api/v1/change-requests/{id}/merge          gated merge
//	POST /api/v1/change-requests/{id}/sync           refresh status from host
package changerequest

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

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

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

// AdminLookup resolves is_admin for a user (narrow interface, same as
// workspace.AdminLookup so the userAdminAdapter can be reused).
type AdminLookup interface {
	IsAdmin(ctx context.Context, id uuid.UUID) (bool, error)
}

// Handler holds the change-request service + auth deps.
type Handler struct {
	svc      Service
	resolver middleware.SessionResolver
	users    AdminLookup
}

// NewHandler constructs the Handler.
func NewHandler(svc Service, resolver middleware.SessionResolver, users AdminLookup) *Handler {
	return &Handler{svc: svc, resolver: resolver, users: users}
}

// Mount registers the change-request routes on r under the Auth middleware.
func (h *Handler) Mount(r chi.Router) {
	auth := middleware.Auth(h.resolver, middleware.AuthOptions{})

	r.With(auth).Route("/api/v1/workspaces/{wsID}/change-requests", func(r chi.Router) {
		r.Post("/", h.create)
		r.Get("/", h.list)
	})
	r.With(auth).Route("/api/v1/change-requests/{id}", func(r chi.Router) {
		r.Get("/", h.get)
		r.Post("/review", h.review)
		r.Post("/merge", h.merge)
		r.Post("/sync", h.sync)
	})
}

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

func writeJSON(w http.ResponseWriter, status int, body any) { httpx.WriteJSON(w, status, body) }

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

func parseUUID(s string) (uuid.UUID, error) {
	id, err := uuid.Parse(s)
	if err != nil {
		return uuid.Nil, errs.Wrap(err, errs.CodeInvalidInput, "invalid uuid")
	}
	return id, nil
}

func (h *Handler) create(w http.ResponseWriter, r *http.Request) {
	c, err := h.caller(r)
	if err != nil {
		writeErr(w, r, err)
		return
	}
	wsID, err := parseUUID(chi.URLParam(r, "wsID"))
	if err != nil {
		writeErr(w, r, err)
		return
	}
	var req createReq
	if err := json.NewDecoder(r.Body).Decode(&req); err != nil {
		writeErr(w, r, errs.Wrap(err, errs.CodeInvalidInput, "decode body"))
		return
	}
	cr, err := h.svc.Create(r.Context(), c, wsID, CreateInput{
		SourceBranch: req.SourceBranch, TargetBranch: req.TargetBranch,
		Title: req.Title, Description: req.Description,
		RequirementID: req.RequirementID, IssueID: req.IssueID,
	})
	if err != nil {
		writeErr(w, r, err)
		return
	}
	writeJSON(w, http.StatusCreated, toResp(cr))
}

func (h *Handler) list(w http.ResponseWriter, r *http.Request) {
	c, err := h.caller(r)
	if err != nil {
		writeErr(w, r, err)
		return
	}
	wsID, err := parseUUID(chi.URLParam(r, "wsID"))
	if err != nil {
		writeErr(w, r, err)
		return
	}
	out, err := h.svc.ListByWorkspace(r.Context(), c, wsID)
	if err != nil {
		writeErr(w, r, err)
		return
	}
	resp := make([]changeRequestResp, 0, len(out))
	for _, cr := range out {
		resp = append(resp, toResp(cr))
	}
	writeJSON(w, http.StatusOK, resp)
}

func (h *Handler) get(w http.ResponseWriter, r *http.Request) {
	c, err := h.caller(r)
	if err != nil {
		writeErr(w, r, err)
		return
	}
	id, err := parseUUID(chi.URLParam(r, "id"))
	if err != nil {
		writeErr(w, r, err)
		return
	}
	cr, err := h.svc.Get(r.Context(), c, id)
	if err != nil {
		writeErr(w, r, err)
		return
	}
	writeJSON(w, http.StatusOK, toResp(cr))
}

func (h *Handler) review(w http.ResponseWriter, r *http.Request) {
	c, err := h.caller(r)
	if err != nil {
		writeErr(w, r, err)
		return
	}
	id, err := parseUUID(chi.URLParam(r, "id"))
	if err != nil {
		writeErr(w, r, err)
		return
	}
	var req reviewReq
	if err := json.NewDecoder(r.Body).Decode(&req); err != nil {
		writeErr(w, r, errs.Wrap(err, errs.CodeInvalidInput, "decode body"))
		return
	}
	cr, err := h.svc.Review(r.Context(), c, id, Verdict(req.Verdict), req.Note)
	if err != nil {
		writeErr(w, r, err)
		return
	}
	writeJSON(w, http.StatusOK, toResp(cr))
}

func (h *Handler) merge(w http.ResponseWriter, r *http.Request) {
	c, err := h.caller(r)
	if err != nil {
		writeErr(w, r, err)
		return
	}
	id, err := parseUUID(chi.URLParam(r, "id"))
	if err != nil {
		writeErr(w, r, err)
		return
	}
	var req mergeReq
	// merge body is optional (method default)
	_ = json.NewDecoder(r.Body).Decode(&req)
	cr, err := h.svc.Merge(r.Context(), c, id, req.Method)
	if err != nil {
		writeErr(w, r, err)
		return
	}
	writeJSON(w, http.StatusOK, toResp(cr))
}

func (h *Handler) sync(w http.ResponseWriter, r *http.Request) {
	c, err := h.caller(r)
	if err != nil {
		writeErr(w, r, err)
		return
	}
	id, err := parseUUID(chi.URLParam(r, "id"))
	if err != nil {
		writeErr(w, r, err)
		return
	}
	cr, err := h.svc.SyncStatus(r.Context(), c, id)
	if err != nil {
		writeErr(w, r, err)
		return
	}
	writeJSON(w, http.StatusOK, toResp(cr))
}
