package contacts

import (
	"errors"
	"net/http"
	"strconv"
	"strings"

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

	"github.com/ultisuite/ulti-backend/internal/api/apiresponse"
	"github.com/ultisuite/ulti-backend/internal/api/apivalidate"
	"github.com/ultisuite/ulti-backend/internal/api/middleware"
	"github.com/ultisuite/ulti-backend/internal/contacts/discovery"
	"github.com/ultisuite/ulti-backend/internal/llm"
	"github.com/ultisuite/ulti-backend/internal/nextcloud"
	"github.com/ultisuite/ulti-backend/internal/websearch"
	"github.com/ultisuite/ulti-backend/internal/permission"
)

func (h *Handler) discoveryRoutes() chi.Router {
	r := chi.NewRouter()
	read := middleware.RequirePermission(permission.ResourceContacts, permission.LevelRead)
	write := middleware.RequirePermission(permission.ResourceContacts, permission.LevelWrite)

	r.With(write).Post("/scan", h.StartDiscoveryScan)
	r.With(write).Post("/scan/cancel", h.CancelDiscoveryScan)
	r.With(read).Get("/scan/active", h.GetActiveDiscoveryScan)
	r.With(read).Get("/scan/{scanID}", h.GetDiscoveryScan)
	r.With(read).Get("/other", h.ListOtherDiscoveredContacts)
	r.With(read).Get("/ignored", h.ListIgnoredDiscoveredContacts)
	r.With(read).Get("/blocked", h.ListBlockedDiscoveredContacts)
	r.With(read).Get("/suggestions", h.ListEnrichmentSuggestions)
	r.With(read).Get("/counts", h.GetDiscoveryCounts)
	r.With(write).Post("/profiles/{profileID}/add-to-book", h.AddDiscoveredProfileToBook)
	r.With(write).Post("/profiles/{profileID}/accept", h.AcceptDiscoveredProfile)
	r.With(write).Post("/profiles/{profileID}/reject", h.RejectDiscoveredProfile)
	r.With(write).Post("/profiles/{profileID}/ignore", h.IgnoreDiscoveredProfile)
	r.With(write).Post("/profiles/{profileID}/block", h.BlockDiscoveredProfile)
	r.With(write).Post("/profiles/{profileID}/enrich", h.EnrichDiscoveredProfile)
	r.With(write).Post("/suggestions/{suggestionID}/accept", h.AcceptEnrichmentSuggestion)
	r.With(write).Post("/suggestions/{suggestionID}/reject", h.RejectEnrichmentSuggestion)
	r.With(read).Get("/llm-settings", h.GetLLMSettings)
	r.With(write).Put("/llm-settings", h.UpdateLLMSettings)
	r.With(read).Post("/llm-models/discover", h.DiscoverLLMModels)
	r.With(read).Get("/search-settings", h.GetSearchSettings)
	r.With(write).Put("/search-settings", h.UpdateSearchSettings)
	return r
}

func (h *Handler) StartDiscoveryScan(w http.ResponseWriter, r *http.Request) {
	claims := middleware.ClaimsFromContext(r.Context())
	if h.discovery == nil {
		apiresponse.WriteError(w, r, http.StatusServiceUnavailable, "discovery_unavailable", "contact discovery unavailable", nil)
		return
	}
	ncUser, ok := h.nextcloudUser(w, r, claims)
	if !ok {
		return
	}
	bookID := r.URL.Query().Get("book_id")
	if bookID == "" {
		bookID = "contacts"
	}
	scan, err := h.discovery.StartScan(r.Context(), claims.Sub, ncUser, bookID)
	if err != nil {
		h.logger.Error("start discovery scan", "error", err)
		apivalidate.WriteInternal(w, r)
		return
	}
	apiresponse.WriteJSON(w, http.StatusAccepted, scan)
}

func (h *Handler) CancelDiscoveryScan(w http.ResponseWriter, r *http.Request) {
	claims := middleware.ClaimsFromContext(r.Context())
	if h.discovery == nil {
		apiresponse.WriteError(w, r, http.StatusServiceUnavailable, "discovery_unavailable", "contact discovery unavailable", nil)
		return
	}
	if err := h.discovery.CancelActiveScan(r.Context(), claims.Sub); err != nil {
		h.logger.Error("cancel discovery scan", "error", err)
		apivalidate.WriteInternal(w, r)
		return
	}
	w.WriteHeader(http.StatusNoContent)
}

func (h *Handler) GetActiveDiscoveryScan(w http.ResponseWriter, r *http.Request) {
	claims := middleware.ClaimsFromContext(r.Context())
	if h.discovery == nil {
		apiresponse.WriteError(w, r, http.StatusServiceUnavailable, "discovery_unavailable", "contact discovery unavailable", nil)
		return
	}
	scan, err := h.discovery.GetActiveScan(r.Context(), claims.Sub)
	if err != nil {
		h.logger.Error("get active discovery scan", "error", err)
		apivalidate.WriteInternal(w, r)
		return
	}
	if scan == nil {
		apiresponse.WriteJSON(w, http.StatusOK, map[string]any{"scan": nil})
		return
	}
	apiresponse.WriteJSON(w, http.StatusOK, map[string]any{"scan": scan})
}

func (h *Handler) GetDiscoveryScan(w http.ResponseWriter, r *http.Request) {
	claims := middleware.ClaimsFromContext(r.Context())
	scan, err := h.discovery.GetScan(r.Context(), claims.Sub, chi.URLParam(r, "scanID"))
	if err != nil {
		h.logger.Error("get discovery scan", "error", err)
		apivalidate.WriteInternal(w, r)
		return
	}
	apiresponse.WriteJSON(w, http.StatusOK, scan)
}

func (h *Handler) ListOtherDiscoveredContacts(w http.ResponseWriter, r *http.Request) {
	claims := middleware.ClaimsFromContext(r.Context())
	limit := discovery.DefaultOtherGroupsPageSize
	offset := 0
	if v := r.URL.Query().Get("limit"); v != "" {
		if n, err := strconv.Atoi(v); err == nil {
			limit = n
		}
	}
	if v := r.URL.Query().Get("offset"); v != "" {
		if n, err := strconv.Atoi(v); err == nil {
			offset = n
		}
	}
	page, err := h.discovery.ListOtherProfileGroupsPage(r.Context(), claims.Sub, limit, offset, strings.TrimSpace(r.URL.Query().Get("q")))
	if err != nil {
		h.logger.Error("list other discovered contacts", "error", err)
		apivalidate.WriteInternal(w, r)
		return
	}
	if page.Groups == nil {
		page.Groups = []discovery.ProfileGroup{}
	}
	apiresponse.WriteJSON(w, http.StatusOK, page)
}

func (h *Handler) ListIgnoredDiscoveredContacts(w http.ResponseWriter, r *http.Request) {
	claims := middleware.ClaimsFromContext(r.Context())
	profiles, err := h.discovery.ListProfilesByStatus(r.Context(), claims.Sub, discovery.ProfileIgnored)
	if err != nil {
		h.logger.Error("list ignored discovered contacts", "error", err)
		apivalidate.WriteInternal(w, r)
		return
	}
	if profiles == nil {
		profiles = []discovery.Profile{}
	}
	apiresponse.WriteJSON(w, http.StatusOK, map[string]any{"profiles": profiles})
}

func (h *Handler) ListBlockedDiscoveredContacts(w http.ResponseWriter, r *http.Request) {
	claims := middleware.ClaimsFromContext(r.Context())
	profiles, err := h.discovery.ListProfilesByStatus(r.Context(), claims.Sub, discovery.ProfileBlocked)
	if err != nil {
		h.logger.Error("list blocked discovered contacts", "error", err)
		apivalidate.WriteInternal(w, r)
		return
	}
	if profiles == nil {
		profiles = []discovery.Profile{}
	}
	apiresponse.WriteJSON(w, http.StatusOK, map[string]any{"profiles": profiles})
}

func (h *Handler) ListEnrichmentSuggestions(w http.ResponseWriter, r *http.Request) {
	claims := middleware.ClaimsFromContext(r.Context())
	enrichOnly := r.URL.Query().Get("type") == "enrich"
	suggestions, err := h.discovery.ListSuggestions(r.Context(), claims.Sub, enrichOnly)
	if err != nil {
		h.logger.Error("list enrichment suggestions", "error", err)
		apivalidate.WriteInternal(w, r)
		return
	}
	if suggestions == nil {
		suggestions = []discovery.Suggestion{}
	}
	apiresponse.WriteJSON(w, http.StatusOK, map[string]any{"suggestions": suggestions})
}

func (h *Handler) GetDiscoveryCounts(w http.ResponseWriter, r *http.Request) {
	claims := middleware.ClaimsFromContext(r.Context())
	other, suggestions, ignored, blocked, err := h.discovery.PendingCounts(r.Context(), claims.Sub)
	if err != nil {
		h.logger.Error("discovery counts", "error", err)
		apivalidate.WriteInternal(w, r)
		return
	}
	apiresponse.WriteJSON(w, http.StatusOK, map[string]int{
		"other_contacts": other,
		"suggestions":    suggestions,
		"ignored":        ignored,
		"blocked":        blocked,
	})
}

type acceptProfileRequest struct {
	ContactUID string `json:"contact_uid"`
}

type addDiscoveredToBookRequest struct {
	BookID  string            `json:"book_id"`
	Contact nextcloud.Contact `json:"contact"`
}

func (h *Handler) AddDiscoveredProfileToBook(w http.ResponseWriter, r *http.Request) {
	claims := middleware.ClaimsFromContext(r.Context())
	if h.discovery == nil {
		apiresponse.WriteError(w, r, http.StatusServiceUnavailable, "discovery_unavailable", "contact discovery unavailable", nil)
		return
	}
	ncUser, ok := h.nextcloudUser(w, r, claims)
	if !ok {
		return
	}
	var req addDiscoveredToBookRequest
	if err := apivalidate.DecodeJSON(w, r, maxRequestBody, &req); err != nil {
		return
	}
	if strings.TrimSpace(req.BookID) == "" {
		apivalidate.WriteValidationError(w, r, apivalidate.NewValidationError(apivalidate.FieldDetail{
			Field: "book_id", Message: "required",
		}))
		return
	}
	if verr := validateCreateContact(&req.Contact); verr != nil {
		apivalidate.WriteValidationError(w, r, verr)
		return
	}
	created, err := h.svc.CreateContact(r.Context(), ncUser, req.BookID, &req.Contact)
	if err != nil {
		h.writeContactServiceError(w, r, "create contact", err)
		return
	}
	uid := strings.TrimSpace(created.UID)
	if err := h.discovery.AcceptProfile(r.Context(), claims.Sub, chi.URLParam(r, "profileID"), uid); err != nil {
		h.logger.Error("accept discovered profile after create", "error", err)
		apivalidate.WriteInternal(w, r)
		return
	}
	apiresponse.WriteJSON(w, http.StatusCreated, created)
}

func (h *Handler) AcceptDiscoveredProfile(w http.ResponseWriter, r *http.Request) {
	claims := middleware.ClaimsFromContext(r.Context())
	var req acceptProfileRequest
	if r.ContentLength > 0 {
		if err := apivalidate.DecodeJSON(w, r, maxRequestBody, &req); err != nil {
			return
		}
	}
	if err := h.discovery.AcceptProfile(r.Context(), claims.Sub, chi.URLParam(r, "profileID"), req.ContactUID); err != nil {
		h.logger.Error("accept discovered profile", "error", err)
		apivalidate.WriteInternal(w, r)
		return
	}
	w.WriteHeader(http.StatusNoContent)
}

func (h *Handler) RejectDiscoveredProfile(w http.ResponseWriter, r *http.Request) {
	claims := middleware.ClaimsFromContext(r.Context())
	if err := h.discovery.RejectProfile(r.Context(), claims.Sub, chi.URLParam(r, "profileID")); err != nil {
		h.logger.Error("reject discovered profile", "error", err)
		apivalidate.WriteInternal(w, r)
		return
	}
	w.WriteHeader(http.StatusNoContent)
}

func (h *Handler) IgnoreDiscoveredProfile(w http.ResponseWriter, r *http.Request) {
	claims := middleware.ClaimsFromContext(r.Context())
	emails, err := h.discovery.IgnoreProfile(r.Context(), claims.Sub, chi.URLParam(r, "profileID"))
	if err != nil {
		h.logger.Error("ignore discovered profile", "error", err)
		apivalidate.WriteInternal(w, r)
		return
	}
	apiresponse.WriteJSON(w, http.StatusOK, map[string]any{"emails": emails})
}

func (h *Handler) EnrichDiscoveredProfile(w http.ResponseWriter, r *http.Request) {
	claims := middleware.ClaimsFromContext(r.Context())
	if h.discovery == nil {
		apiresponse.WriteError(w, r, http.StatusServiceUnavailable, "discovery_unavailable", "contact discovery unavailable", nil)
		return
	}
	result, err := h.discovery.StartProfileEnrichment(r.Context(), claims.Sub, chi.URLParam(r, "profileID"))
	if err != nil {
		switch {
		case errors.Is(err, discovery.ErrProfileNotFound):
			apiresponse.WriteError(w, r, http.StatusNotFound, "profile_not_found", err.Error(), nil)
		case errors.Is(err, discovery.ErrNoSignatures):
			apiresponse.WriteError(w, r, http.StatusBadRequest, "no_signatures", err.Error(), nil)
		case errors.Is(err, discovery.ErrAlreadyEnriched):
			apiresponse.WriteError(w, r, http.StatusConflict, "already_enriched", err.Error(), nil)
		case errors.Is(err, discovery.ErrLLMNotConfigured):
			apiresponse.WriteError(w, r, http.StatusBadRequest, "llm_not_configured", err.Error(), nil)
		case errors.Is(err, discovery.ErrProfileNotSuggested):
			apiresponse.WriteError(w, r, http.StatusConflict, "profile_not_suggested", err.Error(), nil)
		default:
			h.logger.Error("start profile enrichment", "error", err)
			apivalidate.WriteInternal(w, r)
		}
		return
	}
	apiresponse.WriteJSON(w, http.StatusAccepted, result)
}

func (h *Handler) BlockDiscoveredProfile(w http.ResponseWriter, r *http.Request) {
	claims := middleware.ClaimsFromContext(r.Context())
	emails, err := h.discovery.BlockProfile(r.Context(), claims.Sub, chi.URLParam(r, "profileID"))
	if err != nil {
		h.logger.Error("block discovered profile", "error", err)
		apivalidate.WriteInternal(w, r)
		return
	}
	apiresponse.WriteJSON(w, http.StatusOK, map[string]any{"emails": emails})
}

func (h *Handler) AcceptEnrichmentSuggestion(w http.ResponseWriter, r *http.Request) {
	claims := middleware.ClaimsFromContext(r.Context())
	sug, err := h.discovery.AcceptSuggestion(r.Context(), claims.Sub, chi.URLParam(r, "suggestionID"))
	if err != nil {
		h.logger.Error("accept enrichment suggestion", "error", err)
		apivalidate.WriteInternal(w, r)
		return
	}
	apiresponse.WriteJSON(w, http.StatusOK, sug)
}

func (h *Handler) RejectEnrichmentSuggestion(w http.ResponseWriter, r *http.Request) {
	claims := middleware.ClaimsFromContext(r.Context())
	if err := h.discovery.RejectSuggestion(r.Context(), claims.Sub, chi.URLParam(r, "suggestionID")); err != nil {
		h.logger.Error("reject enrichment suggestion", "error", err)
		apivalidate.WriteInternal(w, r)
		return
	}
	w.WriteHeader(http.StatusNoContent)
}

func (h *Handler) GetLLMSettings(w http.ResponseWriter, r *http.Request) {
	claims := middleware.ClaimsFromContext(r.Context())
	settings, err := h.discovery.GetLLMSettings(r.Context(), claims.Sub)
	if err != nil {
		h.logger.Error("get llm settings", "error", err)
		apivalidate.WriteInternal(w, r)
		return
	}
	apiresponse.WriteJSON(w, http.StatusOK, settings)
}

func (h *Handler) UpdateLLMSettings(w http.ResponseWriter, r *http.Request) {
	claims := middleware.ClaimsFromContext(r.Context())
	var settings llm.Settings
	if err := apivalidate.DecodeJSON(w, r, maxRequestBody, &settings); err != nil {
		return
	}
	updated, err := h.discovery.UpdateLLMSettings(r.Context(), claims.Sub, settings)
	if err != nil {
		h.logger.Error("update llm settings", "error", err)
		apivalidate.WriteInternal(w, r)
		return
	}
	apiresponse.WriteJSON(w, http.StatusOK, updated)
}

type discoverLLMModelsRequest struct {
	BaseURL string `json:"base_url"`
	APIKey  string `json:"api_key,omitempty"`
}

func (h *Handler) DiscoverLLMModels(w http.ResponseWriter, r *http.Request) {
	var req discoverLLMModelsRequest
	if err := apivalidate.DecodeJSON(w, r, maxRequestBody, &req); err != nil {
		return
	}
	if strings.TrimSpace(req.BaseURL) == "" {
		apivalidate.WriteValidationError(w, r, apivalidate.NewValidationError(apivalidate.FieldDetail{
			Field: "base_url", Message: "is required",
		}))
		return
	}

	client := llm.NewClient()
	models, err := client.ListModels(r.Context(), llm.Provider{
		BaseURL: req.BaseURL,
		APIKey:  req.APIKey,
	})
	if err != nil {
		h.logger.Error("discover llm models", "error", err)
		apiresponse.WriteError(w, r, http.StatusBadGateway, "llm_models_unavailable", err.Error(), nil)
		return
	}
	if models == nil {
		models = []string{}
	}
	apiresponse.WriteJSON(w, http.StatusOK, map[string]any{"models": models})
}

func (h *Handler) GetSearchSettings(w http.ResponseWriter, r *http.Request) {
	claims := middleware.ClaimsFromContext(r.Context())
	settings, err := h.discovery.GetSearchSettings(r.Context(), claims.Sub)
	if err != nil {
		h.logger.Error("get search settings", "error", err)
		apivalidate.WriteInternal(w, r)
		return
	}
	apiresponse.WriteJSON(w, http.StatusOK, settings)
}

func (h *Handler) UpdateSearchSettings(w http.ResponseWriter, r *http.Request) {
	claims := middleware.ClaimsFromContext(r.Context())
	var settings websearch.Settings
	if err := apivalidate.DecodeJSON(w, r, maxRequestBody, &settings); err != nil {
		return
	}
	updated, err := h.discovery.UpdateSearchSettings(r.Context(), claims.Sub, settings)
	if err != nil {
		h.logger.Error("update search settings", "error", err)
		apivalidate.WriteInternal(w, r)
		return
	}
	apiresponse.WriteJSON(w, http.StatusOK, updated)
}
