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

import (
	"errors"
	"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/pkg/api"
)

// OperationsHandler handles operation query endpoints.
type OperationsHandler struct {
	repo port.OperationRepository
}

// NewOperationsHandler creates a new operations handler.
func NewOperationsHandler(repo port.OperationRepository) *OperationsHandler {
	return &OperationsHandler{repo: repo}
}

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

// OperationSummaryResponse is the response for listing operations.
type OperationSummaryResponse struct {
	ID           string  `json:"id"`
	Type         string  `json:"type"`
	Status       string  `json:"status"`
	StartedAt    string  `json:"started_at"`
	DurationMs   int64   `json:"duration_ms,omitempty"`
	Error        string  `json:"error,omitempty"`
	StepsSummary string  `json:"steps_summary,omitempty"`
	CommitSHA    *string `json:"commit_sha,omitempty"`
	ExternalRef  *string `json:"external_ref,omitempty"`
}

// OperationStepResponse is the response for a single operation step.
type OperationStepResponse struct {
	Name        string         `json:"name"`
	Status      string         `json:"status"`
	StartedAt   string         `json:"started_at"`
	DurationMs  int64          `json:"duration_ms,omitempty"`
	Output      map[string]any `json:"output,omitempty"`
	Error       string         `json:"error,omitempty"`
	ErrorDetail string         `json:"error_detail,omitempty"`
}

// OperationDetailResponse is the full response for a single operation.
type OperationDetailResponse struct {
	ID          string                  `json:"id"`
	ProjectID   string                  `json:"project_id"`
	Type        string                  `json:"type"`
	Status      string                  `json:"status"`
	RequestID   string                  `json:"request_id,omitempty"`
	TriggeredBy string                  `json:"triggered_by,omitempty"`
	CommitSHA   string                  `json:"commit_sha,omitempty"`
	ExternalRef string                  `json:"external_ref,omitempty"`
	StartedAt   string                  `json:"started_at"`
	CompletedAt *string                 `json:"completed_at,omitempty"`
	DurationMs  int64                   `json:"duration_ms,omitempty"`
	Input       map[string]any          `json:"input,omitempty"`
	Output      map[string]any          `json:"output,omitempty"`
	Error       string                  `json:"error,omitempty"`
	ErrorDetail string                  `json:"error_detail,omitempty"`
	Steps       []OperationStepResponse `json:"steps,omitempty"`
}

// List returns operations for a project with optional filters.
// GET /projects/{id}/operations
// Query parameters:
//   - status: filter by status (pending, running, completed, failed)
//   - type: filter by type (project.create, component.add, build, resource.provision)
//   - commit: filter by commit SHA
//   - since: filter by start time (RFC3339 or duration like "1h", "24h")
//   - limit: maximum number of entries (default 50, max 200)
func (h *OperationsHandler) List(w http.ResponseWriter, r *http.Request) {
	projectID := chi.URLParam(r, "id")
	if projectID == "" {
		api.WriteBadRequest(w, r, "project ID is required")
		return
	}

	filters := domain.DefaultOperationFilters()
	filters.ProjectID = projectID

	// Parse status filter
	if status := r.URL.Query().Get("status"); status != "" {
		s := domain.OperationStatus(status)
		if !s.IsValid() {
			api.WriteBadRequest(w, r, "invalid status: must be pending, running, completed, or failed")
			return
		}
		filters.Status = s
	}

	// Parse type filter
	if opType := r.URL.Query().Get("type"); opType != "" {
		t := domain.OperationType(opType)
		if !t.IsValid() {
			api.WriteBadRequest(w, r, "invalid type: must be project.create, component.add, build, or resource.provision")
			return
		}
		filters.Type = t
	}

	// Parse commit filter
	if commit := r.URL.Query().Get("commit"); commit != "" {
		filters.CommitSHA = commit
	}

	// Parse since filter (RFC3339 or duration)
	if sinceStr := r.URL.Query().Get("since"); sinceStr != "" {
		since, err := parseSince(sinceStr)
		if err != nil {
			api.WriteBadRequest(w, r, "invalid since: must be RFC3339 or duration (e.g., 1h, 24h)")
			return
		}
		filters.Since = since
	}

	// Parse limit
	if limitStr := r.URL.Query().Get("limit"); limitStr != "" {
		limit, err := strconv.Atoi(limitStr)
		if err != nil || limit < 1 {
			api.WriteBadRequest(w, r, "invalid limit: must be a positive integer")
			return
		}
		filters.Limit = limit
	}

	filters.Normalize()

	ops, err := h.repo.List(r.Context(), filters)
	if err != nil {
		api.WriteInternalError(w, r, "failed to list operations")
		return
	}

	resp := make([]OperationSummaryResponse, len(ops))
	for i, op := range ops {
		resp[i] = toOperationSummary(op)
	}

	api.WriteSuccess(w, r, map[string]any{
		"data":       resp,
		"project_id": projectID,
		"total":      len(resp),
	})
}

// Get returns details for a single operation.
// GET /projects/{id}/operations/{operation_id}
func (h *OperationsHandler) Get(w http.ResponseWriter, r *http.Request) {
	projectID := chi.URLParam(r, "id")
	operationID := chi.URLParam(r, "operation_id")

	if projectID == "" {
		api.WriteBadRequest(w, r, "project ID is required")
		return
	}
	if operationID == "" {
		api.WriteBadRequest(w, r, "operation ID is required")
		return
	}

	op, err := h.repo.Get(r.Context(), operationID)
	if err != nil {
		if errors.Is(err, domain.ErrOperationNotFound) {
			api.WriteNotFound(w, r, "operation not found")
			return
		}
		api.WriteInternalError(w, r, "failed to get operation")
		return
	}

	// Verify the operation belongs to the requested project
	if op.ProjectID != projectID {
		api.WriteNotFound(w, r, "operation not found")
		return
	}

	api.WriteSuccess(w, r, map[string]any{
		"data": toOperationDetail(op),
	})
}

// toOperationSummary converts an Operation to a summary response.
func toOperationSummary(op *domain.Operation) OperationSummaryResponse {
	resp := OperationSummaryResponse{
		ID:           op.ID,
		Type:         string(op.Type),
		Status:       string(op.Status),
		StartedAt:    op.StartedAt.Format(time.RFC3339),
		DurationMs:   op.DurationMs,
		Error:        op.Error,
		StepsSummary: op.StepsSummary(),
	}

	if op.CommitSHA != "" {
		resp.CommitSHA = &op.CommitSHA
	}
	if op.ExternalRef != "" {
		resp.ExternalRef = &op.ExternalRef
	}

	return resp
}

// toOperationDetail converts an Operation to a full detail response.
func toOperationDetail(op *domain.Operation) OperationDetailResponse {
	resp := OperationDetailResponse{
		ID:          op.ID,
		ProjectID:   op.ProjectID,
		Type:        string(op.Type),
		Status:      string(op.Status),
		RequestID:   op.RequestID,
		TriggeredBy: op.TriggeredBy,
		CommitSHA:   op.CommitSHA,
		ExternalRef: op.ExternalRef,
		StartedAt:   op.StartedAt.Format(time.RFC3339),
		DurationMs:  op.DurationMs,
		Input:       op.Input,
		Output:      op.Output,
		Error:       op.Error,
		ErrorDetail: op.ErrorDetail,
	}

	if op.CompletedAt != nil {
		s := op.CompletedAt.Format(time.RFC3339)
		resp.CompletedAt = &s
	}

	if len(op.Steps) > 0 {
		resp.Steps = make([]OperationStepResponse, len(op.Steps))
		for i, step := range op.Steps {
			resp.Steps[i] = OperationStepResponse{
				Name:        step.Name,
				Status:      string(step.Status),
				StartedAt:   step.StartedAt.Format(time.RFC3339),
				DurationMs:  step.DurationMs,
				Output:      step.Output,
				Error:       step.Error,
				ErrorDetail: step.ErrorDetail,
			}
		}
	}

	return resp
}

// parseSince parses a "since" parameter as either RFC3339 time or duration.
func parseSince(s string) (time.Time, error) {
	// Try RFC3339 first
	if t, err := time.Parse(time.RFC3339, s); err == nil {
		return t, nil
	}

	// Try duration (e.g., "1h", "24h", "7d")
	d, err := time.ParseDuration(s)
	if err != nil {
		return time.Time{}, err
	}

	return time.Now().Add(-d), nil
}
