package handlers

import (
	"errors"
	"net/http"

	"github.com/go-chi/chi/v5"
	"github.com/orchard9/rdev/internal/auth"
	"github.com/orchard9/rdev/internal/domain"
	"github.com/orchard9/rdev/internal/service"
	"github.com/orchard9/rdev/internal/validate"
	"github.com/orchard9/rdev/pkg/api"
)

// BlueprintsHandler handles blueprint endpoints.
type BlueprintsHandler struct {
	blueprintService *service.BlueprintService
}

// NewBlueprintsHandler creates a new blueprints handler.
func NewBlueprintsHandler(blueprintService *service.BlueprintService) *BlueprintsHandler {
	return &BlueprintsHandler{
		blueprintService: blueprintService,
	}
}

// Mount registers blueprint routes.
func (h *BlueprintsHandler) Mount(r api.Router) {
	r.Route("/projects/{id}/blueprints", func(r chi.Router) {
		// Read operations
		r.With(auth.RequireScope(auth.ScopeProjectsRead, auth.ScopeAdmin)).
			Get("/", h.ListBlueprints)
		r.With(auth.RequireScope(auth.ScopeProjectsRead, auth.ScopeAdmin)).
			Get("/{blueprintId}", h.GetBlueprint)

		// Write operations
		r.With(auth.RequireScope(auth.ScopeProjectsExecute, auth.ScopeAdmin)).
			Post("/", h.CreateBlueprint)
		r.With(auth.RequireScope(auth.ScopeProjectsExecute, auth.ScopeAdmin)).
			Put("/{blueprintId}", h.UpdateBlueprint)
		r.With(auth.RequireScope(auth.ScopeProjectsExecute, auth.ScopeAdmin)).
			Delete("/{blueprintId}", h.DeleteBlueprint)
	})
}

// CreateBlueprintRequest is the request body for POST /projects/{id}/blueprints.
type CreateBlueprintRequest struct {
	Name        string         `json:"name"`
	Description string         `json:"description,omitempty"`
	Spec        map[string]any `json:"spec"`
}

// CreateBlueprint creates a new blueprint.
// POST /projects/{id}/blueprints
func (h *BlueprintsHandler) CreateBlueprint(w http.ResponseWriter, r *http.Request) {
	projectID := chi.URLParam(r, "id")

	var req CreateBlueprintRequest
	if err := api.DecodeJSON(r, &req); err != nil {
		api.WriteBadRequest(w, r, "invalid request body")
		return
	}

	v := validate.New()
	v.Required(req.Name, "name")
	if err := v.Error(); err != nil {
		api.WriteBadRequest(w, r, err.Error())
		return
	}

	blueprint, err := h.blueprintService.CreateBlueprint(r.Context(), projectID, req.Name, req.Description, req.Spec)
	if err != nil {
		api.WriteInternalError(w, r, "failed to create blueprint")
		return
	}

	api.WriteCreated(w, r, toBlueprintDTO(blueprint))
}

// ListBlueprints returns all blueprints for a project.
// GET /projects/{id}/blueprints
func (h *BlueprintsHandler) ListBlueprints(w http.ResponseWriter, r *http.Request) {
	projectID := chi.URLParam(r, "id")

	blueprints, err := h.blueprintService.ListBlueprints(r.Context(), projectID)
	if err != nil {
		api.WriteInternalError(w, r, "failed to list blueprints")
		return
	}

	dtos := make([]*BlueprintDTO, len(blueprints))
	for i, b := range blueprints {
		dtos[i] = toBlueprintDTO(b)
	}

	api.WriteSuccess(w, r, map[string]any{
		"blueprints": dtos,
		"total":      len(dtos),
	})
}

// GetBlueprint retrieves a blueprint by ID.
// GET /projects/{id}/blueprints/{blueprintId}
func (h *BlueprintsHandler) GetBlueprint(w http.ResponseWriter, r *http.Request) {
	blueprintID := domain.BlueprintID(chi.URLParam(r, "blueprintId"))

	blueprint, err := h.blueprintService.GetBlueprint(r.Context(), blueprintID)
	if err != nil {
		if errors.Is(err, domain.ErrBlueprintNotFound) {
			api.WriteNotFound(w, r, "blueprint not found")
			return
		}
		api.WriteInternalError(w, r, "failed to get blueprint")
		return
	}

	api.WriteSuccess(w, r, toBlueprintDTO(blueprint))
}

// UpdateBlueprintRequest is the request body for PUT /projects/{id}/blueprints/{blueprintId}.
type UpdateBlueprintRequest struct {
	Name        string         `json:"name"`
	Description string         `json:"description,omitempty"`
	Spec        map[string]any `json:"spec"`
}

// UpdateBlueprint updates a blueprint.
// PUT /projects/{id}/blueprints/{blueprintId}
func (h *BlueprintsHandler) UpdateBlueprint(w http.ResponseWriter, r *http.Request) {
	blueprintID := domain.BlueprintID(chi.URLParam(r, "blueprintId"))

	var req UpdateBlueprintRequest
	if err := api.DecodeJSON(r, &req); err != nil {
		api.WriteBadRequest(w, r, "invalid request body")
		return
	}

	if err := h.blueprintService.UpdateBlueprint(r.Context(), blueprintID, req.Name, req.Description, req.Spec); err != nil {
		if errors.Is(err, domain.ErrBlueprintNotFound) {
			api.WriteNotFound(w, r, "blueprint not found")
			return
		}
		api.WriteInternalError(w, r, "failed to update blueprint")
		return
	}

	api.WriteSuccess(w, r, map[string]any{
		"message": "blueprint updated",
	})
}

// DeleteBlueprint deletes a blueprint.
// DELETE /projects/{id}/blueprints/{blueprintId}
func (h *BlueprintsHandler) DeleteBlueprint(w http.ResponseWriter, r *http.Request) {
	blueprintID := domain.BlueprintID(chi.URLParam(r, "blueprintId"))

	if err := h.blueprintService.DeleteBlueprint(r.Context(), blueprintID); err != nil {
		if errors.Is(err, domain.ErrBlueprintNotFound) {
			api.WriteNotFound(w, r, "blueprint not found")
			return
		}
		api.WriteInternalError(w, r, "failed to delete blueprint")
		return
	}

	api.WriteSuccess(w, r, map[string]any{
		"message": "blueprint deleted",
	})
}
