package api

import (
	"errors"
	"log/slog"
	"net/http"
	"strings"

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

	"github.com/alexei/tinyforge/internal/crypto"
	"github.com/alexei/tinyforge/internal/store"
)

// workloadNotificationRow is the JSON shape returned to clients. The
// `secret_set` boolean replaces the actual ciphertext: once stored a
// secret is write-only, mirroring how workload_env hides encrypted
// values. Rotating means submitting a new value.
type workloadNotificationRow struct {
	ID         string `json:"id"`
	WorkloadID string `json:"workload_id"`
	Name       string `json:"name"`
	URL        string `json:"url"`
	SecretSet  bool   `json:"secret_set"`
	EventTypes string `json:"event_types"`
	Enabled    bool   `json:"enabled"`
	SortOrder  int    `json:"sort_order"`
	CreatedAt  string `json:"created_at"`
	UpdatedAt  string `json:"updated_at"`
}

func toWorkloadNotificationRow(n store.WorkloadNotification) workloadNotificationRow {
	return workloadNotificationRow{
		ID:         n.ID,
		WorkloadID: n.WorkloadID,
		Name:       n.Name,
		URL:        n.URL,
		SecretSet:  n.Secret != "",
		EventTypes: n.EventTypes,
		Enabled:    n.Enabled,
		SortOrder:  n.SortOrder,
		CreatedAt:  n.CreatedAt,
		UpdatedAt:  n.UpdatedAt,
	}
}

func (s *Server) listWorkloadNotifications(w http.ResponseWriter, r *http.Request) {
	id := chi.URLParam(r, "id")
	if _, err := s.store.GetWorkloadByID(id); err != nil {
		if errors.Is(err, store.ErrNotFound) {
			respondNotFound(w, "workload")
			return
		}
		respondError(w, http.StatusInternalServerError, "get workload")
		return
	}
	rows, err := s.store.ListWorkloadNotifications(id)
	if err != nil {
		respondError(w, http.StatusInternalServerError, "list workload notifications")
		return
	}
	out := make([]workloadNotificationRow, 0, len(rows))
	for _, n := range rows {
		out = append(out, toWorkloadNotificationRow(n))
	}
	respondJSON(w, http.StatusOK, out)
}

// workloadNotificationRequest is the POST/PUT body. Secret is the raw
// plaintext webhook signing key; the server encrypts it at rest with
// the global encryption key before INSERT. An empty Secret on UPDATE
// leaves the stored secret untouched so the operator can edit the URL
// or event filter without re-entering the secret each time.
type workloadNotificationRequest struct {
	Name       string `json:"name"`
	URL        string `json:"url"`
	Secret     string `json:"secret"`
	EventTypes string `json:"event_types"`
	Enabled    *bool  `json:"enabled"`
	SortOrder  int    `json:"sort_order"`
}

func (s *Server) createWorkloadNotification(w http.ResponseWriter, r *http.Request) {
	id := chi.URLParam(r, "id")
	if _, err := s.store.GetWorkloadByID(id); err != nil {
		if errors.Is(err, store.ErrNotFound) {
			respondNotFound(w, "workload")
			return
		}
		respondError(w, http.StatusInternalServerError, "get workload")
		return
	}
	var req workloadNotificationRequest
	if !decodeJSONStrict(w, r, &req) {
		return
	}
	req.URL = strings.TrimSpace(req.URL)
	req.Name = strings.TrimSpace(req.Name)
	if req.URL == "" {
		respondError(w, http.StatusBadRequest, "url is required")
		return
	}
	encSecret := ""
	if req.Secret != "" {
		v, err := crypto.Encrypt(s.encKey, req.Secret)
		if err != nil {
			slog.Error("workload notifications: encrypt secret", "workload", id, "error", err)
			respondError(w, http.StatusInternalServerError, "encrypt secret")
			return
		}
		encSecret = v
	}
	enabled := true
	if req.Enabled != nil {
		enabled = *req.Enabled
	}
	created, err := s.store.CreateWorkloadNotification(store.WorkloadNotification{
		WorkloadID: id,
		Name:       req.Name,
		URL:        req.URL,
		Secret:     encSecret,
		EventTypes: req.EventTypes,
		Enabled:    enabled,
		SortOrder:  req.SortOrder,
	})
	if err != nil {
		slog.Error("workload notifications: create", "workload", id, "error", err)
		respondError(w, http.StatusInternalServerError, "create workload notification")
		return
	}
	respondJSON(w, http.StatusCreated, toWorkloadNotificationRow(created))
}

func (s *Server) updateWorkloadNotification(w http.ResponseWriter, r *http.Request) {
	id := chi.URLParam(r, "id")
	nid := chi.URLParam(r, "nid")
	if _, err := s.store.GetWorkloadByID(id); err != nil {
		if errors.Is(err, store.ErrNotFound) {
			respondNotFound(w, "workload")
			return
		}
		respondError(w, http.StatusInternalServerError, "get workload")
		return
	}
	existing, err := s.store.GetWorkloadNotification(nid)
	if err != nil {
		if errors.Is(err, store.ErrNotFound) {
			respondNotFound(w, "workload_notification")
			return
		}
		respondError(w, http.StatusInternalServerError, "get workload_notification")
		return
	}
	if existing.WorkloadID != id {
		// Route mismatch — the row exists but under a different workload.
		// Return 404 rather than 403 so we don't leak the existence of
		// foreign rows to an unauthorised caller.
		respondNotFound(w, "workload_notification")
		return
	}

	var req workloadNotificationRequest
	if !decodeJSONStrict(w, r, &req) {
		return
	}
	req.URL = strings.TrimSpace(req.URL)
	req.Name = strings.TrimSpace(req.Name)
	if req.URL == "" {
		respondError(w, http.StatusBadRequest, "url is required")
		return
	}

	existing.Name = req.Name
	existing.URL = req.URL
	existing.EventTypes = req.EventTypes
	existing.SortOrder = req.SortOrder
	if req.Enabled != nil {
		existing.Enabled = *req.Enabled
	}
	// Empty Secret on UPDATE preserves the stored ciphertext — explicit
	// rotation requires sending the new plaintext. This avoids forcing
	// the operator to re-enter their secret on every URL edit.
	if req.Secret != "" {
		v, err := crypto.Encrypt(s.encKey, req.Secret)
		if err != nil {
			slog.Error("workload notifications: encrypt secret", "workload", id, "error", err)
			respondError(w, http.StatusInternalServerError, "encrypt secret")
			return
		}
		existing.Secret = v
	}

	if err := s.store.UpdateWorkloadNotification(existing); err != nil {
		if errors.Is(err, store.ErrNotFound) {
			respondNotFound(w, "workload_notification")
			return
		}
		slog.Error("workload notifications: update", "workload", id, "error", err)
		respondError(w, http.StatusInternalServerError, "update workload notification")
		return
	}
	respondJSON(w, http.StatusOK, toWorkloadNotificationRow(existing))
}

func (s *Server) deleteWorkloadNotification(w http.ResponseWriter, r *http.Request) {
	id := chi.URLParam(r, "id")
	nid := chi.URLParam(r, "nid")
	existing, err := s.store.GetWorkloadNotification(nid)
	if err != nil {
		if errors.Is(err, store.ErrNotFound) {
			respondNotFound(w, "workload_notification")
			return
		}
		respondError(w, http.StatusInternalServerError, "get workload_notification")
		return
	}
	if existing.WorkloadID != id {
		respondNotFound(w, "workload_notification")
		return
	}
	if err := s.store.DeleteWorkloadNotification(nid); err != nil {
		if errors.Is(err, store.ErrNotFound) {
			respondNotFound(w, "workload_notification")
			return
		}
		slog.Error("workload notifications: delete", "workload", id, "error", err)
		respondError(w, http.StatusInternalServerError, "delete workload notification")
		return
	}
	respondJSON(w, http.StatusOK, map[string]any{"success": true})
}
