// Package claudebox provides HTTP server and handlers for the claudebox sidecar.
// This package enables HTTP-based execution of Claude Code, git, and SDLC operations
// instead of kubectl exec.
package claudebox

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

	"github.com/go-chi/chi/v5"
	"github.com/orchard9/rdev/internal/logging"
	"github.com/orchard9/rdev/pkg/api"
)

// writeRawJSON writes a raw JSON response without the rdev-api wrapper.
// The claudebox sidecar uses direct JSON responses, not the {data, meta} format.
func writeRawJSON(w http.ResponseWriter, status int, data any) {
	w.Header().Set("Content-Type", "application/json")
	w.WriteHeader(status)
	_ = json.NewEncoder(w).Encode(data)
}

// Server handles HTTP requests for claudebox operations.
type Server struct {
	executor   *Executor
	gitOps     *GitOperations
	sdlcRunner *SDLCRunner
	logger     *slog.Logger
}

// ServerConfig holds configuration for the claudebox server.
type ServerConfig struct {
	Executor   *Executor
	GitOps     *GitOperations
	SDLCRunner *SDLCRunner
	Logger     *slog.Logger
}

// NewServer creates a new claudebox HTTP server.
func NewServer(cfg ServerConfig) *Server {
	return &Server{
		executor:   cfg.Executor,
		gitOps:     cfg.GitOps,
		sdlcRunner: cfg.SDLCRunner,
		logger:     cfg.Logger,
	}
}

// Mount registers server routes on the router.
func (s *Server) Mount(r chi.Router) {
	r.Get("/health", s.handleHealth)
	r.Post("/execute", s.handleExecute)
	r.Post("/execute/stream", s.handleExecuteStream)
	r.Post("/git/clone", s.handleGitClone)
	r.Post("/git/commit-and-push", s.handleGitCommitAndPush)
	r.Get("/git/status", s.handleGitStatus)
	r.Post("/sdlc", s.handleSDLC)
}

// HealthResponse is the health check response.
type HealthResponse struct {
	Status    string `json:"status"`
	Timestamp string `json:"timestamp"`
	WorkDir   string `json:"work_dir"`
}

// handleHealth returns server health status.
func (s *Server) handleHealth(w http.ResponseWriter, r *http.Request) {
	resp := HealthResponse{
		Status:    "healthy",
		Timestamp: time.Now().UTC().Format(time.RFC3339),
		WorkDir:   s.executor.workDir,
	}
	writeRawJSON(w, http.StatusOK, resp)
}

// ExecuteRequest is the request to execute Claude Code.
type ExecuteRequest struct {
	Prompt       string            `json:"prompt"`
	AllowedTools []string          `json:"allowed_tools,omitempty"`
	WorkingDir   string            `json:"working_dir,omitempty"`
	Timeout      int               `json:"timeout_seconds,omitempty"` // seconds
	Metadata     map[string]string `json:"metadata,omitempty"`
}

// ExecuteResponse is the response from executing Claude Code.
type ExecuteResponse struct {
	Success     bool              `json:"success"`
	Output      string            `json:"output"`
	ExitCode    int               `json:"exit_code"`
	DurationMs  int64             `json:"duration_ms"`
	Error       string            `json:"error,omitempty"`
	SessionID   string            `json:"session_id,omitempty"`
	FinalOutput string            `json:"final_output,omitempty"`
	Artifacts   map[string]string `json:"artifacts,omitempty"`
}

// handleExecute runs Claude Code and returns the complete result.
func (s *Server) handleExecute(w http.ResponseWriter, r *http.Request) {
	ctx := r.Context()
	log := logging.FromContext(ctx)

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

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

	log.Info("executing Claude Code", "prompt_len", len(req.Prompt))

	result := s.executor.Execute(ctx, &req)
	resp := ExecuteResponse{
		Success:     result.Success,
		Output:      result.Output,
		ExitCode:    result.ExitCode,
		DurationMs:  result.DurationMs,
		SessionID:   result.SessionID,
		FinalOutput: result.FinalOutput,
	}
	if result.Error != nil {
		resp.Error = result.Error.Error()
	}

	writeRawJSON(w, http.StatusOK, resp)
}

// StreamEvent is an SSE event for streaming execution.
type StreamEvent struct {
	Type      string         `json:"type"`
	Content   string         `json:"content,omitempty"`
	Stream    string         `json:"stream,omitempty"`
	ToolName  string         `json:"tool_name,omitempty"`
	Data      map[string]any `json:"data,omitempty"`
	Timestamp string         `json:"timestamp"`
}

// handleExecuteStream runs Claude Code and streams events via SSE.
func (s *Server) handleExecuteStream(w http.ResponseWriter, r *http.Request) {
	ctx := r.Context()
	log := logging.FromContext(ctx)

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

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

	// Set up SSE headers
	w.Header().Set("Content-Type", "text/event-stream")
	w.Header().Set("Cache-Control", "no-cache")
	w.Header().Set("Connection", "keep-alive")
	w.Header().Set("X-Accel-Buffering", "no")

	flusher, ok := w.(http.Flusher)
	if !ok {
		api.WriteInternalError(w, r, "streaming not supported")
		return
	}

	log.Info("starting streaming execution", "prompt_len", len(req.Prompt))

	// Stream events via callback
	eventCh := make(chan StreamEvent, 100)
	go func() {
		defer close(eventCh)
		s.executor.ExecuteStream(ctx, &req, func(evt StreamEvent) {
			select {
			case eventCh <- evt:
			case <-ctx.Done():
			}
		})
	}()

	// Write events to client
	for evt := range eventCh {
		data, err := json.Marshal(evt)
		if err != nil {
			log.Warn("failed to marshal event", logging.FieldError, err)
			continue
		}

		_, writeErr := w.Write([]byte("data: " + string(data) + "\n\n"))
		if writeErr != nil {
			log.Debug("client disconnected during stream")
			return
		}
		flusher.Flush()
	}
}

// GitCloneRequest is the request to clone a repository.
type GitCloneRequest struct {
	CloneURL string `json:"clone_url"`
	WorkDir  string `json:"work_dir,omitempty"` // defaults to /workspace
}

// GitCloneResponse is the response from cloning.
type GitCloneResponse struct {
	Success bool   `json:"success"`
	Cloned  bool   `json:"cloned"` // true if cloned, false if already existed
	Error   string `json:"error,omitempty"`
}

// handleGitClone clones or updates a git repository.
func (s *Server) handleGitClone(w http.ResponseWriter, r *http.Request) {
	ctx := r.Context()

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

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

	workDir := req.WorkDir
	if workDir == "" {
		workDir = s.gitOps.workDir
	}

	result := s.gitOps.CloneRepo(ctx, workDir, req.CloneURL)
	resp := GitCloneResponse{
		Success: result.Error == nil,
		Cloned:  result.Cloned,
	}
	if result.Error != nil {
		resp.Error = result.Error.Error()
	}

	writeRawJSON(w, http.StatusOK, resp)
}

// GitCommitAndPushRequest is the request to commit and push changes.
type GitCommitAndPushRequest struct {
	Message string `json:"message"`
	Push    bool   `json:"push"`
	WorkDir string `json:"work_dir,omitempty"` // defaults to /workspace
}

// GitCommitAndPushResponse is the response from commit and push.
type GitCommitAndPushResponse struct {
	Success      bool     `json:"success"`
	HasChanges   bool     `json:"has_changes"`
	CommitSHA    string   `json:"commit_sha,omitempty"`
	FilesChanged []string `json:"files_changed,omitempty"`
	Pushed       bool     `json:"pushed"`
	Error        string   `json:"error,omitempty"`
}

// handleGitCommitAndPush commits and optionally pushes changes.
func (s *Server) handleGitCommitAndPush(w http.ResponseWriter, r *http.Request) {
	ctx := r.Context()

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

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

	workDir := req.WorkDir
	if workDir == "" {
		workDir = s.gitOps.workDir
	}

	result := s.gitOps.CommitAndPush(ctx, workDir, req.Message, req.Push)
	resp := GitCommitAndPushResponse{
		Success:      result.Error == nil,
		HasChanges:   result.HasChanges,
		CommitSHA:    result.CommitSHA,
		FilesChanged: result.FilesChanged,
		Pushed:       result.Pushed,
	}
	if result.Error != nil {
		resp.Error = result.Error.Error()
	}

	writeRawJSON(w, http.StatusOK, resp)
}

// GitStatusResponse is the response from git status.
type GitStatusResponse struct {
	IsRepo       bool     `json:"is_repo"`
	HasChanges   bool     `json:"has_changes"`
	ChangedFiles []string `json:"changed_files,omitempty"`
	Branch       string   `json:"branch,omitempty"`
	Error        string   `json:"error,omitempty"`
}

// handleGitStatus returns the git status of the workspace.
func (s *Server) handleGitStatus(w http.ResponseWriter, r *http.Request) {
	ctx := r.Context()

	workDir := r.URL.Query().Get("work_dir")
	if workDir == "" {
		workDir = s.gitOps.workDir
	}

	status, err := s.gitOps.Status(ctx, workDir)
	if err != nil {
		writeRawJSON(w, http.StatusOK, GitStatusResponse{
			IsRepo: false,
			Error:  err.Error(),
		})
		return
	}

	writeRawJSON(w, http.StatusOK, status)
}

// SDLCRequest is the request to run an SDLC command.
type SDLCRequest struct {
	Command string   `json:"command"`
	Args    []string `json:"args,omitempty"`
	WorkDir string   `json:"work_dir,omitempty"` // defaults to /workspace
}

// SDLCResponse is the response from running an SDLC command.
type SDLCResponse struct {
	Success bool            `json:"success"`
	Output  string          `json:"output"`
	Data    json.RawMessage `json:"data,omitempty"` // Parsed JSON from sdlc --json output
	Error   string          `json:"error,omitempty"`
}

// handleSDLC runs an SDLC CLI command.
func (s *Server) handleSDLC(w http.ResponseWriter, r *http.Request) {
	ctx := r.Context()

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

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

	workDir := req.WorkDir
	if workDir == "" {
		workDir = s.sdlcRunner.workDir
	}

	result := s.sdlcRunner.Run(ctx, workDir, req.Command, req.Args)
	resp := SDLCResponse{
		Success: result.Success,
		Output:  result.Output,
		Data:    result.Data,
	}
	if result.Error != nil {
		resp.Error = result.Error.Error()
	}

	writeRawJSON(w, http.StatusOK, resp)
}
