// Package handlers provides HTTP handlers for the rdev API.
package handlers

import (
	"context"
	"errors"
	"net/http"
	"strconv"

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

// ComponentsHandler handles component management endpoints.
type ComponentsHandler struct {
	service          port.ComponentService
	operationService *service.OperationService
}

// NewComponentsHandler creates a new components handler.
func NewComponentsHandler(service port.ComponentService) *ComponentsHandler {
	return &ComponentsHandler{service: service}
}

// SetOperationService sets the operation tracking service.
func (h *ComponentsHandler) SetOperationService(svc *service.OperationService) *ComponentsHandler {
	if svc != nil {
		h.operationService = svc
	}
	return h
}

// Mount registers the component routes.
func (h *ComponentsHandler) Mount(r api.Router) {
	r.Route("/projects/{id}/components", func(r chi.Router) {
		// Read operations
		r.With(auth.RequireScope(auth.ScopeProjectsRead, auth.ScopeAdmin)).Get("/", h.List)

		// Write operations
		r.With(auth.RequireScope(auth.ScopeProjectsExecute, auth.ScopeAdmin)).Post("/", h.Add)
		r.With(auth.RequireScope(auth.ScopeProjectsExecute, auth.ScopeAdmin)).Post("/batch", h.AddBatch)
		r.With(auth.RequireScope(auth.ScopeProjectsExecute, auth.ScopeAdmin)).Delete("/*", h.Remove)
	})
}

// AddComponentRequest is the request body for POST /projects/{id}/components.
type AddComponentRequest struct {
	Type     string `json:"type"`     // service, worker, app-astro, app-react, cli
	Name     string `json:"name"`     // component name (slug format)
	Template string `json:"template"` // optional: specific template variant
	Port     int    `json:"port"`     // optional: specific port (auto-assigned if 0)
}

// ComponentResponse is the response for component operations.
type ComponentResponse struct {
	Type         string   `json:"type"`
	Name         string   `json:"name"`
	Path         string   `json:"path"`
	Port         int      `json:"port"`
	Template     string   `json:"template"`
	Dependencies []string `json:"dependencies"`
}

// Add adds a new component to a project's monorepo.
// POST /projects/{id}/components
func (h *ComponentsHandler) Add(w http.ResponseWriter, r *http.Request) {
	projectID := chi.URLParam(r, "id")
	ctx, cancel := context.WithTimeout(r.Context(), TimeoutHeavyWrite)
	defer cancel()

	// Validate project ID
	if err := domain.ValidateProjectID(projectID); err != nil {
		api.WriteBadRequest(w, r, err.Error())
		return
	}

	if h.service == nil {
		api.WriteInternalError(w, r, "component service not configured")
		return
	}

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

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

	// Start operation tracking
	var operationID string
	if h.operationService != nil {
		operationID, _ = h.operationService.StartOperation(ctx, projectID,
			domain.OperationTypeComponentAdd,
			map[string]any{"type": req.Type, "name": req.Name, "template": req.Template, "port": req.Port},
			r.Header.Get("X-Request-ID"))
	}

	component, err := h.service.AddComponent(ctx, projectID, port.AddComponentRequest{
		Type:     req.Type,
		Name:     req.Name,
		Template: req.Template,
		Port:     req.Port,
	})
	if err != nil {
		if h.operationService != nil && operationID != "" {
			if opErr := h.operationService.FailOperation(ctx, operationID, err.Error(), ""); opErr != nil {
				log := logging.FromContext(ctx).WithHandler("Add")
				log.Error("failed to record operation failure", logging.FieldError, opErr.Error(), logging.FieldOperation, operationID)
			}
		}
		// Map domain errors to HTTP responses
		switch {
		case errors.Is(err, domain.ErrInvalidComponentType):
			api.WriteBadRequest(w, r, err.Error())
		case errors.Is(err, domain.ErrInvalidComponentName):
			api.WriteBadRequest(w, r, err.Error())
		case errors.Is(err, domain.ErrDuplicateComponent):
			api.WriteError(w, r, http.StatusConflict, "CONFLICT", err.Error())
		case errors.Is(err, domain.ErrProjectNotFound):
			api.WriteNotFound(w, r, err.Error())
		default:
			log := logging.FromContext(ctx).WithHandler("Add")
			log.Error("failed to add component", logging.FieldError, err.Error(), logging.FieldProjectID, projectID, "name", req.Name)
			api.WriteInternalError(w, r, "failed to add component")
		}
		return
	}

	if h.operationService != nil && operationID != "" {
		if opErr := h.operationService.CompleteOperation(ctx, operationID, map[string]any{
			"path": component.Path,
			"port": component.Port,
		}); opErr != nil {
			log := logging.FromContext(ctx).WithHandler("Add")
			log.Error("failed to record operation completion", logging.FieldError, opErr.Error(), logging.FieldOperation, operationID)
		}
	}

	deps := component.Dependencies
	if deps == nil {
		deps = []string{}
	}
	resp := map[string]any{
		"type":         string(component.Type),
		"name":         component.Name,
		"path":         component.Path,
		"port":         component.Port,
		"template":     component.Template,
		"dependencies": deps,
	}
	if operationID != "" {
		resp["operation_id"] = operationID
	}
	api.WriteCreated(w, r, resp)
}

// AddComponentBatchRequest is the request body for POST /projects/{id}/components/batch.
type AddComponentBatchRequest struct {
	Components []AddComponentRequest `json:"components"`
}

// AddBatch adds multiple components to a project's monorepo in a single atomic operation.
// POST /projects/{id}/components/batch
func (h *ComponentsHandler) AddBatch(w http.ResponseWriter, r *http.Request) {
	projectID := chi.URLParam(r, "id")
	ctx, cancel := context.WithTimeout(r.Context(), TimeoutLongRunning)
	defer cancel()

	// Validate project ID
	if err := domain.ValidateProjectID(projectID); err != nil {
		api.WriteBadRequest(w, r, err.Error())
		return
	}

	if h.service == nil {
		api.WriteInternalError(w, r, "component service not configured")
		return
	}

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

	// Validate we have at least one component
	if len(req.Components) == 0 {
		api.WriteBadRequest(w, r, "at least one component is required")
		return
	}

	// Validate each component's required fields
	for i, comp := range req.Components {
		v := validate.New()
		v.Required(comp.Type, "components["+strconv.Itoa(i)+"].type")
		v.Required(comp.Name, "components["+strconv.Itoa(i)+"].name")
		if err := v.Error(); err != nil {
			api.WriteBadRequest(w, r, err.Error())
			return
		}
	}

	// Convert to port requests
	portReqs := make([]port.AddComponentRequest, len(req.Components))
	for i, comp := range req.Components {
		portReqs[i] = port.AddComponentRequest{
			Type:     comp.Type,
			Name:     comp.Name,
			Template: comp.Template,
			Port:     comp.Port,
		}
	}

	// Start operation tracking
	var operationID string
	if h.operationService != nil {
		componentNames := make([]string, len(req.Components))
		for i, c := range req.Components {
			componentNames[i] = c.Type + "/" + c.Name
		}
		operationID, _ = h.operationService.StartOperation(ctx, projectID,
			domain.OperationTypeComponentAdd,
			map[string]any{"batch": true, "components": componentNames},
			r.Header.Get("X-Request-ID"))
	}

	components, err := h.service.AddComponentBatch(ctx, projectID, portReqs)
	if err != nil {
		if h.operationService != nil && operationID != "" {
			if opErr := h.operationService.FailOperation(ctx, operationID, err.Error(), ""); opErr != nil {
				log := logging.FromContext(ctx).WithHandler("AddBatch")
				log.Error("failed to record operation failure", logging.FieldError, opErr.Error(), logging.FieldOperation, operationID)
			}
		}
		// Map domain errors to HTTP responses
		switch {
		case errors.Is(err, domain.ErrInvalidComponentType):
			api.WriteBadRequest(w, r, err.Error())
		case errors.Is(err, domain.ErrInvalidComponentName):
			api.WriteBadRequest(w, r, err.Error())
		case errors.Is(err, domain.ErrDuplicateComponent):
			api.WriteError(w, r, http.StatusConflict, "CONFLICT", err.Error())
		case errors.Is(err, domain.ErrProjectNotFound):
			api.WriteNotFound(w, r, err.Error())
		default:
			log := logging.FromContext(ctx).WithHandler("AddBatch")
			log.Error("failed to add components", logging.FieldError, err.Error(), logging.FieldProjectID, projectID)
			api.WriteInternalError(w, r, "failed to add components")
		}
		return
	}

	if h.operationService != nil && operationID != "" {
		paths := make([]string, len(components))
		for i, c := range components {
			paths[i] = c.Path
		}
		if opErr := h.operationService.CompleteOperation(ctx, operationID, map[string]any{
			"paths": paths,
			"count": len(components),
		}); opErr != nil {
			log := logging.FromContext(ctx).WithHandler("AddBatch")
			log.Error("failed to record operation completion", logging.FieldError, opErr.Error(), logging.FieldOperation, operationID)
		}
	}

	// Convert to response format
	response := make([]ComponentResponse, len(components))
	for i, c := range components {
		deps := c.Dependencies
		if deps == nil {
			deps = []string{}
		}
		response[i] = ComponentResponse{
			Type:         string(c.Type),
			Name:         c.Name,
			Path:         c.Path,
			Port:         c.Port,
			Template:     c.Template,
			Dependencies: deps,
		}
	}

	resp := map[string]any{
		"components": response,
	}
	if operationID != "" {
		resp["operation_id"] = operationID
	}
	api.WriteCreated(w, r, resp)
}

// List lists all components in a project's monorepo.
// GET /projects/{id}/components
func (h *ComponentsHandler) List(w http.ResponseWriter, r *http.Request) {
	projectID := chi.URLParam(r, "id")
	ctx, cancel := context.WithTimeout(r.Context(), TimeoutStandard)
	defer cancel()

	// Validate project ID
	if err := domain.ValidateProjectID(projectID); err != nil {
		api.WriteBadRequest(w, r, err.Error())
		return
	}

	if h.service == nil {
		api.WriteInternalError(w, r, "component service not configured")
		return
	}

	components, err := h.service.ListComponents(ctx, projectID)
	if err != nil {
		if errors.Is(err, domain.ErrProjectNotFound) {
			api.WriteNotFound(w, r, err.Error())
			return
		}
		log := logging.FromContext(ctx).WithHandler("List")
		log.Error("failed to list components", logging.FieldError, err.Error(), logging.FieldProjectID, projectID)
		api.WriteInternalError(w, r, "failed to list components")
		return
	}

	// Convert to response format
	response := make([]ComponentResponse, len(components))
	for i, c := range components {
		response[i] = ComponentResponse{
			Type:         string(c.Type),
			Name:         c.Name,
			Path:         c.Path,
			Port:         c.Port,
			Template:     c.Template,
			Dependencies: c.Dependencies,
		}
		// Ensure dependencies is not nil for JSON
		if response[i].Dependencies == nil {
			response[i].Dependencies = []string{}
		}
	}

	api.WriteSuccess(w, r, map[string]any{"components": response})
}

// Remove removes a component from a project's monorepo.
// DELETE /projects/{id}/components/{path}
func (h *ComponentsHandler) Remove(w http.ResponseWriter, r *http.Request) {
	projectID := chi.URLParam(r, "id")

	// Get the component path from the wildcard - chi captures everything after /components/
	componentPath := chi.URLParam(r, "*")
	if componentPath == "" {
		api.WriteBadRequest(w, r, "component path is required")
		return
	}

	ctx, cancel := context.WithTimeout(r.Context(), TimeoutStandard)
	defer cancel()

	// Validate project ID
	if err := domain.ValidateProjectID(projectID); err != nil {
		api.WriteBadRequest(w, r, err.Error())
		return
	}

	if h.service == nil {
		api.WriteInternalError(w, r, "component service not configured")
		return
	}

	err := h.service.RemoveComponent(ctx, projectID, componentPath)
	if err != nil {
		if errors.Is(err, domain.ErrProjectNotFound) {
			api.WriteNotFound(w, r, err.Error())
			return
		}
		if errors.Is(err, domain.ErrComponentNotFound) {
			api.WriteNotFound(w, r, err.Error())
			return
		}
		log := logging.FromContext(ctx).WithHandler("Remove")
		log.Error("failed to remove component", logging.FieldError, err.Error(), logging.FieldProjectID, projectID, "path", componentPath)
		api.WriteInternalError(w, r, "failed to remove component")
		return
	}

	api.WriteNoContent(w)
}
