package handlers

import (
	"bytes"
	"context"
	"encoding/json"
	"errors"
	"net/http"
	"net/http/httptest"
	"strings"
	"testing"

	"goyco/internal/database"
	"goyco/internal/middleware"
	"goyco/internal/repositories"
	"goyco/internal/services"
	"goyco/internal/testutils"

	"github.com/go-chi/chi/v5"
	"github.com/jackc/pgconn"
	"gorm.io/gorm"
)

func decodeHandlerResponse(t *testing.T, rr *httptest.ResponseRecorder) map[string]any {
	t.Helper()
	var payload map[string]any
	if err := json.Unmarshal(rr.Body.Bytes(), &payload); err != nil {
		t.Fatalf("failed to decode response: %v", err)
	}
	return payload
}

func TestPostHandlerGetPostsWithVoteService(t *testing.T) {
	repo := testutils.NewPostRepositoryStub()
	repo.GetAllFn = func(limit, offset int) ([]database.Post, error) {
		return []database.Post{
			{ID: 1, Title: "Test Post 1"},
			{ID: 2, Title: "Test Post 2"},
		}, nil
	}

	voteRepo := testutils.NewMockVoteRepository()
	voteService := services.NewVoteService(voteRepo, repo, nil)
	handler := NewPostHandler(repo, nil, voteService)

	request := httptest.NewRequest(http.MethodGet, "/api/posts", nil)
	request = testutils.WithUserContext(request, middleware.UserIDKey, uint(1))
	recorder := httptest.NewRecorder()

	handler.GetPosts(recorder, request)

	testutils.AssertHTTPStatus(t, recorder, http.StatusOK)

	payload := decodeHandlerResponse(t, recorder)
	if !payload["success"].(bool) {
		t.Fatalf("expected success response, got %v", payload)
	}
}

func TestPostHandlerCreatePostWithTitleFetcher(t *testing.T) {
	repo := testutils.NewPostRepositoryStub()
	var storedPost *database.Post
	repo.CreateFn = func(post *database.Post) error {
		storedPost = post
		return nil
	}

	titleFetcher := &testutils.MockTitleFetcher{}
	titleFetcher.SetTitle("Fetched Title")

	handler := NewPostHandler(repo, titleFetcher, nil)

	request := createCreatePostRequest(`{"url":"https://example.com","content":"Test content"}`)
	request = testutils.WithUserContext(request, middleware.UserIDKey, uint(1))
	recorder := httptest.NewRecorder()

	handler.CreatePost(recorder, request)

	testutils.AssertHTTPStatus(t, recorder, http.StatusCreated)

	if storedPost == nil {
		t.Fatal("expected post to be created")
	}

	if storedPost.Title != "Fetched Title" {
		t.Errorf("expected title 'Fetched Title', got %s", storedPost.Title)
	}
}

func TestPostHandlerCreatePostTitleFetcherError(t *testing.T) {
	repo := testutils.NewPostRepositoryStub()
	titleFetcher := &testutils.MockTitleFetcher{}
	titleFetcher.SetError(services.ErrUnsupportedScheme)

	handler := NewPostHandler(repo, titleFetcher, nil)

	request := httptest.NewRequest(http.MethodPost, "/api/posts", bytes.NewBufferString(`{"url":"ftp://example.com"}`))
	request = testutils.WithUserContext(request, middleware.UserIDKey, uint(1))
	request.Header.Set("Content-Type", "application/json")
	recorder := httptest.NewRecorder()

	handler.CreatePost(recorder, request)

	testutils.AssertHTTPStatus(t, recorder, http.StatusBadRequest)

	payload := decodeHandlerResponse(t, recorder)
	if payload["success"].(bool) {
		t.Fatalf("expected error response, got %v", payload)
	}
}

func TestPostHandlerSearchPosts(t *testing.T) {
	repo := testutils.NewPostRepositoryStub()
	repo.SearchFn = func(query string, limit, offset int) ([]database.Post, error) {
		return []database.Post{
			{ID: 1, Title: "Search Result 1"},
			{ID: 2, Title: "Search Result 2"},
		}, nil
	}

	handler := NewPostHandler(repo, nil, nil)

	request := httptest.NewRequest(http.MethodGet, "/api/posts/search?q=test", nil)
	recorder := httptest.NewRecorder()

	handler.SearchPosts(recorder, request)

	testutils.AssertHTTPStatus(t, recorder, http.StatusOK)

	payload := decodeHandlerResponse(t, recorder)
	if !payload["success"].(bool) {
		t.Fatalf("expected success response, got %v", payload)
	}
}

func TestPostHandlerFetchTitleFromURL(t *testing.T) {
	titleFetcher := &testutils.MockTitleFetcher{}
	titleFetcher.SetTitle("Test Title")

	handler := NewPostHandler(nil, titleFetcher, nil)

	request := httptest.NewRequest(http.MethodGet, "/api/posts/title?url=https://example.com", nil)
	recorder := httptest.NewRecorder()

	handler.FetchTitleFromURL(recorder, request)

	testutils.AssertHTTPStatus(t, recorder, http.StatusOK)

	payload := decodeHandlerResponse(t, recorder)
	if !payload["success"].(bool) {
		t.Fatalf("expected success response, got %v", payload)
	}
}

func TestPostHandlerFetchTitleFromURLNoFetcher(t *testing.T) {
	handler := NewPostHandler(nil, nil, nil)

	request := httptest.NewRequest(http.MethodGet, "/api/posts/title?url=https://example.com", nil)
	recorder := httptest.NewRecorder()

	handler.FetchTitleFromURL(recorder, request)

	testutils.AssertHTTPStatus(t, recorder, http.StatusNotImplemented)
}

func TestPostHandlerUpdatePostUnauthorized(t *testing.T) {
	repo := testutils.NewPostRepositoryStub()
	repo.GetByIDFn = func(id uint) (*database.Post, error) {
		return &database.Post{ID: id, AuthorID: func() *uint { u := uint(2); return &u }()}, nil
	}

	handler := NewPostHandler(repo, nil, nil)

	request := createUpdatePostRequest(`{"title":"Updated Title","content":"Updated content"}`)
	request = testutils.WithUserContext(request, middleware.UserIDKey, uint(1))
	request = testutils.WithURLParams(request, map[string]string{"id": "1"})
	request.Header.Set("Content-Type", "application/json")
	recorder := httptest.NewRecorder()

	handler.UpdatePost(recorder, request)

	testutils.AssertHTTPStatus(t, recorder, http.StatusForbidden)
}

func TestPostHandlerDeletePostUnauthorized(t *testing.T) {
	repo := testutils.NewPostRepositoryStub()
	repo.GetByIDFn = func(id uint) (*database.Post, error) {
		return &database.Post{ID: id, AuthorID: func() *uint { u := uint(2); return &u }()}, nil
	}

	voteRepo := testutils.NewMockVoteRepository()
	voteService := services.NewVoteService(voteRepo, repo, nil)
	handler := NewPostHandler(repo, nil, voteService)

	request := httptest.NewRequest(http.MethodDelete, "/api/posts/1", nil)
	request = testutils.WithUserContext(request, middleware.UserIDKey, uint(1))
	request = testutils.WithURLParams(request, map[string]string{"id": "1"})
	recorder := httptest.NewRecorder()

	handler.DeletePost(recorder, request)

	testutils.AssertHTTPStatus(t, recorder, http.StatusForbidden)
}

func TestPostHandlerGetPosts(t *testing.T) {
	var receivedLimit, receivedOffset int
	repo := testutils.NewPostRepositoryStub()
	repo.GetAllFn = func(limit, offset int) ([]database.Post, error) {
		receivedLimit = limit
		receivedOffset = offset
		return []database.Post{{ID: 1}}, nil
	}

	handler := NewPostHandler(repo, nil, nil)

	request := httptest.NewRequest(http.MethodGet, "/api/posts?limit=5&offset=2", nil)
	recorder := httptest.NewRecorder()

	handler.GetPosts(recorder, request)

	if receivedLimit != 5 || receivedOffset != 2 {
		t.Fatalf("expected limit=5 offset=2, got %d %d", receivedLimit, receivedOffset)
	}

	testutils.AssertHTTPStatus(t, recorder, http.StatusOK)

	payload := decodeHandlerResponse(t, recorder)
	if !payload["success"].(bool) {
		t.Fatalf("expected success response, got %v", payload)
	}
}

func TestPostHandlerGetPostErrors(t *testing.T) {
	repo := testutils.NewPostRepositoryStub()
	handler := NewPostHandler(repo, nil, nil)

	request := httptest.NewRequest(http.MethodGet, "/api/posts", nil)
	recorder := httptest.NewRecorder()
	handler.GetPost(recorder, request)
	if recorder.Result().StatusCode != http.StatusBadRequest {
		t.Fatalf("expected 400 for missing id, got %d", recorder.Result().StatusCode)
	}

	request = httptest.NewRequest(http.MethodGet, "/api/posts/abc", nil)
	request = testutils.WithURLParams(request, map[string]string{"id": "abc"})
	recorder = httptest.NewRecorder()
	handler.GetPost(recorder, request)
	if recorder.Result().StatusCode != http.StatusBadRequest {
		t.Fatalf("expected 400 for invalid id, got %d", recorder.Result().StatusCode)
	}

	repo.GetByIDFn = func(uint) (*database.Post, error) {
		return nil, gorm.ErrRecordNotFound
	}
	request = httptest.NewRequest(http.MethodGet, "/api/posts/1", nil)
	request = testutils.WithURLParams(request, map[string]string{"id": "1"})
	recorder = httptest.NewRecorder()
	handler.GetPost(recorder, request)
	testutils.AssertHTTPStatus(t, recorder, http.StatusNotFound)
}

func TestPostHandlerCreatePostSuccess(t *testing.T) {
	var storedPost *database.Post
	repo := testutils.NewPostRepositoryStub()
	repo.CreateFn = func(post *database.Post) error {
		storedPost = &database.Post{
			Title:    post.Title,
			URL:      post.URL,
			Content:  post.Content,
			AuthorID: post.AuthorID,
		}
		storedPost.ID = 1
		return nil
	}
	fetcher := &testutils.TitleFetcherStub{FetchTitleFn: func(ctx context.Context, rawURL string) (string, error) {
		return "Fetched Title", nil
	}}

	handler := NewPostHandler(repo, fetcher, nil)

	request := createCreatePostRequest(`{"title":"","url":"https://example.com","content":"Go"}`)
	ctx := context.WithValue(request.Context(), middleware.UserIDKey, uint(42))
	request = request.WithContext(ctx)

	recorder := httptest.NewRecorder()
	handler.CreatePost(recorder, request)

	testutils.AssertHTTPStatus(t, recorder, http.StatusCreated)

	if storedPost == nil || storedPost.Title != "Fetched Title" || storedPost.AuthorID == nil || *storedPost.AuthorID != 42 {
		t.Fatalf("unexpected stored post: %#v", storedPost)
	}
}

func TestPostHandlerCreatePostValidation(t *testing.T) {
	handler := NewPostHandler(testutils.NewPostRepositoryStub(), &testutils.TitleFetcherStub{}, nil)

	recorder := httptest.NewRecorder()
	request := createCreatePostRequest(`{"title":"","url":"","content":""}`)
	request = request.WithContext(context.WithValue(request.Context(), middleware.UserIDKey, uint(1)))
	handler.CreatePost(recorder, request)
	if recorder.Result().StatusCode != http.StatusBadRequest {
		t.Fatalf("expected 400 for missing url, got %d", recorder.Result().StatusCode)
	}

	recorder = httptest.NewRecorder()
	request = createCreatePostRequest(`invalid json`)
	handler.CreatePost(recorder, request)
	if recorder.Result().StatusCode != http.StatusBadRequest {
		t.Fatalf("expected 400 for invalid JSON, got %d", recorder.Result().StatusCode)
	}

	recorder = httptest.NewRecorder()
	request = createCreatePostRequest(`{"title":"okay","url":"https://example.com"}`)
	handler.CreatePost(recorder, request)
	testutils.AssertHTTPStatus(t, recorder, http.StatusUnauthorized)
}

func TestPostHandlerCreatePostTitleFetcherErrors(t *testing.T) {
	tests := []struct {
		name       string
		err        error
		wantStatus int
		wantMsg    string
	}{
		{name: "Unsupported", err: services.ErrUnsupportedScheme, wantStatus: http.StatusBadRequest, wantMsg: "Only HTTP and HTTPS URLs are supported"},
		{name: "TitleMissing", err: services.ErrTitleNotFound, wantStatus: http.StatusBadRequest, wantMsg: "Title could not be extracted"},
		{name: "Generic", err: errors.New("timeout"), wantStatus: http.StatusBadGateway, wantMsg: "Failed to fetch title"},
	}

	for _, tc := range tests {
		t.Run(tc.name, func(t *testing.T) {
			repo := testutils.NewPostRepositoryStub()
			fetcher := &testutils.TitleFetcherStub{FetchTitleFn: func(ctx context.Context, rawURL string) (string, error) {
				return "", tc.err
			}}
			handler := NewPostHandler(repo, fetcher, nil)
			request := createCreatePostRequest(`{"title":"","url":"https://example.com"}`)
			request = request.WithContext(context.WithValue(request.Context(), middleware.UserIDKey, uint(1)))

			recorder := httptest.NewRecorder()
			handler.CreatePost(recorder, request)

			testutils.AssertHTTPStatus(t, recorder, tc.wantStatus)

			if !strings.Contains(recorder.Body.String(), tc.wantMsg) {
				t.Fatalf("expected message to contain %q, got %q", tc.wantMsg, recorder.Body.String())
			}
		})
	}
}

func TestPostHandlerFetchTitleFromURLErrors(t *testing.T) {
	handler := NewPostHandler(testutils.NewPostRepositoryStub(), nil, nil)
	request := httptest.NewRequest(http.MethodGet, "/api/posts/title?url=https://example.com", nil)
	recorder := httptest.NewRecorder()
	handler.FetchTitleFromURL(recorder, request)
	if recorder.Result().StatusCode != http.StatusNotImplemented {
		t.Fatalf("expected 501 when fetcher unavailable, got %d", recorder.Result().StatusCode)
	}

	handler = NewPostHandler(testutils.NewPostRepositoryStub(), &testutils.TitleFetcherStub{}, nil)
	request = httptest.NewRequest(http.MethodGet, "/api/posts/title", nil)
	recorder = httptest.NewRecorder()
	handler.FetchTitleFromURL(recorder, request)
	if recorder.Result().StatusCode != http.StatusBadRequest {
		t.Fatalf("expected 400 for missing url query, got %d", recorder.Result().StatusCode)
	}

	handler = NewPostHandler(testutils.NewPostRepositoryStub(), &testutils.TitleFetcherStub{FetchTitleFn: func(ctx context.Context, rawURL string) (string, error) {
		return "", errors.New("failed")
	}}, nil)
	request = httptest.NewRequest(http.MethodGet, "/api/posts/title?url=https://example.com", nil)
	recorder = httptest.NewRecorder()
	handler.FetchTitleFromURL(recorder, request)
	testutils.AssertHTTPStatus(t, recorder, http.StatusBadGateway)
}

func TestTranslatePostCreateError(t *testing.T) {
	conflictErr := &pgconn.PgError{Code: "23505"}
	msg, status := translatePostCreateError(conflictErr)
	if status != http.StatusConflict || !strings.Contains(msg, "already been submitted") {
		t.Fatalf("unexpected conflict translation: status=%d msg=%q", status, msg)
	}

	fkErr := &pgconn.PgError{Code: "23503"}
	msg, status = translatePostCreateError(fkErr)
	if status != http.StatusUnauthorized || !strings.Contains(msg, "Author account not found") {
		t.Fatalf("unexpected foreign key translation: status=%d msg=%q", status, msg)
	}

	msg, status = translatePostCreateError(errors.New("other"))
	if status != 0 || msg != "" {
		t.Fatalf("expected passthrough for unrelated errors, got status=%d msg=%q", status, msg)
	}
}

func TestPostHandlerUpdatePost(t *testing.T) {
	tests := []struct {
		name           string
		postID         string
		requestBody    string
		userID         uint
		mockSetup      func(*testutils.PostRepositoryStub)
		expectedStatus int
		expectedError  string
	}{
		{
			name:        "valid post update",
			postID:      "1",
			requestBody: `{"title": "Updated Title", "content": "Updated content"}`,
			userID:      1,
			mockSetup: func(repo *testutils.PostRepositoryStub) {
				repo.GetByIDFn = func(id uint) (*database.Post, error) {
					authorID := uint(1)
					return &database.Post{ID: id, Title: "Old Title", AuthorID: &authorID}, nil
				}
				repo.UpdateFn = func(post *database.Post) error { return nil }
			},
			expectedStatus: http.StatusOK,
		},
		{
			name:           "missing user context",
			postID:         "1",
			requestBody:    `{"title": "Updated Title", "content": "Updated content"}`,
			userID:         0,
			mockSetup:      func(repo *testutils.PostRepositoryStub) {},
			expectedStatus: http.StatusUnauthorized,
			expectedError:  "Authentication required",
		},
		{
			name:        "post not found",
			postID:      "999",
			requestBody: `{"title": "Updated Title", "content": "Updated content"}`,
			userID:      1,
			mockSetup: func(repo *testutils.PostRepositoryStub) {
				repo.GetByIDFn = func(id uint) (*database.Post, error) {
					return nil, gorm.ErrRecordNotFound
				}
			},
			expectedStatus: http.StatusNotFound,
			expectedError:  "Post not found",
		},
		{
			name:        "not author",
			postID:      "1",
			requestBody: `{"title": "Updated Title", "content": "Updated content"}`,
			userID:      2,
			mockSetup: func(repo *testutils.PostRepositoryStub) {
				repo.GetByIDFn = func(id uint) (*database.Post, error) {
					authorID := uint(1)
					return &database.Post{ID: id, Title: "Old Title", AuthorID: &authorID}, nil
				}
			},
			expectedStatus: http.StatusForbidden,
			expectedError:  "You can only edit your own posts",
		},
		{
			name:        "empty title",
			postID:      "1",
			requestBody: `{"title": "", "content": "Updated content"}`,
			userID:      1,
			mockSetup: func(repo *testutils.PostRepositoryStub) {
				authorID := uint(1)
				repo.GetByIDFn = func(id uint) (*database.Post, error) {
					return &database.Post{ID: id, Title: "Old Title", AuthorID: &authorID}, nil
				}
			},
			expectedStatus: http.StatusBadRequest,
			expectedError:  "title is required",
		},
		{
			name:        "short title",
			postID:      "1",
			requestBody: `{"title": "ab", "content": "Updated content"}`,
			userID:      1,
			mockSetup: func(repo *testutils.PostRepositoryStub) {
				authorID := uint(1)
				repo.GetByIDFn = func(id uint) (*database.Post, error) {
					return &database.Post{ID: id, Title: "Old Title", AuthorID: &authorID}, nil
				}
			},
			expectedStatus: http.StatusBadRequest,
			expectedError:  "title must be at least 3 characters",
		},
	}

	for _, tt := range tests {
		t.Run(tt.name, func(t *testing.T) {
			repo := testutils.NewPostRepositoryStub()
			if tt.mockSetup != nil {
				tt.mockSetup(repo)
			}
			handler := NewPostHandler(repo, &testutils.TitleFetcherStub{}, nil)

			request := createUpdatePostRequest(tt.requestBody)
			if tt.userID > 0 {
				ctx := context.WithValue(request.Context(), middleware.UserIDKey, tt.userID)
				request = request.WithContext(ctx)
			}

			ctx := chi.NewRouteContext()
			ctx.URLParams.Add("id", tt.postID)
			request = request.WithContext(context.WithValue(request.Context(), chi.RouteCtxKey, ctx))

			recorder := httptest.NewRecorder()

			handler.UpdatePost(recorder, request)

			testutils.AssertHTTPStatus(t, recorder, tt.expectedStatus)

			if tt.expectedError != "" {
				if !strings.Contains(recorder.Body.String(), tt.expectedError) {
					t.Fatalf("expected error to contain %q, got %q", tt.expectedError, recorder.Body.String())
				}
			}
		})
	}
}

func TestPostHandlerDeletePost(t *testing.T) {
	tests := []struct {
		name           string
		postID         string
		userID         uint
		mockSetup      func(*testutils.PostRepositoryStub)
		expectedStatus int
		expectedError  string
	}{
		{
			name:   "valid post deletion",
			postID: "1",
			userID: 1,
			mockSetup: func(repo *testutils.PostRepositoryStub) {
				repo.GetByIDFn = func(id uint) (*database.Post, error) {
					authorID := uint(1)
					return &database.Post{ID: id, Title: "Test Post", AuthorID: &authorID}, nil
				}
				repo.DeleteFn = func(id uint) error { return nil }
			},
			expectedStatus: http.StatusOK,
		},
		{
			name:           "missing user context",
			postID:         "1",
			userID:         0,
			mockSetup:      func(repo *testutils.PostRepositoryStub) {},
			expectedStatus: http.StatusUnauthorized,
			expectedError:  "Authentication required",
		},
		{
			name:   "post not found",
			postID: "999",
			userID: 1,
			mockSetup: func(repo *testutils.PostRepositoryStub) {
				repo.GetByIDFn = func(id uint) (*database.Post, error) {
					return nil, gorm.ErrRecordNotFound
				}
			},
			expectedStatus: http.StatusNotFound,
			expectedError:  "Post not found",
		},
		{
			name:   "not author",
			postID: "1",
			userID: 2,
			mockSetup: func(repo *testutils.PostRepositoryStub) {
				repo.GetByIDFn = func(id uint) (*database.Post, error) {
					authorID := uint(1)
					return &database.Post{ID: id, Title: "Test Post", AuthorID: &authorID}, nil
				}
			},
			expectedStatus: http.StatusForbidden,
			expectedError:  "You can only delete your own posts",
		},
		{
			name:   "delete error",
			postID: "1",
			userID: 1,
			mockSetup: func(repo *testutils.PostRepositoryStub) {
				repo.GetByIDFn = func(id uint) (*database.Post, error) {
					authorID := uint(1)
					return &database.Post{ID: id, Title: "Test Post", AuthorID: &authorID}, nil
				}
				repo.DeleteFn = func(id uint) error { return errors.New("database error") }
			},
			expectedStatus: http.StatusInternalServerError,
			expectedError:  "Failed to delete post",
		},
		{
			name:   "delete votes error",
			postID: "1",
			userID: 1,
			mockSetup: func(repo *testutils.PostRepositoryStub) {
				repo.GetByIDFn = func(id uint) (*database.Post, error) {
					authorID := uint(1)
					return &database.Post{ID: id, Title: "Test Post", AuthorID: &authorID}, nil
				}
			},
			expectedStatus: http.StatusInternalServerError,
			expectedError:  "Failed to delete post votes",
		},
	}

	for _, tt := range tests {
		t.Run(tt.name, func(t *testing.T) {
			repo := testutils.NewPostRepositoryStub()
			if tt.mockSetup != nil {
				tt.mockSetup(repo)
			}

			var voteService *services.VoteService
			if tt.name == "delete votes error" {
				voteRepo := &errorVoteRepository{}
				voteService = services.NewVoteService(voteRepo, repo, nil)
			} else {
				voteRepo := testutils.NewMockVoteRepository()
				voteService = services.NewVoteService(voteRepo, repo, nil)
			}

			handler := NewPostHandler(repo, &testutils.TitleFetcherStub{}, voteService)

			request := httptest.NewRequest(http.MethodDelete, "/api/posts/"+tt.postID, nil)
			if tt.userID > 0 {
				ctx := context.WithValue(request.Context(), middleware.UserIDKey, tt.userID)
				request = request.WithContext(ctx)
			}

			ctx := chi.NewRouteContext()
			ctx.URLParams.Add("id", tt.postID)
			request = request.WithContext(context.WithValue(request.Context(), chi.RouteCtxKey, ctx))

			recorder := httptest.NewRecorder()

			handler.DeletePost(recorder, request)

			testutils.AssertHTTPStatus(t, recorder, tt.expectedStatus)

			if tt.expectedError != "" {
				if !strings.Contains(recorder.Body.String(), tt.expectedError) {
					t.Fatalf("expected error to contain %q, got %q", tt.expectedError, recorder.Body.String())
				}
			}
		})
	}
}

type errorVoteRepository struct{}

func (e *errorVoteRepository) Create(*database.Vote) error         { return nil }
func (e *errorVoteRepository) CreateOrUpdate(*database.Vote) error { return nil }
func (e *errorVoteRepository) GetByID(uint) (*database.Vote, error) {
	return nil, gorm.ErrRecordNotFound
}
func (e *errorVoteRepository) GetByUserAndPost(uint, uint) (*database.Vote, error) {
	return nil, gorm.ErrRecordNotFound
}
func (e *errorVoteRepository) GetByVoteHash(string) (*database.Vote, error) {
	return nil, gorm.ErrRecordNotFound
}
func (e *errorVoteRepository) GetByPostID(uint) ([]database.Vote, error) {
	return nil, errors.New("database error")
}
func (e *errorVoteRepository) GetByUserID(uint) ([]database.Vote, error) { return nil, nil }
func (e *errorVoteRepository) Update(*database.Vote) error               { return nil }
func (e *errorVoteRepository) Delete(uint) error                         { return nil }
func (e *errorVoteRepository) Count() (int64, error)                     { return 0, nil }
func (e *errorVoteRepository) CountByPostID(uint) (int64, error)         { return 0, nil }
func (e *errorVoteRepository) CountByUserID(uint) (int64, error)         { return 0, nil }
func (e *errorVoteRepository) GetVoteCountsByPostID(uint) (int, int, error) {
	return 0, 0, errors.New("database error")
}
func (e *errorVoteRepository) WithTx(*gorm.DB) repositories.VoteRepository { return e }

func TestPostHandler_EdgeCases(t *testing.T) {
	postRepo := testutils.NewPostRepositoryStub()
	titleFetcher := &testutils.TitleFetcherStub{}
	handler := NewPostHandler(postRepo, titleFetcher, nil)

	t.Run("GetPosts with zero limit", func(t *testing.T) {
		req := httptest.NewRequest("GET", "/api/posts?limit=0", nil)
		w := httptest.NewRecorder()

		handler.GetPosts(w, req)

		if w.Code != http.StatusOK {
			t.Errorf("Expected status 200 for zero limit, got %d", w.Code)
		}
	})

	t.Run("GetPosts with negative limit", func(t *testing.T) {
		req := httptest.NewRequest("GET", "/api/posts?limit=-1", nil)
		w := httptest.NewRecorder()

		handler.GetPosts(w, req)

		if w.Code != http.StatusOK {
			t.Errorf("Expected status 200 for negative limit, got %d", w.Code)
		}
	})

	t.Run("GetPosts with negative offset", func(t *testing.T) {
		req := httptest.NewRequest("GET", "/api/posts?offset=-1", nil)
		w := httptest.NewRecorder()

		handler.GetPosts(w, req)

		if w.Code != http.StatusOK {
			t.Errorf("Expected status 200 for negative offset, got %d", w.Code)
		}
	})
}
