package services

import (
	"context"
	"testing"

	"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"

	"veza-backend-api/internal/database"
)

func setupTestNotificationService(t *testing.T) (*NotificationService, *gorm.DB, *database.Database) {
	db, err := gorm.Open(sqlite.Open(":memory:"), &gorm.Config{})
	require.NoError(t, err)
	db.Exec("PRAGMA foreign_keys = ON")

	// Create notifications table manually
	err = db.Exec(`
		CREATE TABLE notifications (
			id TEXT PRIMARY KEY,
			user_id TEXT NOT NULL,
			type TEXT NOT NULL,
			title TEXT NOT NULL,
			content TEXT NOT NULL,
			link TEXT NOT NULL,
			read INTEGER NOT NULL DEFAULT 0,
			created_at TIMESTAMP DEFAULT CURRENT_TIMESTAMP
		)
	`).Error
	require.NoError(t, err)

	sqlDB, err := db.DB()
	require.NoError(t, err)

	testDB := &database.Database{
		DB: sqlDB,
	}

	logger := zap.NewNop()
	service := NewNotificationService(testDB, logger)

	return service, db, testDB
}

func TestNotificationService_CreateNotification_Success(t *testing.T) {
	service, _, _ := setupTestNotificationService(t)

	userID := uuid.New()
	notificationType := "test_type"
	title := "Test Title"
	content := "Test Content"
	link := "https://example.com"

	err := service.CreateNotification(userID, notificationType, title, content, link)
	assert.NoError(t, err)
}

func TestNotificationService_CreateNotification_EmptyFields(t *testing.T) {
	service, _, _ := setupTestNotificationService(t)

	userID := uuid.New()
	notificationType := ""
	title := ""
	content := ""
	link := ""

	// SQLite allows empty strings, so this should not error
	err := service.CreateNotification(userID, notificationType, title, content, link)
	assert.NoError(t, err)
}

func TestNotificationService_GetNotifications_AllNotifications(t *testing.T) {
	service, _, testDB := setupTestNotificationService(t)

	userID := uuid.New()
	ctx := context.Background()

	// Create test notifications
	for i := 0; i < 3; i++ {
		notificationID := uuid.New()
		_, err := testDB.ExecContext(ctx, `
			INSERT INTO notifications (id, user_id, type, title, content, link, read)
			VALUES ($1, $2, $3, $4, $5, $6, $7)
		`, notificationID.String(), userID.String(), "test_type", "Title", "Content", "", false)
		require.NoError(t, err)
	}

	notifications, err := service.GetNotifications(userID, false)
	assert.NoError(t, err)
	assert.Len(t, notifications, 3)
	assert.Equal(t, userID, notifications[0].UserID)
}

func TestNotificationService_GetNotifications_UnreadOnly(t *testing.T) {
	service, _, testDB := setupTestNotificationService(t)

	userID := uuid.New()
	ctx := context.Background()

	// Create read notification
	readID := uuid.New()
	_, err := testDB.ExecContext(ctx, `
		INSERT INTO notifications (id, user_id, type, title, content, link, read)
		VALUES ($1, $2, $3, $4, $5, $6, $7)
	`, readID.String(), userID.String(), "test_type", "Read Title", "Content", "", true)
	require.NoError(t, err)

	// Create unread notification
	unreadID := uuid.New()
	_, err = testDB.ExecContext(ctx, `
		INSERT INTO notifications (id, user_id, type, title, content, link, read)
		VALUES ($1, $2, $3, $4, $5, $6, $7)
	`, unreadID.String(), userID.String(), "test_type", "Unread Title", "Content", "", false)
	require.NoError(t, err)

	// Get all notifications
	allNotifications, err := service.GetNotifications(userID, false)
	assert.NoError(t, err)
	assert.Len(t, allNotifications, 2)

	// Get unread only
	unreadNotifications, err := service.GetNotifications(userID, true)
	assert.NoError(t, err)
	assert.Len(t, unreadNotifications, 1)
	assert.False(t, unreadNotifications[0].Read)
}

func TestNotificationService_GetNotifications_NoNotifications(t *testing.T) {
	service, _, _ := setupTestNotificationService(t)

	userID := uuid.New()

	notifications, err := service.GetNotifications(userID, false)
	assert.NoError(t, err)
	assert.Len(t, notifications, 0)
}

func TestNotificationService_GetNotifications_DifferentUsers(t *testing.T) {
	service, _, testDB := setupTestNotificationService(t)

	userID1 := uuid.New()
	userID2 := uuid.New()
	ctx := context.Background()

	// Create notification for user1
	notificationID1 := uuid.New()
	_, err := testDB.ExecContext(ctx, `
		INSERT INTO notifications (id, user_id, type, title, content, link, read)
		VALUES ($1, $2, $3, $4, $5, $6, $7)
	`, notificationID1.String(), userID1.String(), "test_type", "Title", "Content", "", false)
	require.NoError(t, err)

	// Create notification for user2
	notificationID2 := uuid.New()
	_, err = testDB.ExecContext(ctx, `
		INSERT INTO notifications (id, user_id, type, title, content, link, read)
		VALUES ($1, $2, $3, $4, $5, $6, $7)
	`, notificationID2.String(), userID2.String(), "test_type", "Title", "Content", "", false)
	require.NoError(t, err)

	// Get notifications for user1
	notifications1, err := service.GetNotifications(userID1, false)
	assert.NoError(t, err)
	assert.Len(t, notifications1, 1)
	assert.Equal(t, userID1, notifications1[0].UserID)

	// Get notifications for user2
	notifications2, err := service.GetNotifications(userID2, false)
	assert.NoError(t, err)
	assert.Len(t, notifications2, 1)
	assert.Equal(t, userID2, notifications2[0].UserID)
}

func TestNotificationService_MarkAsRead_Success(t *testing.T) {
	service, _, testDB := setupTestNotificationService(t)

	userID := uuid.New()
	notificationID := uuid.New()
	ctx := context.Background()

	// Create unread notification
	_, err := testDB.ExecContext(ctx, `
		INSERT INTO notifications (id, user_id, type, title, content, link, read)
		VALUES ($1, $2, $3, $4, $5, $6, $7)
	`, notificationID.String(), userID.String(), "test_type", "Title", "Content", "", false)
	require.NoError(t, err)

	// Mark as read
	err = service.MarkAsRead(userID, notificationID)
	assert.NoError(t, err)

	// Verify it's marked as read
	var read bool
	err = testDB.QueryRowContext(ctx, `
		SELECT read FROM notifications WHERE id = $1
	`, notificationID.String()).Scan(&read)
	assert.NoError(t, err)
	assert.True(t, read)
}

func TestNotificationService_MarkAsRead_WrongUser(t *testing.T) {
	service, _, testDB := setupTestNotificationService(t)

	userID1 := uuid.New()
	userID2 := uuid.New()
	notificationID := uuid.New()
	ctx := context.Background()

	// Create notification for user1
	_, err := testDB.ExecContext(ctx, `
		INSERT INTO notifications (id, user_id, type, title, content, link, read)
		VALUES ($1, $2, $3, $4, $5, $6, $7)
	`, notificationID.String(), userID1.String(), "test_type", "Title", "Content", "", false)
	require.NoError(t, err)

	// Try to mark as read with wrong user (should not error, but won't update)
	err = service.MarkAsRead(userID2, notificationID)
	assert.NoError(t, err)

	// Verify it's still unread (because user_id didn't match)
	var read bool
	err = testDB.QueryRowContext(ctx, `
		SELECT read FROM notifications WHERE id = $1
	`, notificationID.String()).Scan(&read)
	assert.NoError(t, err)
	assert.False(t, read)
}

func TestNotificationService_MarkAsRead_NonExistent(t *testing.T) {
	service, _, _ := setupTestNotificationService(t)

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

	// Try to mark non-existent notification as read (should not error)
	err := service.MarkAsRead(userID, notificationID)
	assert.NoError(t, err)
}

func TestNotificationService_MarkAllAsRead_Success(t *testing.T) {
	service, _, testDB := setupTestNotificationService(t)

	userID := uuid.New()
	ctx := context.Background()

	// Create multiple unread notifications
	for i := 0; i < 3; i++ {
		notificationID := uuid.New()
		_, err := testDB.ExecContext(ctx, `
			INSERT INTO notifications (id, user_id, type, title, content, link, read)
			VALUES ($1, $2, $3, $4, $5, $6, $7)
		`, notificationID.String(), userID.String(), "test_type", "Title", "Content", "", false)
		require.NoError(t, err)
	}

	// Mark all as read
	err := service.MarkAllAsRead(userID)
	assert.NoError(t, err)

	// Verify all are marked as read
	var count int
	err = testDB.QueryRowContext(ctx, `
		SELECT COUNT(*) FROM notifications WHERE user_id = $1 AND read = FALSE
	`, userID.String()).Scan(&count)
	assert.NoError(t, err)
	assert.Equal(t, 0, count)
}

func TestNotificationService_MarkAllAsRead_NoUnread(t *testing.T) {
	service, _, testDB := setupTestNotificationService(t)

	userID := uuid.New()
	ctx := context.Background()

	// Create read notification
	notificationID := uuid.New()
	_, err := testDB.ExecContext(ctx, `
		INSERT INTO notifications (id, user_id, type, title, content, link, read)
		VALUES ($1, $2, $3, $4, $5, $6, $7)
	`, notificationID.String(), userID.String(), "test_type", "Title", "Content", "", true)
	require.NoError(t, err)

	// Mark all as read (should not error even if none are unread)
	err = service.MarkAllAsRead(userID)
	assert.NoError(t, err)
}

func TestNotificationService_MarkAllAsRead_NoNotifications(t *testing.T) {
	service, _, _ := setupTestNotificationService(t)

	userID := uuid.New()

	// Mark all as read when user has no notifications (should not error)
	err := service.MarkAllAsRead(userID)
	assert.NoError(t, err)
}

func TestNotificationService_GetUnreadCount_Success(t *testing.T) {
	service, _, testDB := setupTestNotificationService(t)

	userID := uuid.New()
	ctx := context.Background()

	// Create unread notifications
	for i := 0; i < 3; i++ {
		notificationID := uuid.New()
		_, err := testDB.ExecContext(ctx, `
			INSERT INTO notifications (id, user_id, type, title, content, link, read)
			VALUES ($1, $2, $3, $4, $5, $6, $7)
		`, notificationID.String(), userID.String(), "test_type", "Title", "Content", "", false)
		require.NoError(t, err)
	}

	// Create read notification
	readID := uuid.New()
	_, err := testDB.ExecContext(ctx, `
		INSERT INTO notifications (id, user_id, type, title, content, link, read)
		VALUES ($1, $2, $3, $4, $5, $6, $7)
	`, readID.String(), userID.String(), "test_type", "Title", "Content", "", true)
	require.NoError(t, err)

	count, err := service.GetUnreadCount(userID)
	assert.NoError(t, err)
	assert.Equal(t, 3, count)
}

func TestNotificationService_GetUnreadCount_Zero(t *testing.T) {
	service, _, _ := setupTestNotificationService(t)

	userID := uuid.New()

	count, err := service.GetUnreadCount(userID)
	assert.NoError(t, err)
	assert.Equal(t, 0, count)
}

func TestNotificationService_GetUnreadCount_AfterMarkAllAsRead(t *testing.T) {
	service, _, testDB := setupTestNotificationService(t)

	userID := uuid.New()
	ctx := context.Background()

	// Create unread notifications
	for i := 0; i < 2; i++ {
		notificationID := uuid.New()
		_, err := testDB.ExecContext(ctx, `
			INSERT INTO notifications (id, user_id, type, title, content, link, read)
			VALUES ($1, $2, $3, $4, $5, $6, $7)
		`, notificationID.String(), userID.String(), "test_type", "Title", "Content", "", false)
		require.NoError(t, err)
	}

	// Mark all as read
	err := service.MarkAllAsRead(userID)
	assert.NoError(t, err)

	// Verify unread count is 0
	count, err := service.GetUnreadCount(userID)
	assert.NoError(t, err)
	assert.Equal(t, 0, count)
}

func TestNotificationService_GetUnreadCount_DifferentUsers(t *testing.T) {
	service, _, testDB := setupTestNotificationService(t)

	userID1 := uuid.New()
	userID2 := uuid.New()
	ctx := context.Background()

	// Create unread notification for user1
	notificationID1 := uuid.New()
	_, err := testDB.ExecContext(ctx, `
		INSERT INTO notifications (id, user_id, type, title, content, link, read)
		VALUES ($1, $2, $3, $4, $5, $6, $7)
	`, notificationID1.String(), userID1.String(), "test_type", "Title", "Content", "", false)
	require.NoError(t, err)

	// Create unread notification for user2
	notificationID2 := uuid.New()
	_, err = testDB.ExecContext(ctx, `
		INSERT INTO notifications (id, user_id, type, title, content, link, read)
		VALUES ($1, $2, $3, $4, $5, $6, $7)
	`, notificationID2.String(), userID2.String(), "test_type", "Title", "Content", "", false)
	require.NoError(t, err)

	// Get unread count for user1
	count1, err := service.GetUnreadCount(userID1)
	assert.NoError(t, err)
	assert.Equal(t, 1, count1)

	// Get unread count for user2
	count2, err := service.GetUnreadCount(userID2)
	assert.NoError(t, err)
	assert.Equal(t, 1, count2)
}
