package handlers

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

	"github.com/gin-gonic/gin"
	"github.com/google/uuid"
	"github.com/stretchr/testify/assert"
	"go.uber.org/zap"
	"gorm.io/gorm"
	"veza-backend-api/internal/models"
	"veza-backend-api/internal/services"
)

type MockRoomService struct {
	createRoomFunc func(ctx context.Context, userID int64, req services.CreateRoomRequest) (*services.RoomResponse, error)
	getUserRoomsFunc func(ctx context.Context, userID int64) ([]*services.RoomResponse, error)
	getRoomFunc func(ctx context.Context, roomID int64) (*services.RoomResponse, error)
	addMemberFunc func(ctx context.Context, roomID, userID int64) error
	getRoomHistoryFunc func(ctx context.Context, roomID uuid.UUID, limit, offset int) ([]services.ChatMessageResponse, error)
}

func (m *MockRoomService) CreateRoom(ctx context.Context, userID int64, req services.CreateRoomRequest) (*services.RoomResponse, error) {
	return m.createRoomFunc(ctx, userID, req)
}
func (m *MockRoomService) GetUserRooms(ctx context.Context, userID int64) ([]*services.RoomResponse, error) {
	return m.getUserRoomsFunc(ctx, userID)
}
func (m *MockRoomService) GetRoom(ctx context.Context, roomID int64) (*services.RoomResponse, error) {
	return m.getRoomFunc(ctx, roomID)
}
func (m *MockRoomService) AddMember(ctx context.Context, roomID, userID int64) error {
	return m.addMemberFunc(ctx, roomID, userID)
}
func (m *MockRoomService) GetRoomHistory(ctx context.Context, roomID uuid.UUID, limit, offset int) ([]services.ChatMessageResponse, error) {
	return m.getRoomHistoryFunc(ctx, roomID, limit, offset)
}


func setupTestRoomHandler(t *testing.T, mockRoomService *MockRoomService) (*RoomHandler, *gin.Engine) {
	gin.SetMode(gin.TestMode)
	logger := zap.NewNop()

	handler := NewRoomHandler(mockRoomService, logger)

	r := gin.New()
	r.Use(func(c *gin.Context) {
		c.Set("user_id", int64(1)) // Simulate authenticated user
		c.Next()
	})

	chatRoutes := r.Group("/conversations")
	{
		chatRoutes.POST("", handler.CreateRoom)
		chatRoutes.GET("", handler.GetUserRooms)
		chatRoutes.GET("/:id", handler.GetRoom)
		chatRoutes.POST("/:id/members", handler.AddMember)
		chatRoutes.GET("/:id/history", handler.GetRoomHistory)
	}

	return handler, r
}

func TestRoomHandler_CreateRoom_Success(t *testing.T) {
	mockService := &MockRoomService{
		createRoomFunc: func(ctx context.Context, userID int64, req services.CreateRoomRequest) (*services.RoomResponse, error) {
			return &services.RoomResponse{
				ID:        1,
				Name:      req.Name,
				Type:      req.Type,
				CreatedBy: userID,
			}, nil
		},
	}
	_, r := setupTestRoomHandler(t, mockService)

	body := gin.H{"name": "New Room", "type": "public"}
	reqBody, _ := json.Marshal(body)

	req := httptest.NewRequest(http.MethodPost, "/conversations", bytes.NewBuffer(reqBody))
	req.Header.Set("Content-Type", "application/json")
	w := httptest.NewRecorder()
	r.ServeHTTP(w, req)

	assert.Equal(t, http.StatusCreated, w.Code)
	var response services.RoomResponse
	err := json.Unmarshal(w.Body.Bytes(), &response)
	assert.NoError(t, err)
	assert.Equal(t, "New Room", response.Name)
}

func TestRoomHandler_CreateRoom_Unauthorized(t *testing.T) {
	mockService := &MockRoomService{}
	logger := zap.NewNop()
	handler := NewRoomHandler(mockService, logger)

	r := gin.New()
	r.POST("/conversations", handler.CreateRoom) // No auth middleware

	body := gin.H{"name": "New Room", "type": "public"}
	reqBody, _ := json.Marshal(body)

	req := httptest.NewRequest(http.MethodPost, "/conversations", bytes.NewBuffer(reqBody))
	req.Header.Set("Content-Type", "application/json")
	w := httptest.NewRecorder()
	r.ServeHTTP(w, req)

	assert.Equal(t, http.StatusUnauthorized, w.Code)
}

func TestRoomHandler_GetRoomHistory_Success(t *testing.T) {
	convID := uuid.New()
	mockMessages := []services.ChatMessageResponse{
		{ID: uuid.New(), ConversationID: convID, SenderID: uuid.New(), Content: "Msg 1"},
		{ID: uuid.New(), ConversationID: convID, SenderID: uuid.New(), Content: "Msg 2"},
	}
	mockService := &MockRoomService{
		getRoomHistoryFunc: func(ctx context.Context, roomID uuid.UUID, limit, offset int) ([]services.ChatMessageResponse, error) {
			assert.Equal(t, convID, roomID)
			assert.Equal(t, 50, limit)
			assert.Equal(t, 0, offset)
			return mockMessages, nil
		},
	}
	_, r := setupTestRoomHandler(t, mockService)

	req := httptest.NewRequest(http.MethodGet, fmt.Sprintf("/conversations/%s/history", convID.String()), nil)
	w := httptest.NewRecorder()
	r.ServeHTTP(w, req)

	assert.Equal(t, http.StatusOK, w.Code)
	var response struct {
		Messages []services.ChatMessageResponse `json:"messages"`
	}
	err := json.Unmarshal(w.Body.Bytes(), &response)
	assert.NoError(t, err)
	assert.Len(t, response.Messages, 2)
	assert.Equal(t, "Msg 1", response.Messages[0].Content)
}

func TestRoomHandler_GetRoomHistory_InvalidID(t *testing.T) {
	mockService := &MockRoomService{}
	_, r := setupTestRoomHandler(t, mockService)

	req := httptest.NewRequest(http.MethodGet, "/conversations/invalid-uuid/history", nil)
	w := httptest.NewRecorder()
	r.ServeHTTP(w, req)

	assert.Equal(t, http.StatusBadRequest, w.Code)
}

func TestRoomHandler_GetRoomHistory_NotFound(t *testing.T) {
	convID := uuid.New()
	mockService := &MockRoomService{
		getRoomHistoryFunc: func(ctx context.Context, roomID uuid.UUID, limit, offset int) ([]services.ChatMessageResponse, error) {
			return nil, gorm.ErrRecordNotFound
		},
	}
	_, r := setupTestRoomHandler(t, mockService)

	req := httptest.NewRequest(http.MethodGet, fmt.Sprintf("/conversations/%s/history", convID.String()), nil)
	w := httptest.NewRecorder()
	r.ServeHTTP(w, req)

	assert.Equal(t, http.StatusInternalServerError, w.Code) // RoomService should return internal error, not notfound
}

func TestRoomHandler_GetUserRooms_Success(t *testing.T) {
	mockService := &MockRoomService{
		getUserRoomsFunc: func(ctx context.Context, userID int64) ([]*services.RoomResponse, error) {
			return []*services.RoomResponse{
				{ID: 1, Name: "Room 1"},
				{ID: 2, Name: "Room 2"},
			}, nil
		},
	}
	_, r := setupTestRoomHandler(t, mockService)

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

	assert.Equal(t, http.StatusOK, w.Code)
	var response struct {
		Conversations []*services.RoomResponse `json:"conversations"`
		Total int `json:"total"`
	}
	err := json.Unmarshal(w.Body.Bytes(), &response)
	assert.NoError(t, err)
	assert.Len(t, response.Conversations, 2)
}
