package services

import (
	"context"
	"fmt"
	"testing"
	"time"

	"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/repositories"
)

type MockRoomRepository struct {
	rooms map[uuid.UUID]*models.Room
	members map[uuid.UUID][]*models.RoomMember
}

func NewMockRoomRepository() *MockRoomRepository {
	return &MockRoomRepository{
		rooms: make(map[uuid.UUID]*models.Room),
		members: make(map[uuid.UUID][]*models.RoomMember),
	}
}

func (m *MockRoomRepository) Create(ctx context.Context, room *models.Room) error {
	room.ID = uuid.New() // Generate new UUID
	room.CreatedAt = time.Now()
	room.UpdatedAt = time.Now()
	m.rooms[room.ID] = room
	return nil
}

func (m *MockRoomRepository) GetByID(ctx context.Context, id uuid.UUID) (*models.Room, error) {
	room, ok := m.rooms[id]
	if !ok {
		return nil, gorm.ErrRecordNotFound
	}
	return room, nil
}

func (m *MockRoomRepository) GetByUserID(ctx context.Context, userID int64) ([]*models.Room, error) {
	var userRooms []*models.Room
	for _, room := range m.rooms {
		// In a real scenario, this would query room_members.
		// For mock, we'll assume a direct match for now.
		// This mock is simplified and doesn't fully simulate the join logic of a real repo.
		// We'll rely on the AddMember mock below to add members correctly.
		if _, ok := m.members[room.ID]; ok {
			for _, member := range m.members[room.ID] {
				if member.UserID == userID {
					userRooms = append(userRooms, room)
					break
				}
			}
		}
	}
	return userRooms, nil
}

func (m *MockRoomRepository) AddMember(ctx context.Context, member *models.RoomMember) error {
	// If the member ID is not set, generate it
	if member.ID == uuid.Nil {
		// This is a mock internal ID, actual GORM might auto-increment
		member.ID = int64(len(m.members[member.RoomID]) + 1)
	}
	m.members[member.RoomID] = append(m.members[member.RoomID], member)
	return nil
}

func (m *MockRoomRepository) GetMembersByRoomID(ctx context.Context, roomID uuid.UUID) ([]*models.RoomMember, error) {
	return m.members[roomID], nil
}

func (m *MockRoomRepository) Update(ctx context.Context, room *models.Room) error { panic("not implemented") }
func (m *MockRoomRepository) Delete(ctx context.Context, id uuid.UUID) error { panic("not implemented") }
func (m *MockRoomRepository) RemoveMember(ctx context.Context, roomID uuid.UUID, userID int64) error { panic("not implemented") }


type MockChatMessageRepository struct {
	messages []models.ChatMessage
}

func NewMockChatMessageRepository() *MockChatMessageRepository {
	return &MockChatMessageRepository{
		messages: make([]models.ChatMessage, 0),
	}
}

func (m *MockChatMessageRepository) GetConversationMessages(ctx context.Context, conversationID uuid.UUID, limit, offset int) ([]models.ChatMessage, error) {
	var filtered []models.ChatMessage
	for _, msg := range m.messages {
		if msg.ConversationID == conversationID {
			filtered = append(filtered, msg)
		}
	}
	// Simple reverse order and limit/offset for mock
	// Order by CreatedAt DESC
	if len(filtered) > 1 {
		for i := 0; i < len(filtered)/2; i++ {
			filtered[i], filtered[len(filtered)-1-i] = filtered[len(filtered)-1-i], filtered[i]
		}
	}
	
	start := offset
	end := offset + limit
	if start > len(filtered) {
		start = len(filtered)
	}
	if end > len(filtered) {
		end = len(filtered)
	}
	
	return filtered[start:end], nil
}


func TestRoomService_CreateRoom(t *testing.T) {
	logger := zap.NewNop()
	roomRepo := NewMockRoomRepository()
	messageRepo := NewMockChatMessageRepository() // Not used in this test
	service := NewRoomService(roomRepo, messageRepo, logger)

	userID := int64(1)
	req := CreateRoomRequest{
		Name:      "Test Room",
		Type:      "public",
		IsPrivate: false,
	}

	room, err := service.CreateRoom(context.Background(), userID, req)
	assert.NoError(t, err)
	assert.NotNil(t, room)
	assert.Equal(t, req.Name, room.Name)
	assert.Contains(t, room.Participants, userID)

	// Verify room created in repo
	createdRoom, _ := roomRepo.GetByID(context.Background(), room.ID)
	assert.NotNil(t, createdRoom)
	assert.Equal(t, room.ID, createdRoom.ID) // Check UUID match
}

func TestRoomService_GetUserRooms(t *testing.T) {
	logger := zap.NewNop()
	roomRepo := NewMockRoomRepository()
	messageRepo := NewMockChatMessageRepository()
	service := NewRoomService(roomRepo, messageRepo, logger)

	userID := int64(1)
	userID2 := int64(2)

	roomReq1 := CreateRoomRequest{Name: "Room 1", Type: "public", IsPrivate: false}
	roomReq2 := CreateRoomRequest{Name: "Room 2", Type: "private", IsPrivate: true}

	room1, _ := service.CreateRoom(context.Background(), userID, roomReq1)
	room2, _ := service.CreateRoom(context.Background(), userID2, roomReq2)

	// User 1 joins room 2
	err := service.AddMember(context.Background(), room2.ID, userID)
	assert.NoError(t, err)

	rooms, err := service.GetUserRooms(context.Background(), userID)
	assert.NoError(t, err)
	assert.Len(t, rooms, 2) // Should contain Room 1 and Room 2
	
	// Check content
	var foundRoom1, foundRoom2 bool
	for _, r := range rooms {
		if r.ID == room1.ID {
			foundRoom1 = true
		}
		if r.ID == room2.ID {
			foundRoom2 = true
		}
	}
	assert.True(t, foundRoom1)
	assert.True(t, foundRoom2)
}

func TestRoomService_GetRoomHistory(t *testing.T) {
	logger := zap.NewNop()
	roomRepo := NewMockRoomRepository()
	mockMessageRepo := NewMockChatMessageRepository()
	service := NewRoomService(roomRepo, mockMessageRepo, logger)

	// Create a dummy conversation ID
	convID := uuid.New()
	
	// Create a room first to simulate existence
	roomReq := CreateRoomRequest{Name: "History Room", Type: "public", IsPrivate: false}
	_, _ = service.CreateRoom(context.Background(), int64(1), roomReq)

	// Add mock messages
	mockMessageRepo.messages = []models.ChatMessage{
		{ID: uuid.New(), ConversationID: convID, SenderID: uuid.New(), Content: "Hello 1", CreatedAt: time.Now().Add(-2 * time.Minute)},
		{ID: uuid.New(), ConversationID: convID, SenderID: uuid.New(), Content: "Hello 2", CreatedAt: time.Now().Add(-1 * time.Minute)},
		{ID: uuid.New(), ConversationID: convID, SenderID: uuid.New(), Content: "Hello 3", CreatedAt: time.Now()},
	}

	history, err := service.GetRoomHistory(context.Background(), convID, 10, 0)
	assert.NoError(t, err)
	assert.Len(t, history, 3)
	assert.Equal(t, "Hello 3", history[0].Content) // Should be ordered by created_at DESC

	history, err = service.GetRoomHistory(context.Background(), convID, 1, 1) // limit 1, offset 1
	assert.NoError(t, err)
	assert.Len(t, history, 1)
	assert.Equal(t, "Hello 2", history[0].Content)
}

func TestRoomService_GetRoom_Success(t *testing.T) {
	logger := zap.NewNop()
	roomRepo := NewMockRoomRepository()
	messageRepo := NewMockChatMessageRepository()
	service := NewRoomService(roomRepo, messageRepo, logger)

	userID := int64(1)
	req := CreateRoomRequest{Name: "Single Room", Type: "public", IsPrivate: false}
	createdRoom, _ := service.CreateRoom(context.Background(), userID, req)

	retrievedRoom, err := service.GetRoom(context.Background(), createdRoom.ID)
	assert.NoError(t, err)
	assert.NotNil(t, retrievedRoom)
	assert.Equal(t, createdRoom.ID, retrievedRoom.ID)
	assert.Equal(t, "Single Room", retrievedRoom.Name)
}

func TestRoomService_GetRoom_NotFound(t *testing.T) {
	logger := zap.NewNop()
	roomRepo := NewMockRoomRepository()
	messageRepo := NewMockChatMessageRepository()
	service := NewRoomService(roomRepo, messageRepo, logger)

	_, err := service.GetRoom(context.Background(), uuid.New())
	assert.Error(t, err)
	assert.Equal(t, "playlist not found", err.Error()) // Gorm returns playlist not found here
}

func TestRoomService_AddMember_Success(t *testing.T) {
	logger := zap.NewNop()
	roomRepo := NewMockRoomRepository()
	messageRepo := NewMockChatMessageRepository()
	service := NewRoomService(roomRepo, messageRepo, logger)

	userID := int64(1)
	roomReq := CreateRoomRequest{Name: "Member Room", Type: "public", IsPrivate: false}
	room, _ := service.CreateRoom(context.Background(), userID, roomReq)

	newMemberID := int64(2)
	err := service.AddMember(context.Background(), room.ID, newMemberID)
	assert.NoError(t, err)

	members, _ := roomRepo.GetMembersByRoomID(context.Background(), room.ID)
	assert.Len(t, members, 2) // Original creator + new member
	assert.Equal(t, newMemberID, members[1].UserID)
}