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

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

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

// VerifyHandler handles visual verification endpoints.
type VerifyHandler struct {
	verifyService *service.VerifyService
	streams       port.StreamPublisher
}

// NewVerifyHandler creates a new verify handler.
func NewVerifyHandler(verifyService *service.VerifyService, streams port.StreamPublisher) *VerifyHandler {
	return &VerifyHandler{
		verifyService: verifyService,
		streams:       streams,
	}
}

// Mount registers the verify routes.
func (h *VerifyHandler) Mount(r api.Router) {
	r.Route("/verify", func(r chi.Router) {
		r.With(auth.RequireScope(auth.ScopeVerifyWrite, auth.ScopeAdmin)).Post("/", h.Submit)
		r.With(auth.RequireScope(auth.ScopeVerifyRead, auth.ScopeAdmin)).Get("/{taskId}", h.Get)
		r.With(auth.RequireScope(auth.ScopeVerifyRead, auth.ScopeAdmin)).Get("/{taskId}/stream", h.Stream)
		r.With(auth.RequireScope(auth.ScopeVerifyWrite, auth.ScopeAdmin)).Delete("/{taskId}", h.Cancel)
	})
	r.With(auth.RequireScope(auth.ScopeVerifyRead, auth.ScopeAdmin)).Get("/projects/{id}/verify", h.ListByProject)
}

// SubmitVerifyRequest is the request body for POST /verify.
type SubmitVerifyRequest struct {
	ProjectID   string   `json:"project_id"`
	URL         string   `json:"url"`
	Viewports   []string `json:"viewports,omitempty"`
	WaitFor     string   `json:"wait_for,omitempty"`
	WaitTimeout int      `json:"wait_timeout,omitempty"`
	FullPage    bool     `json:"full_page,omitempty"`
	Video       bool     `json:"video,omitempty"`
	CallbackURL string   `json:"callback_url,omitempty"`
}

// SubmitVerifyResponse is the response for POST /verify.
type SubmitVerifyResponse struct {
	TaskID    string `json:"task_id"`
	StatusURL string `json:"status_url"`
	StreamURL string `json:"stream_url"`
}

// VerifyTaskResponse is the response for GET /verify/{taskId}.
type VerifyTaskResponse struct {
	ID          string            `json:"id"`
	ProjectID   string            `json:"project_id"`
	Status      string            `json:"status"`
	URL         string            `json:"url"`
	Screenshots map[string]string `json:"screenshots,omitempty"`
	Video       string            `json:"video,omitempty"`
	Error       string            `json:"error,omitempty"`
	CreatedAt   string            `json:"created_at"`
	CompletedAt string            `json:"completed_at,omitempty"`
	DurationMs  int64             `json:"duration_ms,omitempty"`
}

// Submit enqueues a visual verification task.
// POST /verify
func (h *VerifyHandler) Submit(w http.ResponseWriter, r *http.Request) {
	var req SubmitVerifyRequest
	if err := api.DecodeJSON(r, &req); err != nil {
		api.WriteBadRequest(w, r, "invalid request body")
		return
	}

	// Validate required fields
	if req.ProjectID == "" {
		api.WriteBadRequest(w, r, "project_id is required")
		return
	}
	if req.URL == "" {
		api.WriteBadRequest(w, r, "url is required")
		return
	}

	// Validate callback URL if provided
	if req.CallbackURL != "" {
		if err := domain.ValidateCallbackURL(req.CallbackURL); err != nil {
			api.WriteBadRequest(w, r, err.Error())
			return
		}
	}

	// Build verify spec
	spec := domain.VerifySpec{
		URL:         req.URL,
		Viewports:   req.Viewports,
		WaitFor:     req.WaitFor,
		WaitTimeout: req.WaitTimeout,
		FullPage:    req.FullPage,
		Video:       req.Video,
		CallbackURL: req.CallbackURL,
	}

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

	taskID, err := h.verifyService.SubmitCapture(ctx, req.ProjectID, spec)
	if err != nil {
		if errors.Is(err, domain.ErrVerifyURLRequired) {
			api.WriteBadRequest(w, r, err.Error())
			return
		}
		api.WriteInternalError(w, r, "failed to submit verify task")
		return
	}

	api.WriteCreated(w, r, SubmitVerifyResponse{
		TaskID:    taskID,
		StatusURL: "/verify/" + taskID,
		StreamURL: "/verify/" + taskID + "/stream",
	})
}

// Get retrieves a verify task by ID.
// GET /verify/{taskId}
func (h *VerifyHandler) Get(w http.ResponseWriter, r *http.Request) {
	taskID := chi.URLParam(r, "taskId")
	if taskID == "" {
		api.WriteBadRequest(w, r, "task ID is required")
		return
	}

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

	task, err := h.verifyService.GetCapture(ctx, taskID)
	if err != nil {
		if errors.Is(err, domain.ErrWorkTaskNotFound) {
			api.WriteNotFound(w, r, fmt.Sprintf("verify task not found: %s", taskID))
			return
		}
		api.WriteInternalError(w, r, "failed to get verify task")
		return
	}

	api.WriteSuccess(w, r, toVerifyTaskResponse(task))
}

// Stream streams verify task events via SSE.
// GET /verify/{taskId}/stream
func (h *VerifyHandler) Stream(w http.ResponseWriter, r *http.Request) {
	taskID := chi.URLParam(r, "taskId")
	if taskID == "" {
		api.WriteBadRequest(w, r, "task ID is required")
		return
	}

	lastEventID := r.Header.Get("Last-Event-ID")

	// Verify task exists
	ctx, cancel := context.WithTimeout(r.Context(), TimeoutFastLookup)
	defer cancel()

	_, err := h.verifyService.GetCapture(ctx, taskID)
	if err != nil {
		if errors.Is(err, domain.ErrWorkTaskNotFound) {
			api.WriteNotFound(w, r, fmt.Sprintf("verify task not found: %s", taskID))
			return
		}
		api.WriteInternalError(w, r, "failed to get verify task")
		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
	var events <-chan port.StreamEvent
	var cleanup func()

	if lastEventID != "" {
		events, cleanup = h.streams.SubscribeFromID(taskID, lastEventID)
	} else {
		events, cleanup = h.streams.Subscribe(taskID)
	}
	defer cleanup()

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

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

	for {
		select {
		case <-reqCtx.Done():
			return
		case event, ok := <-events:
			if !ok {
				return
			}
			writeSSEWithID(w, flusher, event.ID, event.Type, event.Data)
			// Check for terminal events
			if event.Type == worker.VerifyEventCompleted || event.Type == worker.VerifyEventFailed {
				return
			}
		case <-heartbeat.C:
			writeSSE(w, flusher, "heartbeat", map[string]any{
				"timestamp": time.Now().UTC().Format(time.RFC3339),
			})
		}
	}
}

// Cancel cancels a pending verify task.
// DELETE /verify/{taskId}
func (h *VerifyHandler) Cancel(w http.ResponseWriter, r *http.Request) {
	taskID := chi.URLParam(r, "taskId")
	if taskID == "" {
		api.WriteBadRequest(w, r, "task ID is required")
		return
	}

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

	if err := h.verifyService.CancelCapture(ctx, taskID); err != nil {
		if errors.Is(err, domain.ErrWorkTaskNotFound) {
			api.WriteNotFound(w, r, fmt.Sprintf("verify task not found: %s", taskID))
			return
		}
		api.WriteBadRequest(w, r, err.Error())
		return
	}

	api.WriteSuccess(w, r, map[string]any{
		"task_id": taskID,
		"status":  "cancelled",
		"message": "verify task cancelled successfully",
	})
}

// ListByProject returns verify tasks for a project.
// GET /projects/{id}/verify?limit=50&offset=0
func (h *VerifyHandler) ListByProject(w http.ResponseWriter, r *http.Request) {
	projectID := chi.URLParam(r, "id")
	if projectID == "" {
		api.WriteBadRequest(w, r, "project ID is required")
		return
	}

	// Parse pagination options
	opts := domain.DefaultWorkListOptions()
	if limitStr := r.URL.Query().Get("limit"); limitStr != "" {
		limit, err := strconv.Atoi(limitStr)
		if err != nil {
			api.WriteBadRequest(w, r, "limit must be a valid integer")
			return
		}
		opts.Limit = limit
	}
	if offsetStr := r.URL.Query().Get("offset"); offsetStr != "" {
		offset, err := strconv.Atoi(offsetStr)
		if err != nil {
			api.WriteBadRequest(w, r, "offset must be a valid integer")
			return
		}
		opts.Offset = offset
	}

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

	result, err := h.verifyService.ListCaptures(ctx, projectID, opts)
	if err != nil {
		api.WriteInternalError(w, r, "failed to list verify tasks")
		return
	}

	dtos := make([]*VerifyTaskResponse, len(result.Tasks))
	for i, task := range result.Tasks {
		dtos[i] = toVerifyTaskResponse(task)
	}

	api.WriteSuccess(w, r, map[string]any{
		"tasks":      dtos,
		"project_id": projectID,
		"total":      result.Total,
		"limit":      result.Limit,
		"offset":     result.Offset,
	})
}

// toVerifyTaskResponse converts a WorkTask to a VerifyTaskResponse.
func toVerifyTaskResponse(task *domain.WorkTask) *VerifyTaskResponse {
	if task == nil {
		return nil
	}

	resp := &VerifyTaskResponse{
		ID:        task.ID,
		ProjectID: task.ProjectID,
		Status:    string(task.Status),
		CreatedAt: task.CreatedAt.Format("2006-01-02T15:04:05Z07:00"),
		Error:     task.Error,
	}

	// Extract URL from spec
	if url, ok := task.Spec["url"].(string); ok {
		resp.URL = url
	}

	// Extract results from task.Result artifacts
	if task.Result != nil && task.Result.Artifacts != nil {
		resp.Screenshots = make(map[string]string)
		for key, value := range task.Result.Artifacts {
			if len(key) > 11 && key[:11] == "screenshot_" {
				viewport := key[11:]
				resp.Screenshots[viewport] = value
			}
			if key == "video" {
				resp.Video = value
			}
			if key == "duration_ms" {
				if durationMs, err := strconv.ParseInt(value, 10, 64); err == nil {
					resp.DurationMs = durationMs
				}
			}
		}
	}

	if task.CompletedAt != nil {
		resp.CompletedAt = task.CompletedAt.Format("2006-01-02T15:04:05Z07:00")
	}

	return resp
}
