package httpapi

import (
	"encoding/json"
	"errors"
	"log/slog"
	"net/http"

	"github.com/dtoro/oikos/internal/core/domain"
	"github.com/dtoro/oikos/internal/httpapi/gen"
)

// errNotImplemented marks endpoints stubbed for later phases.
var errNotImplemented = errors.New("not implemented yet")

// statusFor maps domain sentinel errors to HTTP status codes (plan SG11).
func statusFor(err error) (status int, title string) {
	switch {
	case errors.Is(err, domain.ErrNotFound):
		return http.StatusNotFound, "not found"
	case errors.Is(err, domain.ErrInvalidTransition):
		return http.StatusConflict, "invalid lifecycle transition"
	case errors.Is(err, domain.ErrConflict), errors.Is(err, domain.ErrAlreadyExists):
		return http.StatusConflict, "conflict"
	case errors.Is(err, domain.ErrCardinality):
		return http.StatusConflict, "relationship cardinality violation"
	case errors.Is(err, domain.ErrAbstractType), errors.Is(err, domain.ErrInvalidEdge):
		return http.StatusUnprocessableEntity, "ontology validation failed"
	case errors.Is(err, domain.ErrInvalidInput):
		return http.StatusBadRequest, "invalid input"
	case errors.Is(err, domain.ErrApprovalRequired):
		return http.StatusForbidden, "operator approval required"
	case errors.Is(err, domain.ErrAutonomyBlocked):
		return http.StatusForbidden, "autonomy policy blocks this action"
	case errors.Is(err, domain.ErrCircuitOpen):
		return http.StatusServiceUnavailable, "circuit breaker open"
	case errors.Is(err, errNotImplemented):
		return http.StatusNotImplemented, "not implemented"
	default:
		return http.StatusInternalServerError, "internal error"
	}
}

// writeProblem writes an RFC 9457 problem+json response.
func writeProblem(w http.ResponseWriter, r *http.Request, status int, title, detail string) {
	instance := r.URL.Path
	p := gen.Problem{
		Status:   status,
		Title:    title,
		Instance: &instance,
	}
	if detail != "" {
		p.Detail = &detail
	}
	w.Header().Set("Content-Type", "application/problem+json")
	w.WriteHeader(status)
	json.NewEncoder(w).Encode(p)
}

// writeProblemFromErr maps an error to a problem+json response. Internal
// error details are logged server-side, never leaked to clients.
func writeProblemFromErr(w http.ResponseWriter, r *http.Request, err error) {
	status, title := statusFor(err)
	detail := ""
	if status != http.StatusInternalServerError {
		detail = err.Error()
	} else {
		slog.Error("internal error", "method", r.Method, "path", r.URL.Path, "error", err)
	}
	writeProblem(w, r, status, title, detail)
}
