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"
)

// AuditHandler handles audit log endpoints.
type AuditHandler struct {
	auditLogger port.AuditLogger
}

// NewAuditHandler creates a new audit handler.
func NewAuditHandler(auditLogger port.AuditLogger) *AuditHandler {
	return &AuditHandler{auditLogger: auditLogger}
}

// Mount registers the audit routes.
func (h *AuditHandler) Mount(r api.Router) {
	r.Route("/audit-log", func(r chi.Router) {
		// All audit endpoints require authentication with audit:read scope
		r.With(auth.RequireScope(auth.ScopeAuditRead, auth.ScopeAdmin)).Get("/", h.List)
		r.With(auth.RequireScope(auth.ScopeAuditRead, auth.ScopeAdmin)).Get("/{command_id}", h.Get)
	})
}

// AuditLogResponse is the JSON response for an audit log entry.
type AuditLogResponse struct {
	ID              string  `json:"id"`
	APIKeyID        string  `json:"api_key_id"`
	CommandID       string  `json:"command_id"`
	ProjectID       string  `json:"project_id"`
	CommandType     string  `json:"command_type"`
	Args            string  `json:"args,omitempty"`
	ClientIP        string  `json:"client_ip,omitempty"`
	UserAgent       string  `json:"user_agent,omitempty"`
	StartedAt       string  `json:"started_at"`
	CompletedAt     *string `json:"completed_at,omitempty"`
	ExitCode        *int    `json:"exit_code,omitempty"`
	DurationMs      *int64  `json:"duration_ms,omitempty"`
	Status          string  `json:"status"`
	ErrorMessage    string  `json:"error_message,omitempty"`
	OutputSizeBytes int64   `json:"output_size_bytes"`
	CreatedAt       string  `json:"created_at"`
}

// auditLogToResponse converts an AuditLogEntry to a JSON response.
func auditLogToResponse(entry *domain.AuditLogEntry) AuditLogResponse {
	resp := AuditLogResponse{
		ID:              entry.ID,
		APIKeyID:        entry.APIKeyID,
		CommandID:       entry.CommandID,
		ProjectID:       entry.ProjectID,
		CommandType:     string(entry.CommandType),
		Args:            entry.Args,
		ClientIP:        entry.ClientIP,
		UserAgent:       entry.UserAgent,
		StartedAt:       entry.StartedAt.Format(time.RFC3339),
		Status:          string(entry.Status),
		ErrorMessage:    entry.ErrorMessage,
		OutputSizeBytes: entry.OutputSizeBytes,
		CreatedAt:       entry.CreatedAt.Format(time.RFC3339),
	}

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

	if entry.ExitCode != nil {
		resp.ExitCode = entry.ExitCode
	}

	if entry.DurationMs != nil {
		resp.DurationMs = entry.DurationMs
	}

	return resp
}

// ListAuditLogResponse is the JSON response for listing audit logs.
type ListAuditLogResponse struct {
	Entries []AuditLogResponse `json:"entries"`
	Total   int                `json:"total"`
	Limit   int                `json:"limit"`
	Offset  int                `json:"offset"`
}

// List returns audit log entries with optional filters.
// GET /audit-log
// Query parameters:
//   - project: filter by project ID
//   - api_key: filter by API key ID
//   - command_type: filter by command type (claude, shell, git)
//   - status: filter by status (running, success, error, cancelled)
//   - start: filter by start time (RFC3339 format)
//   - end: filter by end time (RFC3339 format)
//   - limit: maximum number of entries (default 100, max 1000)
//   - offset: number of entries to skip (for pagination)
func (h *AuditHandler) List(w http.ResponseWriter, r *http.Request) {
	filters := domain.DefaultAuditFilters()

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

	// Parse api_key filter
	if apiKey := r.URL.Query().Get("api_key"); apiKey != "" {
		filters.APIKeyID = apiKey
	}

	// Parse command_type filter
	if cmdType := r.URL.Query().Get("command_type"); cmdType != "" {
		ct := domain.CommandType(cmdType)
		switch ct {
		case domain.CommandTypeClaude, domain.CommandTypeShell, domain.CommandTypeGit:
			filters.CommandType = ct
		default:
			api.WriteBadRequest(w, r, "invalid command_type: must be claude, shell, or git")
			return
		}
	}

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

	// Parse start time filter
	if startStr := r.URL.Query().Get("start"); startStr != "" {
		start, err := time.Parse(time.RFC3339, startStr)
		if err != nil {
			api.WriteBadRequest(w, r, "invalid start time: must be RFC3339 format")
			return
		}
		filters.StartTime = &start
	}

	// Parse end time filter
	if endStr := r.URL.Query().Get("end"); endStr != "" {
		end, err := time.Parse(time.RFC3339, endStr)
		if err != nil {
			api.WriteBadRequest(w, r, "invalid end time: must be RFC3339 format")
			return
		}
		filters.EndTime = &end
	}

	// 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
		}
		if limit > 1000 {
			limit = 1000 // Cap at 1000
		}
		filters.Limit = limit
	}

	// Parse offset
	if offsetStr := r.URL.Query().Get("offset"); offsetStr != "" {
		offset, err := strconv.Atoi(offsetStr)
		if err != nil || offset < 0 {
			api.WriteBadRequest(w, r, "invalid offset: must be a non-negative integer")
			return
		}
		filters.Offset = offset
	}

	entries, err := h.auditLogger.List(r.Context(), filters)
	if err != nil {
		api.WriteInternalError(w, r, "Failed to list audit logs")
		return
	}

	resp := make([]AuditLogResponse, len(entries))
	for i, entry := range entries {
		resp[i] = auditLogToResponse(&entry)
	}

	api.WriteSuccess(w, r, ListAuditLogResponse{
		Entries: resp,
		Total:   len(resp),
		Limit:   filters.Limit,
		Offset:  filters.Offset,
	})
}

// Get returns a single audit log entry by command ID.
// GET /audit-log/{command_id}
func (h *AuditHandler) Get(w http.ResponseWriter, r *http.Request) {
	commandID := chi.URLParam(r, "command_id")
	if commandID == "" {
		api.WriteBadRequest(w, r, "command_id is required")
		return
	}

	entry, err := h.auditLogger.Get(r.Context(), commandID)
	if err != nil {
		if errors.Is(err, domain.ErrAuditNotFound) {
			api.WriteNotFound(w, r, "audit log entry not found")
			return
		}
		api.WriteInternalError(w, r, "Failed to get audit log entry")
		return
	}

	api.WriteSuccess(w, r, auditLogToResponse(entry))
}
