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

import (
	"context"
	"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/logging"
	"github.com/orchard9/rdev/internal/service"
	"github.com/orchard9/rdev/pkg/api"
)

// ProjectManagementHandler handles project lifecycle operations.
type ProjectManagementHandler struct {
	infraService     *service.ProjectInfraService
	operationService *service.OperationService
}

// NewProjectManagementHandler creates a new project management handler.
func NewProjectManagementHandler(infraService *service.ProjectInfraService) *ProjectManagementHandler {
	return &ProjectManagementHandler{
		infraService: infraService,
	}
}

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

// Mount registers the project management routes.
func (h *ProjectManagementHandler) Mount(r api.Router) {
	r.Route("/project", func(r chi.Router) {
		// Write operations
		r.With(auth.RequireScope(auth.ScopeProjectsExecute, auth.ScopeAdmin)).
			Post("/", h.Create)
		r.With(auth.RequireScope(auth.ScopeProjectsExecute, auth.ScopeAdmin)).
			Delete("/{name}", h.Delete)

		// Read operations
		r.With(auth.RequireScope(auth.ScopeProjectsRead, auth.ScopeAdmin)).
			Get("/", h.List)
		r.With(auth.RequireScope(auth.ScopeProjectsRead, auth.ScopeAdmin)).
			Get("/{name}", h.Status)
	})

	// Bulk operations - cleanup test projects (admin only)
	// Note: Uses direct route to avoid conflict with /projects in projects.go
	r.With(auth.RequireScope(auth.ScopeAdmin)).
		Delete("/projects/cleanup", h.CleanupTestProjects)

	// Template endpoints (read-only)
	r.With(auth.RequireScope(auth.ScopeProjectsRead, auth.ScopeAdmin)).
		Get("/templates", h.ListTemplates)
	r.With(auth.RequireScope(auth.ScopeProjectsRead, auth.ScopeAdmin)).
		Get("/templates/components", h.ListComponentTemplates)
	r.With(auth.RequireScope(auth.ScopeProjectsRead, auth.ScopeAdmin)).
		Get("/templates/{name}", h.GetTemplate)
}

// CreateRequest is the request body for POST /project.
type CreateRequest struct {
	Name        string `json:"name"`
	Description string `json:"description,omitempty"`
	Private     bool   `json:"private,omitempty"`
	Template    string `json:"template,omitempty"` // Template to seed repo (default: "default")
}

// Create creates a new project with git repo and DNS.
// POST /project
func (h *ProjectManagementHandler) Create(w http.ResponseWriter, r *http.Request) {
	ctx, cancel := context.WithTimeout(r.Context(), TimeoutOrchestration)
	defer cancel()

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

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

	if req.Name == "" {
		api.WriteBadRequest(w, r, "name is required")
		return
	}

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

	result, err := h.infraService.CreateProject(ctx, service.CreateProjectRequest{
		Name:        req.Name,
		Description: req.Description,
		Private:     req.Private,
		Template:    req.Template,
	})
	if err != nil {
		log := logging.FromContext(ctx).WithHandler("Create")
		if h.operationService != nil && operationID != "" {
			if opErr := h.operationService.FailOperation(ctx, operationID, err.Error(), ""); opErr != nil {
				log.Error("failed to record operation failure", logging.FieldError, opErr.Error(), logging.FieldOperation, operationID)
			}
		}
		// Check for validation errors (user input) vs internal errors
		if errors.Is(err, domain.ErrInvalidProjectName) {
			api.WriteBadRequest(w, r, err.Error())
			return
		}
		// Log internal errors but return generic message to client
		log.Error("project creation failed", logging.FieldError, err.Error(), logging.FieldProjectName, req.Name)
		api.WriteInternalError(w, r, "failed to create project")
		return
	}

	if h.operationService != nil && operationID != "" {
		if opErr := h.operationService.CompleteOperation(ctx, operationID, map[string]any{
			"project_id": result.ProjectID,
			"git_url":    result.CloneHTTP,
		}); opErr != nil {
			log := logging.FromContext(ctx).WithHandler("Create")
			log.Error("failed to record operation completion", logging.FieldError, opErr.Error(), logging.FieldOperation, operationID)
		}
	}

	resp := map[string]any{
		"project_id":  result.ProjectID,
		"name":        result.Name,
		"description": result.Description,
		"git": map[string]string{
			"owner":      result.GitRepoOwner,
			"name":       result.GitRepoName,
			"clone_ssh":  result.CloneSSH,
			"clone_http": result.CloneHTTP,
			"html_url":   result.HTMLURL,
		},
		"domain":     result.Domain,
		"url":        result.URL,
		"next_steps": result.NextSteps,
	}
	if operationID != "" {
		resp["operation_id"] = operationID
	}
	api.WriteCreated(w, r, resp)
}

// List returns all projects.
// GET /project
func (h *ProjectManagementHandler) List(w http.ResponseWriter, r *http.Request) {
	ctx, cancel := context.WithTimeout(r.Context(), TimeoutLookup)
	defer cancel()

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

	projects, err := h.infraService.ListProjects(ctx)
	if err != nil {
		log := logging.FromContext(ctx).WithHandler("List")
		log.Error("failed to list projects", logging.FieldError, err.Error())
		api.WriteInternalError(w, r, "failed to list projects")
		return
	}

	// Convert to response format
	response := make([]map[string]any, len(projects))
	for i, p := range projects {
		response[i] = map[string]any{
			"project_id":  p.ProjectID,
			"name":        p.Name,
			"description": p.Description,
			"git": map[string]string{
				"clone_ssh":  p.CloneSSH,
				"clone_http": p.CloneHTTP,
				"html_url":   p.HTMLURL,
			},
			"domain": p.Domain,
			"url":    p.URL,
			"deployment": map[string]any{
				"status":         p.DeploymentStatus,
				"image":          p.DeploymentImage,
				"replicas":       p.DeploymentReplicas,
				"ready_replicas": p.ReadyReplicas,
			},
		}
	}

	api.WriteSuccess(w, r, response)
}

// Status returns the status of a specific project.
// GET /project/{name}
func (h *ProjectManagementHandler) Status(w http.ResponseWriter, r *http.Request) {
	name := chi.URLParam(r, "name")
	ctx, cancel := context.WithTimeout(r.Context(), TimeoutLookup)
	defer cancel()

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

	status, err := h.infraService.GetStatus(ctx, name)
	if err != nil {
		// Check if it's a "not found" error
		if errors.Is(err, domain.ErrProjectNotFound) {
			api.WriteNotFound(w, r, "project not found")
			return
		}
		log := logging.FromContext(ctx).WithHandler("Status")
		log.Error("failed to get project status", logging.FieldError, err.Error(), logging.FieldProjectName, name)
		api.WriteInternalError(w, r, "failed to get project status")
		return
	}

	api.WriteSuccess(w, r, map[string]any{
		"project_id":  status.ProjectID,
		"name":        status.Name,
		"description": status.Description,
		"git": map[string]string{
			"owner":      status.GitRepoOwner,
			"name":       status.GitRepoName,
			"clone_ssh":  status.CloneSSH,
			"clone_http": status.CloneHTTP,
			"html_url":   status.HTMLURL,
		},
		"domain":        status.Domain,
		"custom_domain": status.CustomDomain,
		"url":           status.URL,
		"deployment": map[string]any{
			"status":         status.DeploymentStatus,
			"image":          status.DeploymentImage,
			"replicas":       status.DeploymentReplicas,
			"ready_replicas": status.ReadyReplicas,
		},
	})
}

// Delete removes a project and its associated resources.
// DELETE /project/{name}
func (h *ProjectManagementHandler) Delete(w http.ResponseWriter, r *http.Request) {
	name := chi.URLParam(r, "name")
	ctx, cancel := context.WithTimeout(r.Context(), TimeoutHeavyWrite)
	defer cancel()

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

	err := h.infraService.DeleteProject(ctx, name)
	if err != nil {
		// Check if it's a "not found" error
		if errors.Is(err, domain.ErrProjectNotFound) {
			api.WriteNotFound(w, r, "project not found")
			return
		}
		log := logging.FromContext(ctx).WithHandler("Delete")
		log.Error("failed to delete project", logging.FieldError, err.Error(), logging.FieldProjectName, name)
		api.WriteInternalError(w, r, "failed to delete project")
		return
	}

	api.WriteSuccess(w, r, map[string]string{
		"status":  "deleted",
		"project": name,
	})
}

// ListTemplates returns available project templates.
// GET /templates
func (h *ProjectManagementHandler) ListTemplates(w http.ResponseWriter, r *http.Request) {
	ctx, cancel := context.WithTimeout(r.Context(), TimeoutLookup)
	defer cancel()

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

	templates, err := h.infraService.ListTemplates(ctx)
	if err != nil {
		log := logging.FromContext(ctx).WithHandler("ListTemplates")
		log.Error("failed to list templates", logging.FieldError, err.Error())
		api.WriteInternalError(w, r, "failed to list templates")
		return
	}

	// Convert to response format
	response := make([]map[string]any, len(templates))
	for i, t := range templates {
		response[i] = map[string]any{
			"name":        t.Name,
			"description": t.Description,
			"stack":       t.Stack,
			"files":       t.Files,
		}
	}

	api.WriteSuccess(w, r, response)
}

// GetTemplate returns details about a specific template.
// GET /templates/{name}
func (h *ProjectManagementHandler) GetTemplate(w http.ResponseWriter, r *http.Request) {
	name := chi.URLParam(r, "name")
	ctx, cancel := context.WithTimeout(r.Context(), TimeoutLookup)
	defer cancel()

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

	template, err := h.infraService.GetTemplate(ctx, name)
	if err != nil {
		if errors.Is(err, domain.ErrTemplateNotFound) {
			api.WriteNotFound(w, r, "template not found")
			return
		}
		log := logging.FromContext(ctx).WithHandler("GetTemplate")
		log.Error("failed to get template", logging.FieldError, err.Error(), "name", name)
		api.WriteInternalError(w, r, "failed to get template")
		return
	}

	api.WriteSuccess(w, r, map[string]any{
		"name":        template.Name,
		"description": template.Description,
		"stack":       template.Stack,
		"files":       template.Files,
	})
}

// CleanupTestProjectsRequest is the request body for DELETE /projects/cleanup.
type CleanupTestProjectsRequest struct {
	Patterns     []string `json:"patterns"`         // Name patterns to match (e.g., "tree-test-*", "landing-test-*")
	OlderThanHrs int      `json:"older_than_hours"` // Only delete projects older than this many hours
	DryRun       bool     `json:"dry_run"`          // If true, don't actually delete, just return what would be deleted
}

// CleanupTestProjects deletes test projects matching patterns that are older than a specified age.
// DELETE /projects/cleanup
// Admin scope required for safety.
func (h *ProjectManagementHandler) CleanupTestProjects(w http.ResponseWriter, r *http.Request) {
	ctx, cancel := context.WithTimeout(r.Context(), TimeoutLongRunning)
	defer cancel()

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

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

	if len(req.Patterns) == 0 {
		api.WriteBadRequest(w, r, "at least one pattern is required")
		return
	}

	if req.OlderThanHrs < 0 {
		api.WriteBadRequest(w, r, "older_than_hours must be non-negative")
		return
	}

	result, err := h.infraService.CleanupTestProjects(ctx, service.CleanupTestProjectsRequest{
		Patterns:     req.Patterns,
		OlderThanHrs: req.OlderThanHrs,
		DryRun:       req.DryRun,
	})
	if err != nil {
		log := logging.FromContext(ctx).WithHandler("CleanupTestProjects")
		log.Error("failed to cleanup test projects", logging.FieldError, err.Error())
		api.WriteInternalError(w, r, "failed to cleanup test projects")
		return
	}

	api.WriteSuccess(w, r, map[string]any{
		"deleted": result.Deleted,
		"count":   result.Count,
		"dry_run": result.DryRun,
	})
}

// ListComponentTemplates returns available component templates.
// GET /templates/components
func (h *ProjectManagementHandler) ListComponentTemplates(w http.ResponseWriter, r *http.Request) {
	ctx, cancel := context.WithTimeout(r.Context(), TimeoutLookup)
	defer cancel()

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

	componentType := r.URL.Query().Get("type")

	components, err := h.infraService.ListComponentTemplates(ctx, componentType)
	if err != nil {
		log := logging.FromContext(ctx).WithHandler("ListComponentTemplates")
		log.Error("failed to list component templates", logging.FieldError, err.Error())
		api.WriteInternalError(w, r, "failed to list component templates")
		return
	}

	response := make([]map[string]any, len(components))
	for i, c := range components {
		response[i] = map[string]any{
			"type":         c.Type,
			"description":  c.Description,
			"stack":        c.Stack,
			"default_port": c.DefaultPort,
			"dest_dir":     c.DestDir,
			"files":        c.Files,
		}
	}

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