package services

import (
	"context"
	"testing"
	"time"

	"veza-backend-api/internal/models"
	"veza-backend-api/internal/repositories"

	"github.com/google/uuid"
	"github.com/stretchr/testify/assert"
	"github.com/stretchr/testify/require"
	"go.uber.org/zap"
	"gorm.io/driver/sqlite"
	"gorm.io/gorm"
)

func setupTestRoomService(t *testing.T) (*RoomService, *gorm.DB) {
	db, err := gorm.Open(sqlite.Open(":memory:"), &gorm.Config{})
	require.NoError(t, err)

	// Enable foreign keys
	db.Exec("PRAGMA foreign_keys = ON")

	err = db.AutoMigrate(&models.User{}, &models.Room{}, &models.RoomMember{}, &models.Message{})
	require.NoError(t, err)

	logger := zap.NewNop()
	roomRepo := repositories.NewRoomRepository(db)
	messageRepo := repositories.NewChatMessageRepository(db)
	service := NewRoomService(roomRepo, messageRepo, logger)

	return service, db
}

func createTestUserForRoom(t *testing.T, db *gorm.DB, username string) *models.User {
	user := &models.User{
		ID:           uuid.New(),
		Username:     username,
		Email:        username + "@example.com",
		PasswordHash: "hash",
		IsActive:     true,
	}
	err := db.Create(user).Error
	require.NoError(t, err)
	return user
}

func TestRoomService_CreateRoom(t *testing.T) {
	service, db := setupTestRoomService(t)
	user := createTestUserForRoom(t, db, "user1")

	req := CreateRoomRequest{
		Name:      "Test Room",
		Type:      "public",
		IsPrivate: false,
	}

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

	// Verify room created in DB
	var createdRoom models.Room
	err = db.First(&createdRoom, "id = ?", room.ID).Error
	assert.NoError(t, err)
	assert.Equal(t, room.ID, createdRoom.ID)
}

func TestRoomService_GetUserRooms(t *testing.T) {
	service, db := setupTestRoomService(t)
	user1 := createTestUserForRoom(t, db, "user1")
	user2 := createTestUserForRoom(t, db, "user2")

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

	room1, err := service.CreateRoom(context.Background(), user1.ID, roomReq1)
	require.NoError(t, err)
	room2, err := service.CreateRoom(context.Background(), user2.ID, roomReq2)
	require.NoError(t, err)

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

	rooms, err := service.GetUserRooms(context.Background(), user1.ID)
	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) {
	service, db := setupTestRoomService(t)
	user := createTestUserForRoom(t, db, "user1")

	// Create a room
	roomReq := CreateRoomRequest{Name: "History Room", Type: "public", IsPrivate: false}
	room, err := service.CreateRoom(context.Background(), user.ID, roomReq)
	require.NoError(t, err)

	// Add messages to DB
	msgs := []models.Message{
		{ID: uuid.New(), RoomID: room.ID, UserID: user.ID, Content: "Hello 1", CreatedAt: time.Now().Add(-2 * time.Minute)},
		{ID: uuid.New(), RoomID: room.ID, UserID: user.ID, Content: "Hello 2", CreatedAt: time.Now().Add(-1 * time.Minute)},
		{ID: uuid.New(), RoomID: room.ID, UserID: user.ID, Content: "Hello 3", CreatedAt: time.Now()},
	}
	for _, msg := range msgs {
		db.Create(&msg)
	}

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

	history, err = service.GetRoomHistory(context.Background(), room.ID, 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) {
	service, db := setupTestRoomService(t)
	user := createTestUserForRoom(t, db, "user1")

	req := CreateRoomRequest{Name: "Single Room", Type: "public", IsPrivate: false}
	createdRoom, err := service.CreateRoom(context.Background(), user.ID, req)
	require.NoError(t, err)

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

func TestRoomService_GetRoom_NotFound(t *testing.T) {
	service, _ := setupTestRoomService(t)

	_, err := service.GetRoom(context.Background(), uuid.New())
	assert.Error(t, err)
	// GORM RecordNotFound might be wrapped or returned as error
	// Implementation returns fmt.Errorf("failed to get room: %w", err)
	// So we assume it errors out.
}

func TestRoomService_AddMember_Success(t *testing.T) {
	service, db := setupTestRoomService(t)
	user1 := createTestUserForRoom(t, db, "user1")
	user2 := createTestUserForRoom(t, db, "user2")

	roomReq := CreateRoomRequest{Name: "Member Room", Type: "public", IsPrivate: false}
	room, err := service.CreateRoom(context.Background(), user1.ID, roomReq)
	require.NoError(t, err)

	err = service.AddMember(context.Background(), room.ID, user2.ID)
	assert.NoError(t, err)

	// Verify members in DB
	var members []models.RoomMember
	db.Where("room_id = ?", room.ID).Find(&members)
	assert.Len(t, members, 2) // Owner + New Member

	var foundUser2 bool
	for _, m := range members {
		if m.UserID == user2.ID {
			foundUser2 = true
		}
	}
	assert.True(t, foundUser2)
}

func TestRoomService_RemoveMember_Success(t *testing.T) {
	service, db := setupTestRoomService(t)
	user1 := createTestUserForRoom(t, db, "user1")
	user2 := createTestUserForRoom(t, db, "user2")

	roomReq := CreateRoomRequest{Name: "Remove Member Room", Type: "public", IsPrivate: false}
	room, err := service.CreateRoom(context.Background(), user1.ID, roomReq)
	require.NoError(t, err)

	err = service.AddMember(context.Background(), room.ID, user2.ID)
	require.NoError(t, err)

	err = service.RemoveMember(context.Background(), room.ID, user2.ID)
	assert.NoError(t, err)

	// Verify member removed
	var count int64
	db.Model(&models.RoomMember{}).Where("room_id = ? AND user_id = ?", room.ID, user2.ID).Count(&count)
	assert.Equal(t, int64(0), count)
}

func TestRoomService_UpdateRoom_Success(t *testing.T) {
	service, db := setupTestRoomService(t)
	user := createTestUserForRoom(t, db, "user1")

	req := CreateRoomRequest{Name: "Original Name", Type: "public", IsPrivate: false}
	room, err := service.CreateRoom(context.Background(), user.ID, req)
	require.NoError(t, err)

	newName := "Updated Name"
	newDesc := "Updated Desc"
	updateReq := UpdateRoomRequest{Name: &newName, Description: &newDesc}

	updatedRoom, err := service.UpdateRoom(context.Background(), room.ID, user.ID, updateReq)
	assert.NoError(t, err)
	assert.Equal(t, newName, updatedRoom.Name)
	assert.Equal(t, newDesc, updatedRoom.Description)
}

func TestRoomService_UpdateRoom_Forbidden(t *testing.T) {
	service, db := setupTestRoomService(t)
	owner := createTestUserForRoom(t, db, "owner")
	other := createTestUserForRoom(t, db, "other")

	req := CreateRoomRequest{Name: "Owner Room", Type: "public", IsPrivate: false}
	room, err := service.CreateRoom(context.Background(), owner.ID, req)
	require.NoError(t, err)

	newName := "Hacked Name"
	updateReq := UpdateRoomRequest{Name: &newName}

	_, err = service.UpdateRoom(context.Background(), room.ID, other.ID, updateReq)
	assert.Error(t, err)
	assert.Contains(t, err.Error(), "forbidden")
}

func TestRoomService_DeleteRoom_Success(t *testing.T) {
	service, db := setupTestRoomService(t)
	user := createTestUserForRoom(t, db, "user1")

	req := CreateRoomRequest{Name: "Delete Me", Type: "public", IsPrivate: false}
	room, err := service.CreateRoom(context.Background(), user.ID, req)
	require.NoError(t, err)

	err = service.DeleteRoom(context.Background(), room.ID, user.ID)
	assert.NoError(t, err)

	// Verify soft delete
	var deletedRoom models.Room
	err = db.First(&deletedRoom, "id = ?", room.ID).Error
	assert.ErrorIs(t, err, gorm.ErrRecordNotFound) // Should not find with default scope (which excludes deleted)

	// Verify it still exists in DB but with DeletedAt set (Unscoped)
	err = db.Unscoped().First(&deletedRoom, "id = ?", room.ID).Error
	assert.NoError(t, err)
	assert.NotNil(t, deletedRoom.DeletedAt)
}

func TestRoomService_DeleteRoom_Forbidden(t *testing.T) {
	service, db := setupTestRoomService(t)
	owner := createTestUserForRoom(t, db, "owner")
	other := createTestUserForRoom(t, db, "other")

	req := CreateRoomRequest{Name: "Safe Room", Type: "public", IsPrivate: false}
	room, err := service.CreateRoom(context.Background(), owner.ID, req)
	require.NoError(t, err)

	err = service.DeleteRoom(context.Background(), room.ID, other.ID)
	assert.Error(t, err)
	assert.Contains(t, err.Error(), "forbidden")
}
