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

import (
	"context"
	"errors"
	"fmt"
	"net/http"
	"strings"
	"sync/atomic"
	"time"

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

// ProjectsHandler handles project-related endpoints.
type ProjectsHandler struct {
	// Legacy dependencies (for backward compatibility)
	projectRepo *kubernetes.ProjectRepository
	executor    *kubernetes.Executor
	streams     *streamManager
	cmdID       atomic.Uint64

	// New hexagonal architecture dependencies
	projectService *service.ProjectService
}

// NewProjectsHandler creates a new projects handler with injected dependencies.
func NewProjectsHandler(projectRepo *kubernetes.ProjectRepository, executor *kubernetes.Executor) *ProjectsHandler {
	return &ProjectsHandler{
		projectRepo: projectRepo,
		executor:    executor,
		streams:     newStreamManager(),
	}
}

// NewProjectsHandlerWithService creates a new projects handler with injected service.
func NewProjectsHandlerWithService(projectService *service.ProjectService) *ProjectsHandler {
	return &ProjectsHandler{
		projectService: projectService,
	}
}

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

		// Execute operations
		r.With(auth.RequireScope(auth.ScopeProjectsExecute, auth.ScopeAdmin)).Post("/{id}/claude", h.RunClaude)
		r.With(auth.RequireScope(auth.ScopeProjectsExecute, auth.ScopeAdmin)).Post("/{id}/shell", h.RunShell)
		r.With(auth.RequireScope(auth.ScopeProjectsExecute, auth.ScopeAdmin)).Post("/{id}/git", h.RunGit)
		r.With(auth.RequireScope(auth.ScopeProjectsExecute, auth.ScopeAdmin)).Get("/{id}/events", h.Events)
	})
}

// getAuditContext extracts audit-related information from the HTTP request.
func getAuditContext(r *http.Request) *service.AuditContext {
	apiKey := auth.GetAPIKey(r.Context())
	if apiKey == nil {
		return nil
	}

	return &service.AuditContext{
		APIKeyID:  string(apiKey.ID),
		ClientIP:  getClientIP(r),
		UserAgent: r.UserAgent(),
	}
}

// getClientIP extracts the client IP from the request.
func getClientIP(r *http.Request) string {
	// Check X-Forwarded-For header (set by proxies/load balancers)
	if xff := r.Header.Get("X-Forwarded-For"); xff != "" {
		// Take the first IP in the chain
		if idx := strings.Index(xff, ","); idx != -1 {
			return strings.TrimSpace(xff[:idx])
		}
		return strings.TrimSpace(xff)
	}

	// Check X-Real-IP header
	if xri := r.Header.Get("X-Real-IP"); xri != "" {
		return strings.TrimSpace(xri)
	}

	// Fall back to RemoteAddr
	addr := r.RemoteAddr
	// Handle IPv6 addresses like "[::1]:8080"
	if strings.HasPrefix(addr, "[") {
		if idx := strings.LastIndex(addr, "]:"); idx != -1 {
			return addr[1:idx]
		}
		return strings.Trim(addr, "[]")
	}
	// Handle IPv4 addresses like "192.168.1.1:8080"
	if idx := strings.LastIndex(addr, ":"); idx != -1 {
		return addr[:idx]
	}
	return addr
}

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

	// Use new service if available
	if h.projectService != nil {
		projects, err := h.projectService.List(ctx)
		if err != nil {
			api.WriteInternalError(w, r, "failed to list projects")
			return
		}
		api.WriteSuccess(w, r, projects)
		return
	}

	// Legacy path using hexagonal types
	if h.projectRepo != nil {
		_ = h.projectRepo.RefreshStatus(ctx)
		projects, err := h.projectRepo.List(ctx)
		if err != nil {
			api.WriteInternalError(w, r, "failed to list projects")
			return
		}
		api.WriteSuccess(w, r, projects)
		return
	}

	api.WriteInternalError(w, r, "no project service configured")
}

// Get returns a specific project by ID.
// GET /projects/{id}
func (h *ProjectsHandler) Get(w http.ResponseWriter, r *http.Request) {
	id := chi.URLParam(r, "id")
	ctx, cancel := context.WithTimeout(r.Context(), TimeoutFastLookup)
	defer cancel()

	// Use new service if available
	if h.projectService != nil {
		project, err := h.projectService.Get(ctx, 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
		}
		api.WriteSuccess(w, r, project)
		return
	}

	// Legacy path using hexagonal types
	if h.projectRepo != nil {
		_ = h.projectRepo.RefreshStatus(ctx)
		project, err := h.projectRepo.Get(ctx, 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
		}
		api.WriteSuccess(w, r, project)
		return
	}

	api.WriteNotFound(w, r, fmt.Sprintf("project not found: %s", id))
}

// Events streams command output via Server-Sent Events.
// GET /projects/{id}/events
// Supports Last-Event-ID header for reconnection with event replay.
func (h *ProjectsHandler) Events(w http.ResponseWriter, r *http.Request) {
	id := chi.URLParam(r, "id")
	streamID := r.URL.Query().Get("stream_id")
	lastEventID := r.Header.Get("Last-Event-ID")

	// Check project exists
	if h.projectService != nil {
		exists, err := h.projectService.Exists(r.Context(), domain.ProjectID(id))
		if err != nil || !exists {
			api.WriteNotFound(w, r, fmt.Sprintf("project not found: %s", id))
			return
		}
	} else if h.projectRepo != nil {
		exists, err := h.projectRepo.Exists(r.Context(), domain.ProjectID(id))
		if err != nil || !exists {
			api.WriteNotFound(w, r, fmt.Sprintf("project not found: %s", id))
			return
		}
	} else {
		api.WriteNotFound(w, r, fmt.Sprintf("project not found: %s", id))
		return
	}

	// Set 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("Access-Control-Allow-Origin", "*")

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

	// Subscribe to events - use service if available, with Last-Event-ID support
	var events <-chan port.StreamEvent
	var cleanup func()
	if h.projectService != nil {
		if lastEventID != "" {
			events, cleanup = h.projectService.SubscribeFromID(streamID, lastEventID)
		} else {
			events, cleanup = h.projectService.Subscribe(streamID)
		}
	} else {
		legacyEvents := h.streams.Subscribe(streamID)
		// Create adapter from legacy to port.StreamEvent with context cancellation
		portEvents := make(chan port.StreamEvent, 100)
		adapterCtx, adapterCancel := context.WithCancel(r.Context())
		go func() {
			defer close(portEvents)
			for {
				select {
				case ev, ok := <-legacyEvents:
					if !ok {
						return
					}
					select {
					case portEvents <- port.StreamEvent{Type: ev.Type, Data: ev.Data}:
					case <-adapterCtx.Done():
						return
					}
				case <-adapterCtx.Done():
					return
				}
			}
		}()
		events = portEvents
		cleanup = func() {
			adapterCancel()
			h.streams.Unsubscribe(streamID, legacyEvents)
		}
	}
	defer cleanup()

	// Send initial connected event
	writeSSE(w, flusher, "connected", map[string]any{
		"project":      id,
		"stream_id":    streamID,
		"reconnecting": lastEventID != "",
	})

	// Stream events until client disconnects or stream closes
	ctx := r.Context()
	heartbeat := time.NewTicker(30 * time.Second)
	defer heartbeat.Stop()

	for {
		select {
		case <-ctx.Done():
			return
		case event, ok := <-events:
			if !ok {
				return
			}
			// Include event ID in SSE output for reconnection support
			writeSSEWithID(w, flusher, event.ID, event.Type, event.Data)
			if event.Type == "complete" {
				return
			}
		case <-heartbeat.C:
			writeSSE(w, flusher, "heartbeat", map[string]any{
				"timestamp": time.Now().UTC().Format(time.RFC3339),
			})
		}
	}
}

// ProjectRepository returns the project repository for use by other handlers.
func (h *ProjectsHandler) ProjectRepository() *kubernetes.ProjectRepository {
	return h.projectRepo
}

// Executor returns the executor for use by other handlers.
func (h *ProjectsHandler) Executor() *kubernetes.Executor {
	return h.executor
}
