package services

import (
	"database/sql"
	"testing"

	"authentication/db"

	"github.com/DATA-DOG/go-sqlmock"
)

func setupMockDB(t *testing.T) (sqlmock.Sqlmock, func()) {
	mockDB, mock, err := sqlmock.New()
	if err != nil {
		t.Fatalf("Failed to create mock database: %v", err)
	}

	originalDB := db.DB
	db.DB = mockDB

	cleanup := func() {
		mockDB.Close()
		db.DB = originalDB
	}

	return mock, cleanup
}

func TestGetUser(t *testing.T) {
	mock, cleanup := setupMockDB(t)
	defer cleanup()

	email := "test@example.com"
	expectedID := "user123"
	expectedFirstName := "John"
	expectedLastName := "Doe"
	expectedEmail := "test@example.com"

	rows := sqlmock.NewRows([]string{"id", "first_name", "last_name", "email_address"}).
		AddRow(expectedID, expectedFirstName, expectedLastName, expectedEmail)

	mock.ExpectQuery(`SELECT id, first_name, last_name, email_address FROM users WHERE email_address = \? AND is_deleted = 0 LIMIT 1`).
		WithArgs(email).
		WillReturnRows(rows)

	id, firstName, lastName, emailAddress, err := GetUser(email)

	if err != nil {
		t.Errorf("Expected no error, got: %v", err)
	}

	if id != expectedID {
		t.Errorf("Expected ID %s, got %s", expectedID, id)
	}

	if firstName == nil || *firstName != expectedFirstName {
		t.Errorf("Expected first name %s, got %v", expectedFirstName, firstName)
	}

	if lastName == nil || *lastName != expectedLastName {
		t.Errorf("Expected last name %s, got %v", expectedLastName, lastName)
	}

	if emailAddress != expectedEmail {
		t.Errorf("Expected email %s, got %s", expectedEmail, emailAddress)
	}

	if err := mock.ExpectationsWereMet(); err != nil {
		t.Errorf("Unfulfilled expectations: %v", err)
	}
}

func TestGetUserNotFound(t *testing.T) {
	mock, cleanup := setupMockDB(t)
	defer cleanup()

	email := "nonexistent@example.com"

	mock.ExpectQuery(`SELECT id, first_name, last_name, email_address FROM users WHERE email_address = \? AND is_deleted = 0 LIMIT 1`).
		WithArgs(email).
		WillReturnError(sql.ErrNoRows)

	id, firstName, lastName, emailAddress, err := GetUser(email)

	if err != sql.ErrNoRows {
		t.Errorf("Expected sql.ErrNoRows, got: %v", err)
	}

	if id != "" {
		t.Errorf("Expected empty ID, got %s", id)
	}

	if firstName != nil {
		t.Errorf("Expected nil firstName, got %v", firstName)
	}

	if lastName != nil {
		t.Errorf("Expected nil lastName, got %v", lastName)
	}

	if emailAddress != "" {
		t.Errorf("Expected empty email, got %s", emailAddress)
	}
}

func TestGetUserNullNames(t *testing.T) {
	mock, cleanup := setupMockDB(t)
	defer cleanup()

	email := "test@example.com"
	expectedID := "user456"

	rows := sqlmock.NewRows([]string{"id", "first_name", "last_name", "email_address"}).
		AddRow(expectedID, nil, nil, email)

	mock.ExpectQuery(`SELECT id, first_name, last_name, email_address FROM users WHERE email_address = \? AND is_deleted = 0 LIMIT 1`).
		WithArgs(email).
		WillReturnRows(rows)

	id, firstName, lastName, emailAddress, err := GetUser(email)

	if err != nil {
		t.Errorf("Expected no error, got: %v", err)
	}

	if id != expectedID {
		t.Errorf("Expected ID %s, got %s", expectedID, id)
	}

	if firstName != nil {
		t.Errorf("Expected nil firstName for NULL value, got %v", firstName)
	}

	if lastName != nil {
		t.Errorf("Expected nil lastName for NULL value, got %v", lastName)
	}

	if emailAddress != email {
		t.Errorf("Expected email %s, got %s", email, emailAddress)
	}
}

func TestGetUserID(t *testing.T) {
	mock, cleanup := setupMockDB(t)
	defer cleanup()

	email := "test@example.com"
	expectedID := "user789"

	rows := sqlmock.NewRows([]string{"id"}).
		AddRow(expectedID)

	// Note: The query has a typo "SELECT id, FROM" but we match it as-is
	mock.ExpectQuery(`SELECT id, FROM users WHERE email_address = \? AND is_deleted = 0 LIMIT 1`).
		WithArgs(email).
		WillReturnRows(rows)

	id, err := GetUserID(email)

	if err != nil {
		t.Errorf("Expected no error, got: %v", err)
	}

	if id != expectedID {
		t.Errorf("Expected ID %s, got %s", expectedID, id)
	}
}

func TestCheckEmailInDB(t *testing.T) {
	mock, cleanup := setupMockDB(t)
	defer cleanup()

	email := "existing@example.com"

	rows := sqlmock.NewRows([]string{"exists"}).
		AddRow(true)

	mock.ExpectQuery(`SELECT EXISTS \( SELECT 1 FROM users WHERE email_address = \? AND is_deleted = 0\)`).
		WithArgs(email).
		WillReturnRows(rows)

	exists, err := CheckEmailInDB(email)

	if err != nil {
		t.Errorf("Expected no error, got: %v", err)
	}

	if !exists {
		t.Error("Expected email to exist")
	}

	if err := mock.ExpectationsWereMet(); err != nil {
		t.Errorf("Unfulfilled expectations: %v", err)
	}
}

func TestCheckEmailInDBNotExists(t *testing.T) {
	mock, cleanup := setupMockDB(t)
	defer cleanup()

	email := "nonexistent@example.com"

	rows := sqlmock.NewRows([]string{"exists"}).
		AddRow(false)

	mock.ExpectQuery(`SELECT EXISTS \( SELECT 1 FROM users WHERE email_address = \? AND is_deleted = 0\)`).
		WithArgs(email).
		WillReturnRows(rows)

	exists, err := CheckEmailInDB(email)

	if err != nil {
		t.Errorf("Expected no error, got: %v", err)
	}

	if exists {
		t.Error("Expected email to not exist")
	}
}

func TestCheckEmailInDBError(t *testing.T) {
	mock, cleanup := setupMockDB(t)
	defer cleanup()

	email := "error@example.com"

	mock.ExpectQuery(`SELECT EXISTS \( SELECT 1 FROM users WHERE email_address = \? AND is_deleted = 0\)`).
		WithArgs(email).
		WillReturnError(sql.ErrConnDone)

	exists, err := CheckEmailInDB(email)

	if err == nil {
		t.Error("Expected error, got nil")
	}

	if exists {
		t.Error("Expected false when error occurs")
	}
}

func TestGetUserIDFromEmail(t *testing.T) {
	mock, cleanup := setupMockDB(t)
	defer cleanup()

	email := "test@example.com"
	expectedID := "user999"

	rows := sqlmock.NewRows([]string{"id"}).
		AddRow(expectedID)

	mock.ExpectQuery(`SELECT id FROM \( SELECT id, 1 AS priority FROM users WHERE email_address = \? AND is_deleted = 0 \) t ORDER BY priority ASC LIMIT 1`).
		WithArgs(email).
		WillReturnRows(rows)

	id, err := GetUserIDFromEmail(email)

	if err != nil {
		t.Errorf("Expected no error, got: %v", err)
	}

	if id != expectedID {
		t.Errorf("Expected ID %s, got %s", expectedID, id)
	}

	if err := mock.ExpectationsWereMet(); err != nil {
		t.Errorf("Unfulfilled expectations: %v", err)
	}
}

func TestGetUserIDFromEmailNotFound(t *testing.T) {
	mock, cleanup := setupMockDB(t)
	defer cleanup()

	email := "notfound@example.com"

	mock.ExpectQuery(`SELECT id FROM \( SELECT id, 1 AS priority FROM users WHERE email_address = \? AND is_deleted = 0 \) t ORDER BY priority ASC LIMIT 1`).
		WithArgs(email).
		WillReturnError(sql.ErrNoRows)

	id, err := GetUserIDFromEmail(email)

	if err == nil {
		t.Error("Expected error, got nil")
	}

	if id != "" {
		t.Errorf("Expected empty ID on error, got %s", id)
	}
}

func TestGetUserIDFromEmailDBError(t *testing.T) {
	mock, cleanup := setupMockDB(t)
	defer cleanup()

	email := "error@example.com"

	mock.ExpectQuery(`SELECT id FROM \( SELECT id, 1 AS priority FROM users WHERE email_address = \? AND is_deleted = 0 \) t ORDER BY priority ASC LIMIT 1`).
		WithArgs(email).
		WillReturnError(sql.ErrConnDone)

	id, err := GetUserIDFromEmail(email)

	if err == nil {
		t.Error("Expected error, got nil")
	}

	if err != sql.ErrConnDone {
		t.Errorf("Expected sql.ErrConnDone, got: %v", err)
	}

	if id != "" {
		t.Errorf("Expected empty ID on error, got %s", id)
	}
}

func TestGetUserMultipleEmails(t *testing.T) {
	mock, cleanup := setupMockDB(t)
	defer cleanup()

	testCases := []struct {
		email    string
		userID   string
		hasNames bool
	}{
		{"user1@example.com", "id1", true},
		{"user2@example.com", "id2", false},
		{"user3@example.com", "id3", true},
	}

	for _, tc := range testCases {
		t.Run(tc.email, func(t *testing.T) {
			var firstName, lastName interface{}
			if tc.hasNames {
				firstName = "First"
				lastName = "Last"
			} else {
				firstName = nil
				lastName = nil
			}

			rows := sqlmock.NewRows([]string{"id", "first_name", "last_name", "email_address"}).
				AddRow(tc.userID, firstName, lastName, tc.email)

			mock.ExpectQuery(`SELECT id, first_name, last_name, email_address FROM users WHERE email_address = \? AND is_deleted = 0 LIMIT 1`).
				WithArgs(tc.email).
				WillReturnRows(rows)

			id, fn, ln, email, err := GetUser(tc.email)

			if err != nil {
				t.Errorf("Expected no error, got: %v", err)
			}

			if id != tc.userID {
				t.Errorf("Expected ID %s, got %s", tc.userID, id)
			}

			if tc.hasNames {
				if fn == nil || ln == nil {
					t.Error("Expected names to be present")
				}
			} else {
				if fn != nil || ln != nil {
					t.Error("Expected names to be nil")
				}
			}

			if email != tc.email {
				t.Errorf("Expected email %s, got %s", tc.email, email)
			}
		})
	}
}

func TestCheckEmailInDBVariousEmails(t *testing.T) {
	testEmails := []string{
		"normal@example.com",
		"with+plus@example.com",
		"with.dot@example.com",
		"with-dash@example.com",
	}

	mock, cleanup := setupMockDB(t)
	defer cleanup()

	for i, email := range testEmails {
		exists := i%2 == 0 // Alternate between true and false

		rows := sqlmock.NewRows([]string{"exists"}).
			AddRow(exists)

		mock.ExpectQuery(`SELECT EXISTS \( SELECT 1 FROM users WHERE email_address = \? AND is_deleted = 0\)`).
			WithArgs(email).
			WillReturnRows(rows)

		result, err := CheckEmailInDB(email)

		if err != nil {
			t.Errorf("Expected no error for %s, got: %v", email, err)
		}

		if result != exists {
			t.Errorf("Expected %v for %s, got %v", exists, email, result)
		}
	}
}
