package handler

import (
	"encoding/base64"
	"encoding/json"
	"io"
	"net/http"
	"strconv"
	"strings"

	"github.com/elfoundation/hatch/internal/store"
	"github.com/go-chi/chi/v5"
)

// RegisterSpaceRoutes mounts the space management routes on the given router.
func (h *Handler) RegisterSpaceRoutes(r chi.Router) {
	r.Route("/api/spaces", func(r chi.Router) {
		// Public endpoints
		r.Post("/", h.createSpace)
		r.Get("/", h.listPublicSpaces)
		r.Get("/{slug}", h.getSpaceBySlug)
		r.Patch("/{slug}", h.updateSpace)
		r.Delete("/{slug}", h.deleteSpace)

		// Photo management
		r.Post("/{slug}/photos", h.createPhoto)
		r.Get("/{slug}/photos", h.listPhotos)

		// Face search endpoints
		r.Post("/{slug}/face-search/guest", h.guestFaceSearch)
		r.Post("/{slug}/face-search", h.authenticatedFaceSearch)
	})
}

// createSpace handles POST /api/spaces
func (h *Handler) createSpace(w http.ResponseWriter, r *http.Request) {
	var space store.Space
	if err := json.NewDecoder(r.Body).Decode(&space); err != nil {
		writeError(w, http.StatusBadRequest, "invalid JSON body: "+err.Error())
		return
	}

	// Generate slug if not provided
	if space.Slug == "" {
		space.Slug = generateSlug(space.Name)
	}

	// Validate required fields
	if space.Name == "" {
		writeError(w, http.StatusBadRequest, "name is required")
		return
	}

	ctx := r.Context()
	createdSpace, err := h.Repo.CreateSpace(ctx, &space)
	if err != nil {
		writeError(w, http.StatusInternalServerError, "failed to create space: "+err.Error())
		return
	}

	w.Header().Set("Content-Type", "application/json")
	w.WriteHeader(http.StatusCreated)
	json.NewEncoder(w).Encode(createdSpace)
}

// listPublicSpaces handles GET /api/spaces
func (h *Handler) listPublicSpaces(w http.ResponseWriter, r *http.Request) {
	ctx := r.Context()
	spaces, err := h.Repo.ListPublicSpaces(ctx)
	if err != nil {
		writeError(w, http.StatusInternalServerError, "failed to list spaces")
		return
	}

	w.Header().Set("Content-Type", "application/json")
	json.NewEncoder(w).Encode(spaces)
}

// getSpaceBySlug handles GET /api/spaces/{slug}
func (h *Handler) getSpaceBySlug(w http.ResponseWriter, r *http.Request) {
	slug := chi.URLParam(r, "slug")
	if slug == "" {
		writeError(w, http.StatusBadRequest, "missing slug")
		return
	}

	ctx := r.Context()
	space, err := h.Repo.GetSpaceBySlug(ctx, slug)
	if err != nil {
		writeError(w, http.StatusNotFound, "space not found")
		return
	}

	w.Header().Set("Content-Type", "application/json")
	json.NewEncoder(w).Encode(space)
}

// updateSpace handles PATCH /api/spaces/{slug}
func (h *Handler) updateSpace(w http.ResponseWriter, r *http.Request) {
	slug := chi.URLParam(r, "slug")
	if slug == "" {
		writeError(w, http.StatusBadRequest, "missing slug")
		return
	}

	ctx := r.Context()
	existingSpace, err := h.Repo.GetSpaceBySlug(ctx, slug)
	if err != nil {
		writeError(w, http.StatusNotFound, "space not found")
		return
	}

	// Update fields from request body
	var updates store.Space
	if err := json.NewDecoder(r.Body).Decode(&updates); err != nil {
		writeError(w, http.StatusBadRequest, "invalid JSON body: "+err.Error())
		return
	}

	// Apply updates
	if updates.Name != "" {
		existingSpace.Name = updates.Name
	}
	if updates.Description != "" {
		existingSpace.Description = updates.Description
	}
	// Note: is_public is a boolean, so we need to check if it was explicitly set
	// For simplicity, we'll always update it if provided in the request
	existingSpace.IsPublic = updates.IsPublic

	updatedSpace, err := h.Repo.UpdateSpace(ctx, existingSpace)
	if err != nil {
		writeError(w, http.StatusInternalServerError, "failed to update space: "+err.Error())
		return
	}

	w.Header().Set("Content-Type", "application/json")
	json.NewEncoder(w).Encode(updatedSpace)
}

// deleteSpace handles DELETE /api/spaces/{slug}
func (h *Handler) deleteSpace(w http.ResponseWriter, r *http.Request) {
	slug := chi.URLParam(r, "slug")
	if slug == "" {
		writeError(w, http.StatusBadRequest, "missing slug")
		return
	}

	ctx := r.Context()
	space, err := h.Repo.GetSpaceBySlug(ctx, slug)
	if err != nil {
		writeError(w, http.StatusNotFound, "space not found")
		return
	}

	if err := h.Repo.DeleteSpace(ctx, space.ID); err != nil {
		writeError(w, http.StatusInternalServerError, "failed to delete space: "+err.Error())
		return
	}

	w.WriteHeader(http.StatusNoContent)
}

// createPhoto handles POST /api/spaces/{slug}/photos
func (h *Handler) createPhoto(w http.ResponseWriter, r *http.Request) {
	slug := chi.URLParam(r, "slug")
	if slug == "" {
		writeError(w, http.StatusBadRequest, "missing slug")
		return
	}

	ctx := r.Context()
	space, err := h.Repo.GetSpaceBySlug(ctx, slug)
	if err != nil {
		writeError(w, http.StatusNotFound, "space not found")
		return
	}

	var photo store.Photo
	if err := json.NewDecoder(r.Body).Decode(&photo); err != nil {
		writeError(w, http.StatusBadRequest, "invalid JSON body: "+err.Error())
		return
	}

	// Validate required fields
	if photo.URL == "" {
		writeError(w, http.StatusBadRequest, "url is required")
		return
	}

	photo.SpaceID = space.ID
	createdPhoto, err := h.Repo.CreatePhoto(ctx, &photo)
	if err != nil {
		writeError(w, http.StatusInternalServerError, "failed to create photo: "+err.Error())
		return
	}

	w.Header().Set("Content-Type", "application/json")
	w.WriteHeader(http.StatusCreated)
	json.NewEncoder(w).Encode(createdPhoto)
}

// listPhotos handles GET /api/spaces/{slug}/photos
func (h *Handler) listPhotos(w http.ResponseWriter, r *http.Request) {
	slug := chi.URLParam(r, "slug")
	if slug == "" {
		writeError(w, http.StatusBadRequest, "missing slug")
		return
	}

	ctx := r.Context()
	space, err := h.Repo.GetSpaceBySlug(ctx, slug)
	if err != nil {
		writeError(w, http.StatusNotFound, "space not found")
		return
	}

	// Parse limit parameter
	limitStr := r.URL.Query().Get("limit")
	limit := 100
	if limitStr != "" {
		if l, err := strconv.Atoi(limitStr); err == nil && l > 0 {
			limit = l
		}
	}

	photos, err := h.Repo.ListPhotosBySpace(ctx, space.ID, limit)
	if err != nil {
		writeError(w, http.StatusInternalServerError, "failed to list photos")
		return
	}

	w.Header().Set("Content-Type", "application/json")
	json.NewEncoder(w).Encode(photos)
}

// guestFaceSearch handles POST /api/spaces/{slug}/face-search/guest
func (h *Handler) guestFaceSearch(w http.ResponseWriter, r *http.Request) {
	slug := chi.URLParam(r, "slug")
	if slug == "" {
		writeError(w, http.StatusBadRequest, "missing slug")
		return
	}

	ctx := r.Context()
	space, err := h.Repo.GetSpaceBySlug(ctx, slug)
	if err != nil {
		writeError(w, http.StatusNotFound, "space not found")
		return
	}

	// Check rate limit (20 requests per 15 minutes per IP)
	ipAddress := getClientIP(r)
	allowed, err := h.Repo.CheckRateLimit(ctx, ipAddress, space.ID, 20, 15)
	if err != nil {
		writeError(w, http.StatusInternalServerError, "failed to check rate limit")
		return
	}
	if !allowed {
		writeError(w, http.StatusTooManyRequests, "rate limit exceeded")
		return
	}

	// Increment rate limit
	if err := h.Repo.IncrementRateLimit(ctx, ipAddress, space.ID, 15); err != nil {
		writeError(w, http.StatusInternalServerError, "failed to increment rate limit")
		return
	}

	// Parse request body (should contain image data or URL)
	var request struct {
		ImageURL  string `json:"imageUrl,omitempty"`
		ImageData string `json:"imageData,omitempty"`
	}
	if err := json.NewDecoder(r.Body).Decode(&request); err != nil {
		writeError(w, http.StatusBadRequest, "invalid JSON body: "+err.Error())
		return
	}

	// Check if face service is available
	if h.FaceService == nil {
		writeError(w, http.StatusInternalServerError, "face search service not configured")
		return
	}

	// Convert base64 image data to bytes
	var imageBytes []byte
	if request.ImageData != "" {
		// Decode base64 image data
		imageBytes, err = base64.StdEncoding.DecodeString(request.ImageData)
		if err != nil {
			writeError(w, http.StatusBadRequest, "invalid image data")
			return
		}
	} else if request.ImageURL != "" {
		// Download image from URL
		resp, err := http.Get(request.ImageURL)
		if err != nil {
			writeError(w, http.StatusBadRequest, "failed to download image")
			return
		}
		defer resp.Body.Close()

		imageBytes, err = io.ReadAll(resp.Body)
		if err != nil {
			writeError(w, http.StatusInternalServerError, "failed to read image")
			return
		}
	} else {
		writeError(w, http.StatusBadRequest, "image data or URL required")
		return
	}

	// Validate image
	if err := h.FaceService.ValidateImage(imageBytes); err != nil {
		writeError(w, http.StatusBadRequest, err.Error())
		return
	}

	// Search faces
	result, err := h.FaceService.SearchFaces(ctx, space.ID, imageBytes, 10)
	if err != nil {
		writeError(w, http.StatusInternalServerError, "face search failed: "+err.Error())
		return
	}

	// Format response
	response := map[string]interface{}{
		"status":     "success",
		"matches":    result.Matches,
		"photoCount": result.PhotoCount,
		"searchTime": result.SearchTime.Milliseconds(),
	}

	w.Header().Set("Content-Type", "application/json")
	json.NewEncoder(w).Encode(response)
}

// authenticatedFaceSearch handles POST /api/spaces/{slug}/face-search
func (h *Handler) authenticatedFaceSearch(w http.ResponseWriter, r *http.Request) {
	// For now, delegate to guest face search
	h.guestFaceSearch(w, r)
}

// Helper functions

func generateSlug(name string) string {
	// Convert to lowercase, replace spaces with hyphens, remove non-alphanumeric characters
	slug := strings.ToLower(name)
	slug = strings.ReplaceAll(slug, " ", "-")
	slug = strings.ReplaceAll(slug, "_", "-")
	// Remove non-alphanumeric characters except hyphens
	var result strings.Builder
	for _, c := range slug {
		if (c >= 'a' && c <= 'z') || (c >= '0' && c <= '9') || c == '-' {
			result.WriteRune(c)
		}
	}
	return result.String()
}

func getClientIP(r *http.Request) string {
	// Check X-Forwarded-For header first
	xForwardedFor := r.Header.Get("X-Forwarded-For")
	if xForwardedFor != "" {
		// X-Forwarded-For may contain multiple IPs, take the first one
		parts := strings.Split(xForwardedFor, ",")
		return strings.TrimSpace(parts[0])
	}

	// Check X-Real-IP header
	xRealIP := r.Header.Get("X-Real-IP")
	if xRealIP != "" {
		return xRealIP
	}

	// Fall back to RemoteAddr
	parts := strings.Split(r.RemoteAddr, ":")
	if len(parts) > 1 {
		return strings.Join(parts[:len(parts)-1], ":")
	}
	return r.RemoteAddr
}