package handlers

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

	"veza-backend-api/internal/common"
	"veza-backend-api/internal/models"
	"veza-backend-api/internal/services"

	"github.com/gin-gonic/gin"
	"github.com/google/uuid"
	"github.com/stretchr/testify/assert"
	"github.com/stretchr/testify/mock"
	"go.uber.org/zap"
)

// MockCommentService implements CommentService interface for testing
type MockCommentService struct {
	mock.Mock
}

func (m *MockCommentService) CreateComment(ctx context.Context, trackID uuid.UUID, userID uuid.UUID, content string, timestamp float64, parentID *uuid.UUID) (*models.TrackComment, error) {
	args := m.Called(ctx, trackID, userID, content, timestamp, parentID)
	if args.Get(0) == nil {
		return nil, args.Error(1)
	}
	return args.Get(0).(*models.TrackComment), args.Error(1)
}

func (m *MockCommentService) GetComments(ctx context.Context, trackID uuid.UUID, page, limit int) ([]models.TrackComment, int64, error) {
	args := m.Called(ctx, trackID, page, limit)
	if args.Get(0) == nil {
		return nil, args.Get(1).(int64), args.Error(2)
	}
	return args.Get(0).([]models.TrackComment), args.Get(1).(int64), args.Error(2)
}

func (m *MockCommentService) UpdateComment(ctx context.Context, commentID uuid.UUID, userID uuid.UUID, content string) (*models.TrackComment, error) {
	args := m.Called(ctx, commentID, userID, content)
	if args.Get(0) == nil {
		return nil, args.Error(1)
	}
	return args.Get(0).(*models.TrackComment), args.Error(1)
}

func (m *MockCommentService) DeleteComment(ctx context.Context, commentID uuid.UUID, userID uuid.UUID, isAdmin bool) error {
	args := m.Called(ctx, commentID, userID, isAdmin)
	return args.Error(0)
}

func (m *MockCommentService) GetReplies(ctx context.Context, parentID uuid.UUID, page, limit int) ([]models.TrackComment, int64, error) {
	args := m.Called(ctx, parentID, page, limit)
	if args.Get(0) == nil {
		return nil, args.Get(1).(int64), args.Error(2)
	}
	return args.Get(0).([]models.TrackComment), args.Get(1).(int64), args.Error(2)
}

func setupTestCommentRouter(mockService *MockCommentService) *gin.Engine {
	gin.SetMode(gin.TestMode)
	router := gin.New()

	logger := zap.NewNop()
	handler := NewCommentHandlerWithInterface(mockService, logger)

	api := router.Group("/api/v1")
	api.Use(func(c *gin.Context) {
		// Mock auth middleware - set user_id from header if present
		userIDStr := c.GetHeader("X-User-ID")
		if userIDStr != "" {
			uid, err := uuid.Parse(userIDStr)
			if err == nil {
				common.SetUserIDInContext(c, uid)
			}
		}
		c.Next()
	})
	{
		tracks := api.Group("/tracks")
		{
			tracks.POST("/:id/comments", handler.CreateComment)
			tracks.GET("/:id/comments", handler.GetComments)
		}
		// Comments routes - UpdateComment and DeleteComment use /comments/:id
		comments := api.Group("/comments")
		{
			comments.PUT("/:id", handler.UpdateComment)
			comments.DELETE("/:id", handler.DeleteComment)
		}
	}

	return router
}

func TestCommentHandler_CreateComment_Success(t *testing.T) {
	// Setup
	mockService := new(MockCommentService)
	router := setupTestCommentRouter(mockService)

	userID := uuid.New()
	trackID := uuid.New()

	expectedComment := &models.TrackComment{
		ID:      uuid.New(),
		TrackID: trackID,
		UserID:  userID,
		Content: "Great track!",
	}

	mockService.On("CreateComment", mock.Anything, trackID, userID, "Great track!", 0.0, (*uuid.UUID)(nil)).Return(expectedComment, nil)

	reqBody := CreateCommentRequest{
		Content: "Great track!",
	}
	body, _ := json.Marshal(reqBody)

	// Execute
	req, _ := http.NewRequest("POST", "/api/v1/tracks/"+trackID.String()+"/comments", bytes.NewBuffer(body))
	req.Header.Set("Content-Type", "application/json")
	req.Header.Set("X-User-ID", userID.String())
	w := httptest.NewRecorder()
	router.ServeHTTP(w, req)

	// Assert
	assert.Equal(t, http.StatusCreated, w.Code)
	mockService.AssertExpectations(t)
}

func TestCommentHandler_CreateComment_WithParentID(t *testing.T) {
	// Setup
	mockService := new(MockCommentService)
	router := setupTestCommentRouter(mockService)

	userID := uuid.New()
	trackID := uuid.New()
	parentID := uuid.New()

	expectedComment := &models.TrackComment{
		ID:       uuid.New(),
		TrackID:  trackID,
		UserID:   userID,
		Content:  "Reply comment",
		ParentID: &parentID,
	}

	mockService.On("CreateComment", mock.Anything, trackID, userID, "Reply comment", 0.0, &parentID).Return(expectedComment, nil)

	reqBody := CreateCommentRequest{
		Content:  "Reply comment",
		ParentID: &parentID,
	}
	body, _ := json.Marshal(reqBody)

	// Execute
	req, _ := http.NewRequest("POST", "/api/v1/tracks/"+trackID.String()+"/comments", bytes.NewBuffer(body))
	req.Header.Set("Content-Type", "application/json")
	req.Header.Set("X-User-ID", userID.String())
	w := httptest.NewRecorder()
	router.ServeHTTP(w, req)

	// Assert
	assert.Equal(t, http.StatusCreated, w.Code)
	mockService.AssertExpectations(t)
}

func TestCommentHandler_CreateComment_Unauthorized(t *testing.T) {
	// Setup
	mockService := new(MockCommentService)
	router := setupTestCommentRouter(mockService)

	trackID := uuid.New()
	reqBody := CreateCommentRequest{
		Content: "Great track!",
	}
	body, _ := json.Marshal(reqBody)

	// Execute
	req, _ := http.NewRequest("POST", "/api/v1/tracks/"+trackID.String()+"/comments", bytes.NewBuffer(body))
	req.Header.Set("Content-Type", "application/json")
	w := httptest.NewRecorder()
	router.ServeHTTP(w, req)

	// Assert - GetUserIDUUID returns 401 via RespondWithAppError
	assert.True(t, w.Code == http.StatusUnauthorized || w.Code == http.StatusForbidden)
	mockService.AssertNotCalled(t, "CreateComment")
}

func TestCommentHandler_CreateComment_InvalidTrackID(t *testing.T) {
	// Setup
	mockService := new(MockCommentService)
	router := setupTestCommentRouter(mockService)

	userID := uuid.New()
	reqBody := CreateCommentRequest{
		Content: "Great track!",
	}
	body, _ := json.Marshal(reqBody)

	// Execute
	req, _ := http.NewRequest("POST", "/api/v1/tracks/invalid/comments", bytes.NewBuffer(body))
	req.Header.Set("Content-Type", "application/json")
	req.Header.Set("X-User-ID", userID.String())
	w := httptest.NewRecorder()
	router.ServeHTTP(w, req)

	// Assert
	assert.Equal(t, http.StatusBadRequest, w.Code)
	mockService.AssertNotCalled(t, "CreateComment")
}

func TestCommentHandler_CreateComment_TrackNotFound(t *testing.T) {
	// Setup
	mockService := new(MockCommentService)
	router := setupTestCommentRouter(mockService)

	userID := uuid.New()
	trackID := uuid.New()

	mockService.On("CreateComment", mock.Anything, trackID, userID, "Great track!", 0.0, (*uuid.UUID)(nil)).Return(nil, services.ErrTrackNotFound)

	reqBody := CreateCommentRequest{
		Content: "Great track!",
	}
	body, _ := json.Marshal(reqBody)

	// Execute
	req, _ := http.NewRequest("POST", "/api/v1/tracks/"+trackID.String()+"/comments", bytes.NewBuffer(body))
	req.Header.Set("Content-Type", "application/json")
	req.Header.Set("X-User-ID", userID.String())
	w := httptest.NewRecorder()
	router.ServeHTTP(w, req)

	// Assert
	assert.Equal(t, http.StatusNotFound, w.Code)
	mockService.AssertExpectations(t)
}

func TestCommentHandler_GetComments_Success(t *testing.T) {
	// Setup
	mockService := new(MockCommentService)
	router := setupTestCommentRouter(mockService)

	trackID := uuid.New()
	userID := uuid.New()

	expectedComments := []models.TrackComment{
		{
			ID:      uuid.New(),
			TrackID: trackID,
			UserID:  userID,
			Content: "First comment",
		},
		{
			ID:      uuid.New(),
			TrackID: trackID,
			UserID:  userID,
			Content: "Second comment",
		},
	}

	mockService.On("GetComments", mock.Anything, trackID, 1, 20).Return(expectedComments, int64(2), nil)

	// Execute
	req, _ := http.NewRequest("GET", "/api/v1/tracks/"+trackID.String()+"/comments?page=1&limit=20", nil)
	w := httptest.NewRecorder()
	router.ServeHTTP(w, req)

	// Assert
	assert.Equal(t, http.StatusOK, w.Code)
	mockService.AssertExpectations(t)
}

func TestCommentHandler_GetComments_InvalidTrackID(t *testing.T) {
	// Setup
	mockService := new(MockCommentService)
	router := setupTestCommentRouter(mockService)

	// Execute
	req, _ := http.NewRequest("GET", "/api/v1/tracks/invalid/comments", nil)
	w := httptest.NewRecorder()
	router.ServeHTTP(w, req)

	// Assert
	assert.Equal(t, http.StatusBadRequest, w.Code)
	mockService.AssertNotCalled(t, "GetComments")
}

func TestCommentHandler_UpdateComment_Success(t *testing.T) {
	// Setup
	mockService := new(MockCommentService)
	router := setupTestCommentRouter(mockService)

	userID := uuid.New()
	trackID := uuid.New()
	commentID := uuid.New()

	expectedComment := &models.TrackComment{
		ID:      commentID,
		TrackID: trackID,
		UserID:  userID,
		Content: "Updated comment",
	}

	mockService.On("UpdateComment", mock.Anything, commentID, userID, "Updated comment").Return(expectedComment, nil)

	reqBody := UpdateCommentRequest{
		Content: "Updated comment",
	}
	body, _ := json.Marshal(reqBody)

	// Execute - UpdateComment uses /comments/:id route
	req, _ := http.NewRequest("PUT", "/api/v1/comments/"+commentID.String(), bytes.NewBuffer(body))
	req.Header.Set("Content-Type", "application/json")
	req.Header.Set("X-User-ID", userID.String())
	w := httptest.NewRecorder()
	router.ServeHTTP(w, req)

	// Assert
	assert.Equal(t, http.StatusOK, w.Code)
	mockService.AssertExpectations(t)
}

func TestCommentHandler_UpdateComment_Unauthorized(t *testing.T) {
	// Setup
	mockService := new(MockCommentService)
	router := setupTestCommentRouter(mockService)

	commentID := uuid.New()

	reqBody := UpdateCommentRequest{
		Content: "Updated comment",
	}
	body, _ := json.Marshal(reqBody)

	// Execute
	req, _ := http.NewRequest("PUT", "/api/v1/comments/"+commentID.String(), bytes.NewBuffer(body))
	req.Header.Set("Content-Type", "application/json")
	w := httptest.NewRecorder()
	router.ServeHTTP(w, req)

	// Assert
	assert.True(t, w.Code == http.StatusUnauthorized || w.Code == http.StatusForbidden)
	mockService.AssertNotCalled(t, "UpdateComment")
}

func TestCommentHandler_UpdateComment_CommentNotFound(t *testing.T) {
	// Setup
	mockService := new(MockCommentService)
	router := setupTestCommentRouter(mockService)

	userID := uuid.New()
	commentID := uuid.New()

	mockService.On("UpdateComment", mock.Anything, commentID, userID, "Updated comment").Return(nil, services.ErrCommentNotFound)

	reqBody := UpdateCommentRequest{
		Content: "Updated comment",
	}
	body, _ := json.Marshal(reqBody)

	// Execute
	req, _ := http.NewRequest("PUT", "/api/v1/comments/"+commentID.String(), bytes.NewBuffer(body))
	req.Header.Set("Content-Type", "application/json")
	req.Header.Set("X-User-ID", userID.String())
	w := httptest.NewRecorder()
	router.ServeHTTP(w, req)

	// Assert
	assert.Equal(t, http.StatusNotFound, w.Code)
	mockService.AssertExpectations(t)
}

func TestCommentHandler_DeleteComment_Success(t *testing.T) {
	// Setup
	mockService := new(MockCommentService)
	router := setupTestCommentRouter(mockService)

	userID := uuid.New()
	commentID := uuid.New()

	mockService.On("DeleteComment", mock.Anything, commentID, userID, false).Return(nil)

	// Execute - DeleteComment uses /comments/:id route
	req, _ := http.NewRequest("DELETE", "/api/v1/comments/"+commentID.String(), nil)
	req.Header.Set("X-User-ID", userID.String())
	w := httptest.NewRecorder()
	router.ServeHTTP(w, req)

	// Assert
	assert.Equal(t, http.StatusOK, w.Code)
	mockService.AssertExpectations(t)
}

func TestCommentHandler_DeleteComment_Unauthorized(t *testing.T) {
	// Setup
	mockService := new(MockCommentService)
	router := setupTestCommentRouter(mockService)

	commentID := uuid.New()

	// Execute
	req, _ := http.NewRequest("DELETE", "/api/v1/comments/"+commentID.String(), nil)
	w := httptest.NewRecorder()
	router.ServeHTTP(w, req)

	// Assert
	assert.True(t, w.Code == http.StatusUnauthorized || w.Code == http.StatusForbidden)
	mockService.AssertNotCalled(t, "DeleteComment")
}

func TestCommentHandler_DeleteComment_InvalidCommentID(t *testing.T) {
	// Setup
	mockService := new(MockCommentService)
	router := setupTestCommentRouter(mockService)

	userID := uuid.New()

	// Execute
	req, _ := http.NewRequest("DELETE", "/api/v1/comments/invalid", nil)
	req.Header.Set("X-User-ID", userID.String())
	w := httptest.NewRecorder()
	router.ServeHTTP(w, req)

	// Assert
	assert.Equal(t, http.StatusBadRequest, w.Code)
	mockService.AssertNotCalled(t, "DeleteComment")
}
