package server

import (
	"encoding/json"
	"errors"
	"io"
	"net/http"

	"github.com/go-chi/chi/v5"

	"github.com/operator/command-center/internal/webhooks"
)

// WebhookDeps bundles the Phase 4 ingress dependencies passed in via
// server.Options. nil disables both the public /webhook/* surface and the
// authenticated /api/webhooks CRUD.
type WebhookDeps struct {
	Registry *webhooks.Registry

	// Handlers is keyed by handler_type ("autobrr", "qbit", "crossseed",
	// "generic"). main.go constructs the map.
	Handlers map[string]webhooks.Handler
}

// handleWebhookIngress is the single public ingress endpoint. The URL path
// carries the expected handler type; the endpoint id is the path tail. The
// handler validates HMAC/token, looks up the endpoint, dispatches.
func (s *Server) handleWebhookIngress(w http.ResponseWriter, r *http.Request) {
	if s.webhooks == nil {
		http.Error(w, "webhooks not configured", http.StatusServiceUnavailable)
		return
	}
	urlType := chi.URLParam(r, "type")
	endpointID := chi.URLParam(r, "id")
	if urlType == "" || endpointID == "" {
		http.Error(w, "missing type or id in URL", http.StatusBadRequest)
		return
	}

	body, err := io.ReadAll(io.LimitReader(r.Body, 8<<20))
	if err != nil {
		http.Error(w, "body read failed", http.StatusBadRequest)
		return
	}
	r.Body.Close()

	ep, err := s.webhooks.Registry.Get(r.Context(), endpointID)
	if err != nil {
		// Don't leak whether an endpoint id exists — return 401 either way.
		http.Error(w, "unauthorized", http.StatusUnauthorized)
		return
	}
	if !ep.Enabled {
		http.Error(w, "unauthorized", http.StatusUnauthorized)
		return
	}
	if ep.HandlerType != urlType {
		// URL type and stored handler_type must match; mismatch is a misconfiguration.
		http.Error(w, "handler type mismatch", http.StatusBadRequest)
		return
	}
	secret, err := s.webhooks.Registry.LookupToken(r.Context(), endpointID)
	if err != nil {
		s.logger.Warn().Err(err).Str("endpoint", endpointID).Msg("webhook token lookup failed")
		http.Error(w, "unauthorized", http.StatusUnauthorized)
		return
	}
	if ok, _ := webhooks.Validate(r, body, secret); !ok {
		s.logger.Warn().Str("endpoint", endpointID).Str("ua", r.Header.Get("User-Agent")).Msg("webhook signature rejected")
		http.Error(w, "unauthorized", http.StatusUnauthorized)
		return
	}

	handler, ok := s.webhooks.Handlers[ep.HandlerType]
	if !ok {
		http.Error(w, "no handler registered for type", http.StatusInternalServerError)
		return
	}
	if err := handler.Handle(r.Context(), r, body, endpointID); err != nil {
		http.Error(w, err.Error(), http.StatusBadRequest)
		return
	}
	s.webhooks.Registry.MarkCalled(r.Context(), endpointID)
	writeJSON(w, http.StatusOK, map[string]any{"received": true})
}

// Authenticated CRUD endpoints under /api/webhooks. Middleware enforces auth.

type webhookDTO struct {
	ID            string  `json:"id"`
	Name          string  `json:"name"`
	HandlerType   string  `json:"handler_type"`
	Enabled       bool    `json:"enabled"`
	HandlerConfig string  `json:"handler_config_json,omitempty"`
	CreatedAt     int64   `json:"created_at"`
	LastCalledAt  *int64  `json:"last_called_at,omitempty"`
}

func (s *Server) handleListWebhooks(w http.ResponseWriter, r *http.Request) {
	if s.webhooks == nil {
		http.Error(w, "webhooks not configured", http.StatusServiceUnavailable)
		return
	}
	rows, err := s.webhooks.Registry.List(r.Context())
	if err != nil {
		http.Error(w, err.Error(), http.StatusInternalServerError)
		return
	}
	out := []webhookDTO{}
	for _, r := range rows {
		out = append(out, webhookDTO{
			ID:            r.ID,
			Name:          r.Name,
			HandlerType:   r.HandlerType,
			Enabled:       r.Enabled,
			HandlerConfig: r.HandlerConfigJSON,
			CreatedAt:     r.CreatedAt,
			LastCalledAt:  r.LastCalledAt,
		})
	}
	writeJSON(w, http.StatusOK, map[string]any{"count": len(out), "webhooks": out})
}

func (s *Server) handleCreateWebhook(w http.ResponseWriter, r *http.Request) {
	if s.webhooks == nil {
		http.Error(w, "webhooks not configured", http.StatusServiceUnavailable)
		return
	}
	var req webhooks.CreateRequest
	if err := json.NewDecoder(r.Body).Decode(&req); err != nil {
		http.Error(w, err.Error(), http.StatusBadRequest)
		return
	}
	resp, err := s.webhooks.Registry.Create(r.Context(), req)
	if err != nil {
		http.Error(w, err.Error(), http.StatusBadRequest)
		return
	}
	if s.auth != nil && s.auth.Audit != nil {
		s.auth.Audit.Write(r.Context(), r, "operator", "create_webhook",
			"webhook", resp.ID, map[string]any{"handler_type": req.HandlerType})
	}
	writeJSON(w, http.StatusCreated, resp)
}

func (s *Server) handlePatchWebhook(w http.ResponseWriter, r *http.Request) {
	if s.webhooks == nil {
		http.Error(w, "webhooks not configured", http.StatusServiceUnavailable)
		return
	}
	id := chi.URLParam(r, "id")
	var req webhooks.PatchRequest
	if err := json.NewDecoder(r.Body).Decode(&req); err != nil {
		http.Error(w, err.Error(), http.StatusBadRequest)
		return
	}
	if err := s.webhooks.Registry.Patch(r.Context(), id, req); err != nil {
		http.Error(w, err.Error(), http.StatusBadRequest)
		return
	}
	if s.auth != nil && s.auth.Audit != nil {
		s.auth.Audit.Write(r.Context(), r, "operator", "patch_webhook", "webhook", id, nil)
	}
	writeJSON(w, http.StatusOK, map[string]any{"ok": true})
}

func (s *Server) handleDeleteWebhook(w http.ResponseWriter, r *http.Request) {
	if s.webhooks == nil {
		http.Error(w, "webhooks not configured", http.StatusServiceUnavailable)
		return
	}
	id := chi.URLParam(r, "id")
	if err := s.webhooks.Registry.Delete(r.Context(), id); err != nil {
		if errors.Is(err, webhooks.ErrEndpointNotFound) {
			http.Error(w, "not found", http.StatusNotFound)
			return
		}
		http.Error(w, err.Error(), http.StatusInternalServerError)
		return
	}
	if s.auth != nil && s.auth.Audit != nil {
		s.auth.Audit.Write(r.Context(), r, "operator", "delete_webhook", "webhook", id, nil)
	}
	writeJSON(w, http.StatusOK, map[string]any{"ok": true})
}

func (s *Server) handleRotateWebhook(w http.ResponseWriter, r *http.Request) {
	if s.webhooks == nil {
		http.Error(w, "webhooks not configured", http.StatusServiceUnavailable)
		return
	}
	id := chi.URLParam(r, "id")
	token, err := s.webhooks.Registry.RotateToken(r.Context(), id)
	if err != nil {
		http.Error(w, err.Error(), http.StatusBadRequest)
		return
	}
	if s.auth != nil && s.auth.Audit != nil {
		s.auth.Audit.Write(r.Context(), r, "operator", "rotate_webhook", "webhook", id, nil)
	}
	writeJSON(w, http.StatusOK, map[string]any{"id": id, "token": token})
}
