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

import (
	"context"
	"encoding/base64"
	"errors"
	"fmt"
	"net/http"
	"strings"

	"github.com/go-chi/chi/v5"
	"github.com/orchard9/rdev/internal/adapter/kubernetes"
	"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"
)

// maxContentSize limits the size of content that can be written (1MB).
const maxContentSize = 1 << 20

// ClaudeConfigHandler handles Claude config management endpoints.
// Commands, skills, and agents live in /workspace/.claude/ (per-project, in git).
// Credentials live in /root/.claude/ (shared PVC).
type ClaudeConfigHandler struct {
	projectRepo    *kubernetes.ProjectRepository
	executor       *kubernetes.Executor
	projectService *service.ProjectService
}

// NewClaudeConfigHandler creates a new claude config handler with injected dependencies.
func NewClaudeConfigHandler(projectRepo *kubernetes.ProjectRepository, exec *kubernetes.Executor) *ClaudeConfigHandler {
	return &ClaudeConfigHandler{
		projectRepo: projectRepo,
		executor:    exec,
	}
}

// NewClaudeConfigHandlerWithService creates a new claude config handler with injected dependencies.
// This maintains proper DI by receiving all dependencies from the caller.
func NewClaudeConfigHandlerWithService(
	projectService *service.ProjectService,
	projectRepo *kubernetes.ProjectRepository,
	exec *kubernetes.Executor,
) *ClaudeConfigHandler {
	return &ClaudeConfigHandler{
		projectService: projectService,
		projectRepo:    projectRepo,
		executor:       exec,
	}
}

// Mount registers the claude-config routes.
func (h *ClaudeConfigHandler) Mount(r api.Router) {
	r.Route("/projects/{id}/claude-config", func(r chi.Router) {
		// Overview (read)
		r.With(auth.RequireScope(auth.ScopeProjectsRead, auth.ScopeAdmin)).Get("/", h.Overview)

		// Commands - read
		r.With(auth.RequireScope(auth.ScopeProjectsRead, auth.ScopeAdmin)).Get("/commands", h.ListCommands)
		r.With(auth.RequireScope(auth.ScopeProjectsRead, auth.ScopeAdmin)).Get("/commands/{name}", h.GetCommand)

		// Commands - write
		r.With(auth.RequireScope(auth.ScopeProjectsExecute, auth.ScopeAdmin)).Post("/commands", h.CreateCommand)
		r.With(auth.RequireScope(auth.ScopeProjectsExecute, auth.ScopeAdmin)).Put("/commands/{name}", h.UpdateCommand)
		r.With(auth.RequireScope(auth.ScopeProjectsExecute, auth.ScopeAdmin)).Delete("/commands/{name}", h.DeleteCommand)

		// Skills - read
		r.With(auth.RequireScope(auth.ScopeProjectsRead, auth.ScopeAdmin)).Get("/skills", h.ListSkills)
		r.With(auth.RequireScope(auth.ScopeProjectsRead, auth.ScopeAdmin)).Get("/skills/{name}", h.GetSkill)

		// Skills - write
		r.With(auth.RequireScope(auth.ScopeProjectsExecute, auth.ScopeAdmin)).Post("/skills", h.CreateSkill)
		r.With(auth.RequireScope(auth.ScopeProjectsExecute, auth.ScopeAdmin)).Put("/skills/{name}", h.UpdateSkill)
		r.With(auth.RequireScope(auth.ScopeProjectsExecute, auth.ScopeAdmin)).Delete("/skills/{name}", h.DeleteSkill)

		// Agents - read
		r.With(auth.RequireScope(auth.ScopeProjectsRead, auth.ScopeAdmin)).Get("/agents", h.ListAgents)
		r.With(auth.RequireScope(auth.ScopeProjectsRead, auth.ScopeAdmin)).Get("/agents/{name}", h.GetAgent)

		// Agents - write
		r.With(auth.RequireScope(auth.ScopeProjectsExecute, auth.ScopeAdmin)).Post("/agents", h.CreateAgent)
		r.With(auth.RequireScope(auth.ScopeProjectsExecute, auth.ScopeAdmin)).Put("/agents/{name}", h.UpdateAgent)
		r.With(auth.RequireScope(auth.ScopeProjectsExecute, auth.ScopeAdmin)).Delete("/agents/{name}", h.DeleteAgent)
	})
}

// ConfigOverview is the response for GET /projects/{id}/claude-config
type ConfigOverview struct {
	Project  string   `json:"project"`
	Path     string   `json:"path"`
	Commands []string `json:"commands"`
	Skills   []string `json:"skills"`
	Agents   []string `json:"agents"`
}

// Overview returns an overview of the project's Claude config.
// GET /projects/{id}/claude-config
func (h *ClaudeConfigHandler) Overview(w http.ResponseWriter, r *http.Request) {
	id := chi.URLParam(r, "id")

	project, err := h.getProject(r.Context(), domain.ProjectID(id))
	if err != nil {
		if errors.Is(err, domain.ErrProjectNotFound) {
			api.WriteNotFound(w, r, fmt.Sprintf("project not found: %s", id))
			return
		}
		api.WriteInternalError(w, r, "failed to get project")
		return
	}

	overview := ConfigOverview{
		Project:  id,
		Path:     "/workspace/.claude",
		Commands: h.listItems(r.Context(), project.PodName, "commands"),
		Skills:   h.listItems(r.Context(), project.PodName, "skills"),
		Agents:   h.listItems(r.Context(), project.PodName, "agents"),
	}

	api.WriteSuccess(w, r, overview)
}

// ConfigItem represents a command, skill, or agent.
type ConfigItem struct {
	Name    string `json:"name"`
	Type    string `json:"type"`
	Content string `json:"content"`
}

// ConfigItemRequest is the request body for creating/updating items.
type ConfigItemRequest struct {
	Name    string `json:"name,omitempty"` // Optional for POST (can be in URL)
	Content string `json:"content"`
}

// --- Commands ---

// ListCommands returns all commands for a project.
// GET /projects/{id}/claude-config/commands
func (h *ClaudeConfigHandler) ListCommands(w http.ResponseWriter, r *http.Request) {
	h.listType(w, r, "commands")
}

// CreateCommand creates a new command.
// POST /projects/{id}/claude-config/commands
func (h *ClaudeConfigHandler) CreateCommand(w http.ResponseWriter, r *http.Request) {
	h.createItem(w, r, "commands")
}

// GetCommand returns a specific command.
// GET /projects/{id}/claude-config/commands/{name}
func (h *ClaudeConfigHandler) GetCommand(w http.ResponseWriter, r *http.Request) {
	h.getItem(w, r, "commands")
}

// UpdateCommand updates a command.
// PUT /projects/{id}/claude-config/commands/{name}
func (h *ClaudeConfigHandler) UpdateCommand(w http.ResponseWriter, r *http.Request) {
	h.updateItem(w, r, "commands")
}

// DeleteCommand deletes a command.
// DELETE /projects/{id}/claude-config/commands/{name}
func (h *ClaudeConfigHandler) DeleteCommand(w http.ResponseWriter, r *http.Request) {
	h.deleteItem(w, r, "commands")
}

// --- Skills ---

// ListSkills returns all skills for a project.
// GET /projects/{id}/claude-config/skills
func (h *ClaudeConfigHandler) ListSkills(w http.ResponseWriter, r *http.Request) {
	h.listType(w, r, "skills")
}

// CreateSkill creates a new skill.
// POST /projects/{id}/claude-config/skills
func (h *ClaudeConfigHandler) CreateSkill(w http.ResponseWriter, r *http.Request) {
	h.createItem(w, r, "skills")
}

// GetSkill returns a specific skill.
// GET /projects/{id}/claude-config/skills/{name}
func (h *ClaudeConfigHandler) GetSkill(w http.ResponseWriter, r *http.Request) {
	h.getItem(w, r, "skills")
}

// UpdateSkill updates a skill.
// PUT /projects/{id}/claude-config/skills/{name}
func (h *ClaudeConfigHandler) UpdateSkill(w http.ResponseWriter, r *http.Request) {
	h.updateItem(w, r, "skills")
}

// DeleteSkill deletes a skill.
// DELETE /projects/{id}/claude-config/skills/{name}
func (h *ClaudeConfigHandler) DeleteSkill(w http.ResponseWriter, r *http.Request) {
	h.deleteItem(w, r, "skills")
}

// --- Agents ---

// ListAgents returns all agents for a project.
// GET /projects/{id}/claude-config/agents
func (h *ClaudeConfigHandler) ListAgents(w http.ResponseWriter, r *http.Request) {
	h.listType(w, r, "agents")
}

// CreateAgent creates a new agent.
// POST /projects/{id}/claude-config/agents
func (h *ClaudeConfigHandler) CreateAgent(w http.ResponseWriter, r *http.Request) {
	h.createItem(w, r, "agents")
}

// GetAgent returns a specific agent.
// GET /projects/{id}/claude-config/agents/{name}
func (h *ClaudeConfigHandler) GetAgent(w http.ResponseWriter, r *http.Request) {
	h.getItem(w, r, "agents")
}

// UpdateAgent updates an agent.
// PUT /projects/{id}/claude-config/agents/{name}
func (h *ClaudeConfigHandler) UpdateAgent(w http.ResponseWriter, r *http.Request) {
	h.updateItem(w, r, "agents")
}

// DeleteAgent deletes an agent.
// DELETE /projects/{id}/claude-config/agents/{name}
func (h *ClaudeConfigHandler) DeleteAgent(w http.ResponseWriter, r *http.Request) {
	h.deleteItem(w, r, "agents")
}

// --- Helper methods ---

// listItems returns the names of items in a directory.
func (h *ClaudeConfigHandler) listItems(ctx context.Context, pod, itemType string) []string {
	cmd := fmt.Sprintf("ls -1 /workspace/.claude/%s 2>/dev/null | sed 's/\\.md$//'", itemType)
	output, err := h.executor.ExecSimple(ctx, pod, cmd)
	if err != nil {
		return []string{}
	}

	items := []string{}
	for _, line := range strings.Split(strings.TrimSpace(output), "\n") {
		if line != "" {
			items = append(items, line)
		}
	}
	return items
}

// listType handles GET /projects/{id}/claude-config/{type}
func (h *ClaudeConfigHandler) listType(w http.ResponseWriter, r *http.Request, itemType string) {
	id := chi.URLParam(r, "id")

	project, err := h.getProject(r.Context(), domain.ProjectID(id))
	if err != nil {
		if errors.Is(err, domain.ErrProjectNotFound) {
			api.WriteNotFound(w, r, fmt.Sprintf("project not found: %s", id))
			return
		}
		api.WriteInternalError(w, r, "failed to get project")
		return
	}

	items := h.listItems(r.Context(), project.PodName, itemType)
	api.WriteSuccess(w, r, items)
}

// createItem handles POST /projects/{id}/claude-config/{type}
func (h *ClaudeConfigHandler) createItem(w http.ResponseWriter, r *http.Request, itemType string) {
	id := chi.URLParam(r, "id")

	project, err := h.getProject(r.Context(), domain.ProjectID(id))
	if err != nil {
		if errors.Is(err, domain.ErrProjectNotFound) {
			api.WriteNotFound(w, r, fmt.Sprintf("project not found: %s", id))
			return
		}
		api.WriteInternalError(w, r, "failed to get project")
		return
	}

	// Limit request body size to prevent DoS
	r.Body = http.MaxBytesReader(w, r.Body, maxContentSize)

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

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

	// Validate name (alphanumeric, dashes, underscores only, 1-64 chars)
	if err := validate.Name(req.Name, "name"); err != nil {
		api.WriteBadRequest(w, r, err.Error())
		return
	}

	// Ensure directory exists
	dirCmd := fmt.Sprintf("mkdir -p /workspace/.claude/%s", itemType)
	if _, err := h.executor.ExecSimple(r.Context(), project.PodName, dirCmd); err != nil {
		api.WriteInternalError(w, r, fmt.Sprintf("failed to create directory: %v", err))
		return
	}

	// Write file using base64 encoding to prevent shell injection
	// This avoids heredoc terminator injection attacks
	filePath := fmt.Sprintf("/workspace/.claude/%s/%s.md", itemType, req.Name)
	encoded := base64.StdEncoding.EncodeToString([]byte(req.Content))
	writeCmd := fmt.Sprintf("echo '%s' | base64 -d > %s", encoded, filePath)
	if _, err := h.executor.ExecSimple(r.Context(), project.PodName, writeCmd); err != nil {
		api.WriteInternalError(w, r, fmt.Sprintf("failed to write file: %v", err))
		return
	}

	item := ConfigItem{
		Name:    req.Name,
		Type:    itemType,
		Content: req.Content,
	}

	api.WriteJSON(w, r, http.StatusCreated, item)
}

// getItem handles GET /projects/{id}/claude-config/{type}/{name}
func (h *ClaudeConfigHandler) getItem(w http.ResponseWriter, r *http.Request, itemType string) {
	id := chi.URLParam(r, "id")
	name := chi.URLParam(r, "name")

	project, err := h.getProject(r.Context(), domain.ProjectID(id))
	if err != nil {
		if errors.Is(err, domain.ErrProjectNotFound) {
			api.WriteNotFound(w, r, fmt.Sprintf("project not found: %s", id))
			return
		}
		api.WriteInternalError(w, r, "failed to get project")
		return
	}

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

	filePath := fmt.Sprintf("/workspace/.claude/%s/%s.md", itemType, name)
	cmd := fmt.Sprintf("cat %s 2>/dev/null", filePath)
	output, err := h.executor.ExecSimple(r.Context(), project.PodName, cmd)
	if err != nil || output == "" {
		api.WriteNotFound(w, r, fmt.Sprintf("%s not found: %s", itemType, name))
		return
	}

	item := ConfigItem{
		Name:    name,
		Type:    itemType,
		Content: strings.TrimSpace(output),
	}

	api.WriteSuccess(w, r, item)
}

// updateItem handles PUT /projects/{id}/claude-config/{type}/{name}
func (h *ClaudeConfigHandler) updateItem(w http.ResponseWriter, r *http.Request, itemType string) {
	id := chi.URLParam(r, "id")
	name := chi.URLParam(r, "name")

	project, err := h.getProject(r.Context(), domain.ProjectID(id))
	if err != nil {
		if errors.Is(err, domain.ErrProjectNotFound) {
			api.WriteNotFound(w, r, fmt.Sprintf("project not found: %s", id))
			return
		}
		api.WriteInternalError(w, r, "failed to get project")
		return
	}

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

	// Limit request body size to prevent DoS
	r.Body = http.MaxBytesReader(w, r.Body, maxContentSize)

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

	if err := validate.Required(req.Content, "content"); err != nil {
		api.WriteBadRequest(w, r, err.Error())
		return
	}

	// Check file exists
	filePath := fmt.Sprintf("/workspace/.claude/%s/%s.md", itemType, name)
	checkCmd := fmt.Sprintf("test -f %s && echo exists", filePath)
	output, _ := h.executor.ExecSimple(r.Context(), project.PodName, checkCmd)
	if strings.TrimSpace(output) != "exists" {
		api.WriteNotFound(w, r, fmt.Sprintf("%s not found: %s", itemType, name))
		return
	}

	// Write file using base64 encoding to prevent shell injection
	encoded := base64.StdEncoding.EncodeToString([]byte(req.Content))
	writeCmd := fmt.Sprintf("echo '%s' | base64 -d > %s", encoded, filePath)
	if _, err := h.executor.ExecSimple(r.Context(), project.PodName, writeCmd); err != nil {
		api.WriteInternalError(w, r, fmt.Sprintf("failed to write file: %v", err))
		return
	}

	item := ConfigItem{
		Name:    name,
		Type:    itemType,
		Content: req.Content,
	}

	api.WriteSuccess(w, r, item)
}

// deleteItem handles DELETE /projects/{id}/claude-config/{type}/{name}
func (h *ClaudeConfigHandler) deleteItem(w http.ResponseWriter, r *http.Request, itemType string) {
	id := chi.URLParam(r, "id")
	name := chi.URLParam(r, "name")

	project, err := h.getProject(r.Context(), domain.ProjectID(id))
	if err != nil {
		if errors.Is(err, domain.ErrProjectNotFound) {
			api.WriteNotFound(w, r, fmt.Sprintf("project not found: %s", id))
			return
		}
		api.WriteInternalError(w, r, "failed to get project")
		return
	}

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

	filePath := fmt.Sprintf("/workspace/.claude/%s/%s.md", itemType, name)

	// Check file exists
	checkCmd := fmt.Sprintf("test -f %s && echo exists", filePath)
	output, _ := h.executor.ExecSimple(r.Context(), project.PodName, checkCmd)
	if strings.TrimSpace(output) != "exists" {
		api.WriteNotFound(w, r, fmt.Sprintf("%s not found: %s", itemType, name))
		return
	}

	// Delete file
	deleteCmd := fmt.Sprintf("rm %s", filePath)
	if _, err := h.executor.ExecSimple(r.Context(), project.PodName, deleteCmd); err != nil {
		api.WriteInternalError(w, r, fmt.Sprintf("failed to delete file: %v", err))
		return
	}

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

// getProject retrieves a project by ID using available methods.
// It prefers the project service if available, otherwise falls back to the project repository.
func (h *ClaudeConfigHandler) getProject(ctx context.Context, id domain.ProjectID) (*domain.Project, error) {
	// Add timeout for project lookup
	ctx, cancel := context.WithTimeout(ctx, TimeoutFastLookup)
	defer cancel()

	// Use service if available
	if h.projectService != nil {
		return h.projectService.Get(ctx, id)
	}

	// Fall back to direct repository access
	if h.projectRepo != nil {
		return h.projectRepo.Get(ctx, id)
	}

	return nil, domain.ErrProjectNotFound
}
