package handlers

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

	"veza-backend-api/internal/common"
	"veza-backend-api/internal/core/social"

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

// MockSocialService implements social.SocialService interface for testing
type MockSocialService struct {
	mock.Mock
}

func (m *MockSocialService) CreatePost(ctx context.Context, userID uuid.UUID, content string, attachments map[string]uuid.UUID) (*social.Post, error) {
	args := m.Called(ctx, userID, content, attachments)
	if args.Get(0) == nil {
		return nil, args.Error(1)
	}
	return args.Get(0).(*social.Post), args.Error(1)
}

func (m *MockSocialService) GetPostsByUser(ctx context.Context, userID uuid.UUID, limit, offset int) ([]social.Post, error) {
	args := m.Called(ctx, userID, limit, offset)
	if args.Get(0) == nil {
		return nil, args.Error(1)
	}
	return args.Get(0).([]social.Post), args.Error(1)
}

func (m *MockSocialService) GetGlobalFeed(ctx context.Context, limit, offset int) ([]social.FeedItem, error) {
	args := m.Called(ctx, limit, offset)
	if args.Get(0) == nil {
		return nil, args.Error(1)
	}
	return args.Get(0).([]social.FeedItem), args.Error(1)
}

func (m *MockSocialService) GetUserFeed(ctx context.Context, userID uuid.UUID, limit, offset int) ([]social.FeedItem, error) {
	args := m.Called(ctx, userID, limit, offset)
	if args.Get(0) == nil {
		return nil, args.Error(1)
	}
	return args.Get(0).([]social.FeedItem), args.Error(1)
}

func (m *MockSocialService) ToggleLike(ctx context.Context, userID uuid.UUID, targetID uuid.UUID, targetType string) (bool, error) {
	args := m.Called(ctx, userID, targetID, targetType)
	return args.Bool(0), args.Error(1)
}

func (m *MockSocialService) AddComment(ctx context.Context, userID uuid.UUID, targetID uuid.UUID, targetType string, content string) (*social.Comment, error) {
	args := m.Called(ctx, userID, targetID, targetType, content)
	if args.Get(0) == nil {
		return nil, args.Error(1)
	}
	return args.Get(0).(*social.Comment), args.Error(1)
}

func (m *MockSocialService) CreateActivityPost(ctx context.Context, userID uuid.UUID, content string, meta map[string]interface{}) error {
	args := m.Called(ctx, userID, content, meta)
	return args.Error(0)
}

func setupTestSocialRouter(mockService *MockSocialService) *gin.Engine {
	gin.SetMode(gin.TestMode)
	router := gin.New()

	logger := zap.NewNop()
	handler := NewSocialHandlerWithInterface(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()
	})
	{
		api.POST("/posts", handler.CreatePost)
		api.POST("/likes", handler.ToggleLike)
		api.POST("/comments", handler.AddComment)
		api.GET("/feed", handler.GetFeed)
	}

	return router
}

func TestSocialHandler_CreatePost_Success(t *testing.T) {
	// Setup
	mockService := new(MockSocialService)
	router := setupTestSocialRouter(mockService)

	userID := uuid.New()
	reqBody := CreatePostRequest{
		Content: "This is a test post",
	}

	expectedPost := &social.Post{
		ID:      uuid.New(),
		UserID:  userID,
		Content: "This is a test post",
		Type:    social.PostTypeStatus,
	}

	mockService.On("CreatePost", mock.Anything, userID, reqBody.Content, map[string]uuid.UUID{}).Return(expectedPost, nil)

	body, _ := json.Marshal(reqBody)

	// Execute
	req, _ := http.NewRequest("POST", "/api/v1/posts", 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 TestSocialHandler_CreatePost_WithAttachments(t *testing.T) {
	// Setup
	mockService := new(MockSocialService)
	router := setupTestSocialRouter(mockService)

	userID := uuid.New()
	trackID := uuid.New()
	reqBody := CreatePostRequest{
		Content: "Check out this track!",
		Attachments: map[string]string{
			"track_id": trackID.String(),
		},
	}

	expectedPost := &social.Post{
		ID:      uuid.New(),
		UserID:  userID,
		Content: "Check out this track!",
		Type:    social.PostTypeShare,
	}

	mockService.On("CreatePost", mock.Anything, userID, reqBody.Content, map[string]uuid.UUID{"track_id": trackID}).Return(expectedPost, nil)

	body, _ := json.Marshal(reqBody)

	// Execute
	req, _ := http.NewRequest("POST", "/api/v1/posts", 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 TestSocialHandler_CreatePost_Unauthorized(t *testing.T) {
	// Setup
	mockService := new(MockSocialService)
	router := setupTestSocialRouter(mockService)

	reqBody := CreatePostRequest{
		Content: "This is a test post",
	}
	body, _ := json.Marshal(reqBody)

	// Execute
	req, _ := http.NewRequest("POST", "/api/v1/posts", 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, "CreatePost")
}

func TestSocialHandler_CreatePost_InvalidJSON(t *testing.T) {
	// Setup
	mockService := new(MockSocialService)
	router := setupTestSocialRouter(mockService)

	userID := uuid.New()

	// Execute
	req, _ := http.NewRequest("POST", "/api/v1/posts", bytes.NewBuffer([]byte("invalid json")))
	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, "CreatePost")
}

func TestSocialHandler_CreatePost_EmptyContent(t *testing.T) {
	// Setup
	mockService := new(MockSocialService)
	router := setupTestSocialRouter(mockService)

	userID := uuid.New()
	reqBody := CreatePostRequest{
		Content: "",
	}
	body, _ := json.Marshal(reqBody)

	// Execute
	req, _ := http.NewRequest("POST", "/api/v1/posts", 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, "CreatePost")
}

func TestSocialHandler_CreatePost_ServiceError(t *testing.T) {
	// Setup
	mockService := new(MockSocialService)
	router := setupTestSocialRouter(mockService)

	userID := uuid.New()
	reqBody := CreatePostRequest{
		Content: "This is a test post",
	}

	mockService.On("CreatePost", mock.Anything, userID, reqBody.Content, map[string]uuid.UUID{}).Return(nil, assert.AnError)

	body, _ := json.Marshal(reqBody)

	// Execute
	req, _ := http.NewRequest("POST", "/api/v1/posts", 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.StatusInternalServerError, w.Code)
	mockService.AssertExpectations(t)
}

func TestSocialHandler_ToggleLike_Success(t *testing.T) {
	// Setup
	mockService := new(MockSocialService)
	router := setupTestSocialRouter(mockService)

	userID := uuid.New()
	targetID := uuid.New()
	reqBody := ToggleLikeRequest{
		TargetID:   targetID.String(),
		TargetType: "post",
	}

	mockService.On("ToggleLike", mock.Anything, userID, targetID, "post").Return(true, nil)

	body, _ := json.Marshal(reqBody)

	// Execute
	req, _ := http.NewRequest("POST", "/api/v1/likes", 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 TestSocialHandler_ToggleLike_Unauthorized(t *testing.T) {
	// Setup
	mockService := new(MockSocialService)
	router := setupTestSocialRouter(mockService)

	targetID := uuid.New()
	reqBody := ToggleLikeRequest{
		TargetID:   targetID.String(),
		TargetType: "post",
	}
	body, _ := json.Marshal(reqBody)

	// Execute
	req, _ := http.NewRequest("POST", "/api/v1/likes", 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, "ToggleLike")
}

func TestSocialHandler_ToggleLike_InvalidTargetID(t *testing.T) {
	// Setup
	mockService := new(MockSocialService)
	router := setupTestSocialRouter(mockService)

	userID := uuid.New()
	reqBody := ToggleLikeRequest{
		TargetID:   "invalid-uuid",
		TargetType: "post",
	}
	body, _ := json.Marshal(reqBody)

	// Execute
	req, _ := http.NewRequest("POST", "/api/v1/likes", 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, "ToggleLike")
}

func TestSocialHandler_ToggleLike_InvalidTargetType(t *testing.T) {
	// Setup
	mockService := new(MockSocialService)
	router := setupTestSocialRouter(mockService)

	userID := uuid.New()
	targetID := uuid.New()
	reqBody := ToggleLikeRequest{
		TargetID:   targetID.String(),
		TargetType: "invalid_type",
	}
	body, _ := json.Marshal(reqBody)

	// Execute
	req, _ := http.NewRequest("POST", "/api/v1/likes", 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, "ToggleLike")
}

func TestSocialHandler_ToggleLike_ServiceError(t *testing.T) {
	// Setup
	mockService := new(MockSocialService)
	router := setupTestSocialRouter(mockService)

	userID := uuid.New()
	targetID := uuid.New()
	reqBody := ToggleLikeRequest{
		TargetID:   targetID.String(),
		TargetType: "post",
	}

	mockService.On("ToggleLike", mock.Anything, userID, targetID, "post").Return(false, assert.AnError)

	body, _ := json.Marshal(reqBody)

	// Execute
	req, _ := http.NewRequest("POST", "/api/v1/likes", 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.StatusInternalServerError, w.Code)
	mockService.AssertExpectations(t)
}

func TestSocialHandler_AddComment_Success(t *testing.T) {
	// Setup
	mockService := new(MockSocialService)
	router := setupTestSocialRouter(mockService)

	userID := uuid.New()
	targetID := uuid.New()
	reqBody := AddCommentRequest{
		TargetID:   targetID.String(),
		TargetType: "post",
		Content:    "This is a comment",
	}

	expectedComment := &social.Comment{
		ID:        uuid.New(),
		UserID:    userID,
		TargetID:  targetID,
		Content:   "This is a comment",
		CreatedAt: time.Now(),
	}

	mockService.On("AddComment", mock.Anything, userID, targetID, "post", "This is a comment").Return(expectedComment, nil)

	body, _ := json.Marshal(reqBody)

	// Execute
	req, _ := http.NewRequest("POST", "/api/v1/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 TestSocialHandler_AddComment_Unauthorized(t *testing.T) {
	// Setup
	mockService := new(MockSocialService)
	router := setupTestSocialRouter(mockService)

	targetID := uuid.New()
	reqBody := AddCommentRequest{
		TargetID:   targetID.String(),
		TargetType: "post",
		Content:    "This is a comment",
	}
	body, _ := json.Marshal(reqBody)

	// Execute
	req, _ := http.NewRequest("POST", "/api/v1/comments", 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, "AddComment")
}

func TestSocialHandler_AddComment_InvalidTargetID(t *testing.T) {
	// Setup
	mockService := new(MockSocialService)
	router := setupTestSocialRouter(mockService)

	userID := uuid.New()
	reqBody := AddCommentRequest{
		TargetID:   "invalid-uuid",
		TargetType: "post",
		Content:    "This is a comment",
	}
	body, _ := json.Marshal(reqBody)

	// Execute
	req, _ := http.NewRequest("POST", "/api/v1/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, "AddComment")
}

func TestSocialHandler_AddComment_EmptyContent(t *testing.T) {
	// Setup
	mockService := new(MockSocialService)
	router := setupTestSocialRouter(mockService)

	userID := uuid.New()
	targetID := uuid.New()
	reqBody := AddCommentRequest{
		TargetID:   targetID.String(),
		TargetType: "post",
		Content:    "",
	}
	body, _ := json.Marshal(reqBody)

	// Execute
	req, _ := http.NewRequest("POST", "/api/v1/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, "AddComment")
}

func TestSocialHandler_AddComment_ServiceError(t *testing.T) {
	// Setup
	mockService := new(MockSocialService)
	router := setupTestSocialRouter(mockService)

	userID := uuid.New()
	targetID := uuid.New()
	reqBody := AddCommentRequest{
		TargetID:   targetID.String(),
		TargetType: "post",
		Content:    "This is a comment",
	}

	mockService.On("AddComment", mock.Anything, userID, targetID, "post", "This is a comment").Return(nil, assert.AnError)

	body, _ := json.Marshal(reqBody)

	// Execute
	req, _ := http.NewRequest("POST", "/api/v1/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.StatusInternalServerError, w.Code)
	mockService.AssertExpectations(t)
}

func TestSocialHandler_GetFeed_Success(t *testing.T) {
	// Setup
	mockService := new(MockSocialService)
	router := setupTestSocialRouter(mockService)

	expectedFeed := []social.FeedItem{
		{
			ID:        "post:123",
			Type:      social.ActivityPost,
			ActorID:   uuid.New(),
			Content:   "First post",
			CreatedAt: time.Now(),
		},
		{
			ID:        "post:456",
			Type:      social.ActivityPost,
			ActorID:   uuid.New(),
			Content:   "Second post",
			CreatedAt: time.Now(),
		},
	}

	mockService.On("GetGlobalFeed", mock.Anything, 20, 0).Return(expectedFeed, nil)

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

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

func TestSocialHandler_GetFeed_ServiceError(t *testing.T) {
	// Setup
	mockService := new(MockSocialService)
	router := setupTestSocialRouter(mockService)

	mockService.On("GetGlobalFeed", mock.Anything, 20, 0).Return(nil, assert.AnError)

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

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

func TestNewSocialHandler(t *testing.T) {
	// Setup
	mockService := &MockSocialService{}
	logger := zap.NewNop()

	// Execute
	handler := NewSocialHandlerWithInterface(mockService, logger)

	// Assert
	assert.NotNil(t, handler)
	assert.NotNil(t, handler.service)
	assert.NotNil(t, handler.commonHandler)
}
