package handler

import (
	"encoding/json"
	"net/http"
	"net/http/httptest"
	"strings"
	"testing"

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

// testSpaceRouter creates a chi router with all routes registered using a fake repo.
func testSpaceRouter(repo store.Repository) chi.Router {
	r := chi.NewRouter()
	h := New(repo)
	h.RegisterRoutes(r)
	return r
}

func TestCreateSpace(t *testing.T) {
	repo := testutil.NewFakeRepository()
	r := testSpaceRouter(repo)

	body := `{"name": "Test Space", "description": "A test space", "isPublic": true}`
	req := httptest.NewRequest(http.MethodPost, "/api/spaces", strings.NewReader(body))
	req.Header.Set("Content-Type", "application/json")
	w := httptest.NewRecorder()
	r.ServeHTTP(w, req)

	if w.Code != http.StatusCreated {
		t.Fatalf("expected 201, got %d: %s", w.Code, w.Body.String())
	}

	var space store.Space
	if err := json.NewDecoder(w.Body).Decode(&space); err != nil {
		t.Fatalf("failed to decode response: %v", err)
	}

	if space.Name != "Test Space" {
		t.Errorf("expected name 'Test Space', got %q", space.Name)
	}
	if space.Description != "A test space" {
		t.Errorf("expected description 'A test space', got %q", space.Description)
	}
	if !space.IsPublic {
		t.Error("expected isPublic to be true")
	}
	if space.Slug == "" {
		t.Error("expected slug to be generated")
	}
}

func TestCreateSpaceMissingName(t *testing.T) {
	repo := testutil.NewFakeRepository()
	r := testSpaceRouter(repo)

	body := `{"description": "Missing name"}`
	req := httptest.NewRequest(http.MethodPost, "/api/spaces", strings.NewReader(body))
	req.Header.Set("Content-Type", "application/json")
	w := httptest.NewRecorder()
	r.ServeHTTP(w, req)

	if w.Code != http.StatusBadRequest {
		t.Fatalf("expected 400, got %d: %s", w.Code, w.Body.String())
	}
}

func TestListPublicSpaces(t *testing.T) {
	repo := testutil.NewFakeRepository()
	r := testSpaceRouter(repo)

	// Create some spaces
	space1 := &store.Space{ID: "1", Slug: "space-1", Name: "Space 1", IsPublic: true}
	space2 := &store.Space{ID: "2", Slug: "space-2", Name: "Space 2", IsPublic: false}
	space3 := &store.Space{ID: "3", Slug: "space-3", Name: "Space 3", IsPublic: true}

	repo.CreateSpace(nil, space1)
	repo.CreateSpace(nil, space2)
	repo.CreateSpace(nil, space3)

	req := httptest.NewRequest(http.MethodGet, "/api/spaces", nil)
	w := httptest.NewRecorder()
	r.ServeHTTP(w, req)

	if w.Code != http.StatusOK {
		t.Fatalf("expected 200, got %d: %s", w.Code, w.Body.String())
	}

	var spaces []*store.Space
	if err := json.NewDecoder(w.Body).Decode(&spaces); err != nil {
		t.Fatalf("failed to decode response: %v", err)
	}

	if len(spaces) != 2 {
		t.Fatalf("expected 2 spaces, got %d", len(spaces))
	}

	// Check that only public spaces are returned
	for _, space := range spaces {
		if !space.IsPublic {
			t.Errorf("expected only public spaces, got %q", space.Name)
		}
	}
}

func TestGetSpaceBySlug(t *testing.T) {
	repo := testutil.NewFakeRepository()
	r := testSpaceRouter(repo)

	space := &store.Space{ID: "1", Slug: "test-space", Name: "Test Space", IsPublic: true}
	repo.CreateSpace(nil, space)

	req := httptest.NewRequest(http.MethodGet, "/api/spaces/test-space", nil)
	w := httptest.NewRecorder()
	r.ServeHTTP(w, req)

	if w.Code != http.StatusOK {
		t.Fatalf("expected 200, got %d: %s", w.Code, w.Body.String())
	}

	var retrievedSpace store.Space
	if err := json.NewDecoder(w.Body).Decode(&retrievedSpace); err != nil {
		t.Fatalf("failed to decode response: %v", err)
	}

	if retrievedSpace.ID != space.ID {
		t.Errorf("expected ID %q, got %q", space.ID, retrievedSpace.ID)
	}
	if retrievedSpace.Name != space.Name {
		t.Errorf("expected name %q, got %q", space.Name, retrievedSpace.Name)
	}
}

func TestGetSpaceBySlugNotFound(t *testing.T) {
	repo := testutil.NewFakeRepository()
	r := testSpaceRouter(repo)

	req := httptest.NewRequest(http.MethodGet, "/api/spaces/nonexistent", nil)
	w := httptest.NewRecorder()
	r.ServeHTTP(w, req)

	if w.Code != http.StatusNotFound {
		t.Fatalf("expected 404, got %d: %s", w.Code, w.Body.String())
	}
}

func TestUpdateSpace(t *testing.T) {
	repo := testutil.NewFakeRepository()
	r := testSpaceRouter(repo)

	space := &store.Space{ID: "1", Slug: "test-space", Name: "Original Name", IsPublic: true}
	repo.CreateSpace(nil, space)

	body := `{"name": "Updated Name", "isPublic": false}`
	req := httptest.NewRequest(http.MethodPatch, "/api/spaces/test-space", strings.NewReader(body))
	req.Header.Set("Content-Type", "application/json")
	w := httptest.NewRecorder()
	r.ServeHTTP(w, req)

	if w.Code != http.StatusOK {
		t.Fatalf("expected 200, got %d: %s", w.Code, w.Body.String())
	}

	var updatedSpace store.Space
	if err := json.NewDecoder(w.Body).Decode(&updatedSpace); err != nil {
		t.Fatalf("failed to decode response: %v", err)
	}

	if updatedSpace.Name != "Updated Name" {
		t.Errorf("expected name 'Updated Name', got %q", updatedSpace.Name)
	}
	if updatedSpace.IsPublic != false {
		t.Error("expected isPublic to be false")
	}
}

func TestDeleteSpace(t *testing.T) {
	repo := testutil.NewFakeRepository()
	r := testSpaceRouter(repo)

	space := &store.Space{ID: "1", Slug: "test-space", Name: "Test Space", IsPublic: true}
	repo.CreateSpace(nil, space)

	req := httptest.NewRequest(http.MethodDelete, "/api/spaces/test-space", nil)
	w := httptest.NewRecorder()
	r.ServeHTTP(w, req)

	if w.Code != http.StatusNoContent {
		t.Fatalf("expected 204, got %d: %s", w.Code, w.Body.String())
	}

	// Verify space was deleted
	_, err := repo.GetSpaceBySlug(nil, "test-space")
	if err == nil {
		t.Error("expected space to be deleted")
	}
}

func TestCreatePhoto(t *testing.T) {
	repo := testutil.NewFakeRepository()
	r := testSpaceRouter(repo)

	space := &store.Space{ID: "1", Slug: "test-space", Name: "Test Space", IsPublic: true}
	repo.CreateSpace(nil, space)

	body := `{"url": "https://example.com/photo.jpg", "thumbnailUrl": "https://example.com/thumb.jpg", "faceCount": 2}`
	req := httptest.NewRequest(http.MethodPost, "/api/spaces/test-space/photos", strings.NewReader(body))
	req.Header.Set("Content-Type", "application/json")
	w := httptest.NewRecorder()
	r.ServeHTTP(w, req)

	if w.Code != http.StatusCreated {
		t.Fatalf("expected 201, got %d: %s", w.Code, w.Body.String())
	}

	var photo store.Photo
	if err := json.NewDecoder(w.Body).Decode(&photo); err != nil {
		t.Fatalf("failed to decode response: %v", err)
	}

	if photo.URL != "https://example.com/photo.jpg" {
		t.Errorf("expected URL 'https://example.com/photo.jpg', got %q", photo.URL)
	}
	if photo.ThumbnailURL != "https://example.com/thumb.jpg" {
		t.Errorf("expected thumbnail URL 'https://example.com/thumb.jpg', got %q", photo.ThumbnailURL)
	}
	if photo.FaceCount != 2 {
		t.Errorf("expected face count 2, got %d", photo.FaceCount)
	}
}

func TestListPhotos(t *testing.T) {
	repo := testutil.NewFakeRepository()
	r := testSpaceRouter(repo)

	space := &store.Space{ID: "1", Slug: "test-space", Name: "Test Space", IsPublic: true}
	repo.CreateSpace(nil, space)

	// Create some photos
	photo1 := &store.Photo{ID: "1", SpaceID: "1", URL: "https://example.com/photo1.jpg"}
	photo2 := &store.Photo{ID: "2", SpaceID: "1", URL: "https://example.com/photo2.jpg"}
	photo3 := &store.Photo{ID: "3", SpaceID: "2", URL: "https://example.com/photo3.jpg"}

	repo.CreatePhoto(nil, photo1)
	repo.CreatePhoto(nil, photo2)
	repo.CreatePhoto(nil, photo3)

	req := httptest.NewRequest(http.MethodGet, "/api/spaces/test-space/photos", nil)
	w := httptest.NewRecorder()
	r.ServeHTTP(w, req)

	if w.Code != http.StatusOK {
		t.Fatalf("expected 200, got %d: %s", w.Code, w.Body.String())
	}

	var photos []*store.Photo
	if err := json.NewDecoder(w.Body).Decode(&photos); err != nil {
		t.Fatalf("failed to decode response: %v", err)
	}

	if len(photos) != 2 {
		t.Fatalf("expected 2 photos, got %d", len(photos))
	}
}

func TestGuestFaceSearch(t *testing.T) {
	repo := testutil.NewFakeRepository()
	r := testSpaceRouter(repo)

	space := &store.Space{ID: "1", Slug: "test-space", Name: "Test Space", IsPublic: true}
	repo.CreateSpace(nil, space)

	body := `{"imageUrl": "https://example.com/test.jpg"}`
	req := httptest.NewRequest(http.MethodPost, "/api/spaces/test-space/face-search/guest", strings.NewReader(body))
	req.Header.Set("Content-Type", "application/json")
	req.RemoteAddr = "192.168.1.1:12345"
	w := httptest.NewRecorder()
	r.ServeHTTP(w, req)

	if w.Code != http.StatusOK {
		t.Fatalf("expected 200, got %d: %s", w.Code, w.Body.String())
	}

	var response map[string]interface{}
	if err := json.NewDecoder(w.Body).Decode(&response); err != nil {
		t.Fatalf("failed to decode response: %v", err)
	}

	if response["status"] != "success" {
		t.Errorf("expected status 'success', got %q", response["status"])
	}
}