package user

import (
	"database/sql"
	"regexp"
	"testing"
	"time"

	"github.com/DATA-DOG/go-sqlmock"
	"github.com/google/uuid"
	"github.com/stretchr/testify/assert"
	"github.com/stretchr/testify/require"

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

// Helper to setup mock DB
func setupMockDB(t *testing.T) (*database.DB, sqlmock.Sqlmock) {
	db, mock, err := sqlmock.New()
	require.NoError(t, err)

	dbWrapper := &database.DB{
		DB: db,
	}

	return dbWrapper, mock
}

func TestService_GetUserByID_Success(t *testing.T) {
	dbWrapper, mock := setupMockDB(t)
	defer dbWrapper.Close()

	service := NewService(dbWrapper)
	userID := uuid.New()

	expectedUser := UserResponse{
		ID:        userID,
		Username:  "testuser",
		Email:     "test@example.com",
		Role:      "user",
		IsActive:  true,
		CreatedAt: time.Now(),
	}

	rows := sqlmock.NewRows([]string{"id", "email", "first_name", "last_name", "username", "avatar", "bio",
		"role", "is_active", "is_verified", "last_login_at", "created_at", "updated_at"}).
		AddRow(userID, expectedUser.Email, nil, nil, expectedUser.Username, nil, nil,
			expectedUser.Role, expectedUser.IsActive, false, nil, expectedUser.CreatedAt, time.Now())

	mock.ExpectQuery(regexp.QuoteMeta(`SELECT id, email, first_name, last_name, username, avatar, bio,
			   role, is_active, is_verified, last_login_at, created_at, updated_at
		FROM users 
		WHERE id = $1 AND is_active = true`)).
		WithArgs(userID).
		WillReturnRows(rows)

	// Execute
	user, err := service.GetUserByID(userID)

	// Assert
	assert.NoError(t, err)
	assert.NotNil(t, user)
	assert.Equal(t, userID, user.ID)
	assert.Equal(t, "testuser", user.Username)
	assert.NoError(t, mock.ExpectationsWereMet())
}

func TestService_GetUserByID_NotFound(t *testing.T) {
	dbWrapper, mock := setupMockDB(t)
	defer dbWrapper.Close()

	service := NewService(dbWrapper)
	userID := uuid.New()

	mock.ExpectQuery(regexp.QuoteMeta(`FROM users`)).
		WithArgs(userID).
		WillReturnError(sql.ErrNoRows)

	// Execute
	user, err := service.GetUserByID(userID)

	// Assert
	assert.Error(t, err)
	assert.Equal(t, "user not found", err.Error())
	assert.Nil(t, user)
}

func TestService_CreateUser_Success(t *testing.T) {
	dbWrapper, mock := setupMockDB(t)
	defer dbWrapper.Close()

	service := NewService(dbWrapper)

	req := CreateUserRequest{
		Username: "newuser",
		Email:    "new@example.com",
		Password: "password123",
	}

	userID := uuid.New()

	rows := sqlmock.NewRows([]string{"id", "email", "first_name", "last_name", "username", "role", "is_active", "is_verified", "created_at", "updated_at"}).
		AddRow(userID, req.Email, nil, nil, req.Username, "user", true, false, time.Now(), time.Now())

	mock.ExpectQuery(regexp.QuoteMeta(`INSERT INTO users`)).
		WithArgs(req.Email, sqlmock.AnyArg(), "", "", req.Username, "user").
		WillReturnRows(rows)

	// Execute
	user, err := service.CreateUser(req)

	// Assert
	assert.NoError(t, err)
	assert.NotNil(t, user)
	assert.Equal(t, userID, user.ID)
	assert.Equal(t, "newuser", user.Username)
}

func TestService_DeleteUser_Success(t *testing.T) {
	dbWrapper, mock := setupMockDB(t)
	defer dbWrapper.Close()

	service := NewService(dbWrapper)
	userID := uuid.New()

	mock.ExpectExec(regexp.QuoteMeta(`UPDATE users SET is_active = false`)).
		WithArgs(userID).
		WillReturnResult(sqlmock.NewResult(1, 1))

	// Execute
	err := service.DeleteUser(userID)

	// Assert
	assert.NoError(t, err)
	assert.NoError(t, mock.ExpectationsWereMet())
}

func TestService_DeleteUser_NotFound(t *testing.T) {
	dbWrapper, mock := setupMockDB(t)
	defer dbWrapper.Close()

	service := NewService(dbWrapper)
	userID := uuid.New()

	mock.ExpectExec(regexp.QuoteMeta(`UPDATE users SET is_active = false`)).
		WithArgs(userID).
		WillReturnResult(sqlmock.NewResult(1, 0)) // 0 rows affected

	// Execute
	err := service.DeleteUser(userID)

	// Assert
	assert.Error(t, err)
	assert.Equal(t, "user not found", err.Error())
}

func TestService_RecoverAccount_NotFound(t *testing.T) {
	dbWrapper, mock := setupMockDB(t)
	defer dbWrapper.Close()

	service := NewService(dbWrapper)
	email := "deleted@example.com"

	mock.ExpectQuery(regexp.QuoteMeta(`SELECT id, password_hash, deleted_at`)).
		WithArgs(email).
		WillReturnError(sql.ErrNoRows)

	err := service.RecoverAccount(email, "password")

	assert.Error(t, err)
	assert.Equal(t, "no deleted account found for this email", err.Error())
}

func TestService_UpdateUser_Success(t *testing.T) {
	dbWrapper, mock := setupMockDB(t)
	defer dbWrapper.Close()

	service := NewService(dbWrapper)
	userID := uuid.New()

	newFirst := "Jane"
	newLast := "Doe"
	req := UpdateUserRequest{
		FirstName: &newFirst,
		LastName:  &newLast,
	}

	expectedUser := UserResponse{
		ID:        userID,
		Username:  "testuser",
		Email:     "test@example.com",
		FirstName: sql.NullString{String: "Jane", Valid: true},
		LastName:  sql.NullString{String: "Doe", Valid: true},
		UpdatedAt: time.Now(),
	}

	rows := sqlmock.NewRows([]string{"id", "email", "first_name", "last_name", "username", "avatar", "bio",
		"role", "is_active", "is_verified", "last_login_at", "created_at", "updated_at"}).
		AddRow(userID, expectedUser.Email, expectedUser.FirstName, expectedUser.LastName, expectedUser.Username, nil, nil,
			"user", true, false, nil, time.Now(), expectedUser.UpdatedAt)

	mock.ExpectQuery(regexp.QuoteMeta(`UPDATE users`)).
		WithArgs("Jane", "Doe", userID).
		WillReturnRows(rows)

	user, err := service.UpdateUser(userID, req)

	assert.NoError(t, err)
	assert.NotNil(t, user)
	assert.Equal(t, "Jane", user.FirstName.String)
	assert.NoError(t, mock.ExpectationsWereMet())
}

func TestService_GetUsers_Success(t *testing.T) {
	dbWrapper, mock := setupMockDB(t)
	defer dbWrapper.Close()

	service := NewService(dbWrapper)

	// Mock count query
	mock.ExpectQuery(regexp.QuoteMeta(`SELECT COUNT(*) FROM users`)).
		WillReturnRows(sqlmock.NewRows([]string{"count"}).AddRow(2))

	// Mock select query
	rows := sqlmock.NewRows([]string{"id", "email", "first_name", "last_name", "username", "avatar", "bio",
		"role", "is_active", "is_verified", "last_login_at", "created_at", "updated_at"}).
		AddRow(uuid.New(), "user1@example.com", nil, nil, "user1", nil, nil, "user", true, false, nil, time.Now(), time.Now()).
		AddRow(uuid.New(), "user2@example.com", nil, nil, "user2", nil, nil, "user", true, false, nil, time.Now(), time.Now())

	mock.ExpectQuery(regexp.QuoteMeta(`SELECT id, email, first_name, last_name, username, avatar, bio, 
			   role, is_active, is_verified, last_login_at, created_at, updated_at
		FROM users`)).
		WithArgs(20, 0). // Limit 20, Offset 0
		WillReturnRows(rows)

	users, total, err := service.GetUsers(1, 20, "")

	assert.NoError(t, err)
	assert.Equal(t, 2, total)
	assert.Len(t, users, 2)
	assert.NoError(t, mock.ExpectationsWereMet())
}

func TestService_UpdateUserPreferences_Success(t *testing.T) {
	dbWrapper, mock := setupMockDB(t)
	defer dbWrapper.Close()

	service := NewService(dbWrapper)
	userID := uuid.New()

	// Expect GetUserPreferences first
	prefRows := sqlmock.NewRows([]string{"user_id", "theme", "language", "timezone", "notifications", "privacy", "audio", "updated_at"}).
		AddRow(userID, "light", "en", "UTC", "{}", "{}", "{}", time.Now())

	mock.ExpectQuery(regexp.QuoteMeta(`SELECT user_id`)).
		WithArgs(userID).
		WillReturnRows(prefRows)

	// Expect Upsert
	theme := "dark"
	req := UserPreferencesRequest{
		Theme: &theme,
	}

	mock.ExpectExec(regexp.QuoteMeta(`INSERT INTO user_preferences`)).
		WithArgs(userID, "dark", "en", "UTC", "{}", "{}", "{}", sqlmock.AnyArg()).
		WillReturnResult(sqlmock.NewResult(1, 1))

	pref, err := service.UpdateUserPreferences(userID, req)

	assert.NoError(t, err)
	assert.NotNil(t, pref)
	assert.Equal(t, "dark", pref.Theme)
	assert.NoError(t, mock.ExpectationsWereMet())
}

func TestService_ChangePassword_UserNotFound(t *testing.T) {
	dbWrapper, mock := setupMockDB(t)
	defer dbWrapper.Close()

	service := NewService(dbWrapper)
	userID := uuid.New()

	mock.ExpectQuery(regexp.QuoteMeta(`SELECT password_hash FROM users`)).
		WithArgs(userID).
		WillReturnError(sql.ErrNoRows)

	err := service.ChangePassword(userID, "old", "new")

	assert.Error(t, err)
	assert.Equal(t, "user not found", err.Error())
}

func TestService_GetUserStats_Success(t *testing.T) {
	dbWrapper, mock := setupMockDB(t)
	defer dbWrapper.Close()

	service := NewService(dbWrapper)

	// Expect 4 queries
	mock.ExpectQuery(regexp.QuoteMeta(`SELECT COUNT(*) FROM users WHERE is_active = true`)).
		WillReturnRows(sqlmock.NewRows([]string{"count"}).AddRow(100))

	mock.ExpectQuery(regexp.QuoteMeta(`SELECT COUNT(*) FROM users WHERE is_active = true AND is_verified = true`)).
		WillReturnRows(sqlmock.NewRows([]string{"count"}).AddRow(50))

	mock.ExpectQuery(regexp.QuoteMeta(`SELECT COUNT(*) FROM users`)). // Active users query
										WillReturnRows(sqlmock.NewRows([]string{"count"}).AddRow(10))

	mock.ExpectQuery(regexp.QuoteMeta(`SELECT COUNT(*) FROM users`)). // New users query
										WillReturnRows(sqlmock.NewRows([]string{"count"}).AddRow(5))

	stats, err := service.GetUserStats()

	assert.NoError(t, err)
	assert.Equal(t, 100, stats["total_users"])
	assert.Equal(t, 50, stats["verified_users"])
	assert.NoError(t, mock.ExpectationsWereMet())
}

func TestService_RequestDataDeletion_UserNotFound(t *testing.T) {
	dbWrapper, mock := setupMockDB(t)
	defer dbWrapper.Close()

	service := NewService(dbWrapper)
	userID := uuid.New()

	mock.ExpectQuery(regexp.QuoteMeta(`SELECT password_hash FROM users`)).
		WithArgs(userID).
		WillReturnError(sql.ErrNoRows)

	err := service.RequestDataDeletion(userID, "pass", "reason")

	assert.Error(t, err)
	assert.Equal(t, "user not found", err.Error())
}

func TestService_GetAccountStatus_Success(t *testing.T) {
	dbWrapper, mock := setupMockDB(t)
	defer dbWrapper.Close()

	service := NewService(dbWrapper)
	userID := uuid.New()

	rows := sqlmock.NewRows([]string{"id", "is_active", "is_verified", "created_at", "deleted_at", "deletion_reason", "recovery_deadline"}).
		AddRow(userID, true, true, time.Now(), nil, "", nil)

	mock.ExpectQuery(regexp.QuoteMeta(`SELECT id, is_active`)).
		WithArgs(userID).
		WillReturnRows(rows)

	status, err := service.GetAccountStatus(userID)

	assert.NoError(t, err)
	assert.Equal(t, "active", status.Status)
	assert.NoError(t, mock.ExpectationsWereMet())
}

func TestService_ExportUserData_Success(t *testing.T) {
	dbWrapper, mock := setupMockDB(t)
	defer dbWrapper.Close()

	service := NewService(dbWrapper)
	userID := uuid.New()

	// 1. GetUserByID
	userRows := sqlmock.NewRows([]string{"id", "email", "first_name", "last_name", "username", "avatar", "bio",
		"role", "is_active", "is_verified", "last_login_at", "created_at", "updated_at"}).
		AddRow(userID, "test@example.com", nil, nil, "test", nil, nil, "user", true, true, nil, time.Now(), time.Now())

	mock.ExpectQuery(regexp.QuoteMeta(`SELECT id`)).
		WithArgs(userID).
		WillReturnRows(userRows)

	// 2. GetUserPreferences
	prefRows := sqlmock.NewRows([]string{"user_id", "theme", "language", "timezone", "notifications", "privacy", "audio", "updated_at"}).
		AddRow(userID, "light", "en", "UTC", "{}", "{}", "{}", time.Now())

	mock.ExpectQuery(regexp.QuoteMeta(`SELECT user_id`)).
		WithArgs(userID).
		WillReturnRows(prefRows)

	export, err := service.ExportUserData(userID)

	assert.NoError(t, err)
	assert.NotNil(t, export)
	assert.Equal(t, userID, export.UserID)
	assert.NoError(t, mock.ExpectationsWereMet())
}
