package examples

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

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

	"erp-mvp/core-service/internal/models"
	"erp-mvp/core-service/internal/repository"
)

// TestOrganizationRepository_Create тестирует создание организации
func TestOrganizationRepository_Create(t *testing.T) {
	// Arrange
	db, mock, err := sqlmock.New()
	require.NoError(t, err)
	defer db.Close()

	repo := repository.NewOrganizationRepository(db)

	orgID := uuid.New()
	org := &models.Organization{
		ID:   orgID,
		Name: "Test Organization",
		Type: "workshop",
		Settings: models.JSON{
			"created_at": 1234567890,
		},
		CreatedAt: time.Now(),
	}

	// Ожидаем SQL запрос
	mock.ExpectExec("INSERT INTO organizations").
		WithArgs(orgID, org.Name, org.Type, sqlmock.AnyArg(), sqlmock.AnyArg()).
		WillReturnResult(sqlmock.NewResult(1, 1))

	// Act
	err = repo.Create(context.Background(), org)

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

// TestOrganizationRepository_GetByID тестирует получение организации по ID
func TestOrganizationRepository_GetByID(t *testing.T) {
	// Arrange
	db, mock, err := sqlmock.New()
	require.NoError(t, err)
	defer db.Close()

	repo := repository.NewOrganizationRepository(db)

	orgID := uuid.New()
	expectedOrg := &models.Organization{
		ID:   orgID,
		Name: "Test Organization",
		Type: "workshop",
		Settings: models.JSON{
			"created_at": 1234567890,
		},
	}

	// Ожидаем SQL запрос
	rows := sqlmock.NewRows([]string{"id", "name", "type", "settings", "created_at"}).
		AddRow(orgID, expectedOrg.Name, expectedOrg.Type, `{"created_at":1234567890}`, time.Now())

	mock.ExpectQuery("SELECT (.+) FROM organizations").
		WithArgs(orgID).
		WillReturnRows(rows)

	// Act
	org, err := repo.GetByID(context.Background(), orgID)

	// Assert
	assert.NoError(t, err)
	assert.NotNil(t, org)
	assert.Equal(t, expectedOrg.ID, org.ID)
	assert.Equal(t, expectedOrg.Name, org.Name)
	assert.Equal(t, expectedOrg.Type, org.Type)
	assert.NoError(t, mock.ExpectationsWereMet())
}

// TestOrganizationRepository_GetByID_NotFound тестирует случай, когда организация не найдена
func TestOrganizationRepository_GetByID_NotFound(t *testing.T) {
	// Arrange
	db, mock, err := sqlmock.New()
	require.NoError(t, err)
	defer db.Close()

	repo := repository.NewOrganizationRepository(db)
	orgID := uuid.New()

	// Ожидаем SQL запрос с пустым результатом
	mock.ExpectQuery("SELECT (.+) FROM organizations").
		WithArgs(orgID).
		WillReturnError(sql.ErrNoRows)

	// Act
	org, err := repo.GetByID(context.Background(), orgID)

	// Assert
	assert.Error(t, err)
	assert.Nil(t, org)
	assert.Equal(t, "organization not found", err.Error())
	assert.NoError(t, mock.ExpectationsWereMet())
}

// TestUserRepository_Create тестирует создание пользователя
func TestUserRepository_Create(t *testing.T) {
	// Arrange
	db, mock, err := sqlmock.New()
	require.NoError(t, err)
	defer db.Close()

	repo := repository.NewUserRepository(db)

	userID := uuid.New()
	orgID := uuid.New()
	user := &models.User{
		ID:             userID,
		OrganizationID: orgID,
		Email:          "test@example.com",
		PasswordHash:   "hashed_password",
		Role:           "admin",
		CreatedAt:      time.Now(),
	}
	password := "hashed_password"

	// Ожидаем SQL запрос
	mock.ExpectExec("INSERT INTO users").
		WithArgs(userID, orgID, user.Email, password, user.Role, sqlmock.AnyArg()).
		WillReturnResult(sqlmock.NewResult(1, 1))

	// Act
	err = repo.Create(context.Background(), user, password)

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

// TestUserRepository_GetByEmail тестирует получение пользователя по email
func TestUserRepository_GetByEmail(t *testing.T) {
	// Arrange
	db, mock, err := sqlmock.New()
	require.NoError(t, err)
	defer db.Close()

	repo := repository.NewUserRepository(db)

	userID := uuid.New()
	orgID := uuid.New()
	email := "test@example.com"
	expectedUser := &models.User{
		ID:             userID,
		OrganizationID: orgID,
		Email:          email,
		PasswordHash:   "hashed_password",
		Role:           "admin",
	}

	// Ожидаем SQL запрос
	rows := sqlmock.NewRows([]string{"id", "organization_id", "email", "password_hash", "role", "created_at"}).
		AddRow(userID, orgID, email, "hashed_password", "admin", time.Now())

	mock.ExpectQuery("SELECT (.+) FROM users").
		WithArgs(email).
		WillReturnRows(rows)

	// Act
	user, err := repo.GetByEmail(context.Background(), email)

	// Assert
	assert.NoError(t, err)
	assert.NotNil(t, user)
	assert.Equal(t, expectedUser.ID, user.ID)
	assert.Equal(t, expectedUser.Email, user.Email)
	assert.Equal(t, expectedUser.Role, user.Role)
	assert.NoError(t, mock.ExpectationsWereMet())
}

// TestLocationRepository_Create тестирует создание места хранения
func TestLocationRepository_Create(t *testing.T) {
	// Arrange
	db, mock, err := sqlmock.New()
	require.NoError(t, err)
	defer db.Close()

	repo := repository.NewLocationRepository(db)

	locationID := uuid.New()
	orgID := uuid.New()
	location := &models.StorageLocation{
		ID:             locationID,
		OrganizationID: orgID,
		Name:           "Test Location",
		Address:        "Test Address",
		Type:           "warehouse",
		Coordinates: models.JSON{
			"lat": 55.7558,
			"lng": 37.6176,
		},
		CreatedAt: time.Now(),
	}

	// Ожидаем SQL запрос
	mock.ExpectExec("INSERT INTO storage_locations").
		WithArgs(locationID, orgID, sqlmock.AnyArg(), location.Name, location.Address, location.Type, sqlmock.AnyArg(), sqlmock.AnyArg()).
		WillReturnResult(sqlmock.NewResult(1, 1))

	// Act
	err = repo.Create(context.Background(), location)

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

// TestLocationRepository_GetByID тестирует получение места хранения по ID
func TestLocationRepository_GetByID(t *testing.T) {
	// Arrange
	db, mock, err := sqlmock.New()
	require.NoError(t, err)
	defer db.Close()

	repo := repository.NewLocationRepository(db)

	locationID := uuid.New()
	orgID := uuid.New()
	expectedLocation := &models.StorageLocation{
		ID:             locationID,
		OrganizationID: orgID,
		Name:           "Test Location",
		Address:        "Test Address",
		Type:           "warehouse",
		Coordinates: models.JSON{
			"lat": 55.7558,
			"lng": 37.6176,
		},
	}

	// Ожидаем SQL запрос
	rows := sqlmock.NewRows([]string{"id", "organization_id", "parent_id", "name", "address", "type", "coordinates", "created_at"}).
		AddRow(locationID, orgID, nil, expectedLocation.Name, expectedLocation.Address, expectedLocation.Type, `{"lat":55.7558,"lng":37.6176}`, time.Now())

	mock.ExpectQuery("SELECT (.+) FROM storage_locations").
		WithArgs(locationID, orgID).
		WillReturnRows(rows)

	// Act
	location, err := repo.GetByID(context.Background(), locationID, orgID)

	// Assert
	assert.NoError(t, err)
	assert.NotNil(t, location)
	assert.Equal(t, expectedLocation.ID, location.ID)
	assert.Equal(t, expectedLocation.Name, location.Name)
	assert.Equal(t, expectedLocation.Type, location.Type)
	assert.NoError(t, mock.ExpectationsWereMet())
}

// TestItemRepository_Create тестирует создание товара
func TestItemRepository_Create(t *testing.T) {
	// Arrange
	db, mock, err := sqlmock.New()
	require.NoError(t, err)
	defer db.Close()

	repo := repository.NewItemRepository(db)

	itemID := uuid.New()
	orgID := uuid.New()
	item := &models.Item{
		ID:             itemID,
		OrganizationID: orgID,
		Name:           "Test Item",
		Description:    "Test Description",
		Category:       "Test Category",
		CreatedAt:      time.Now(),
	}

	// Ожидаем SQL запрос
	mock.ExpectExec("INSERT INTO items").
		WithArgs(itemID, orgID, item.Name, item.Description, item.Category, sqlmock.AnyArg()).
		WillReturnResult(sqlmock.NewResult(1, 1))

	// Act
	err = repo.Create(context.Background(), item)

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

// TestOperationsRepository_PlaceItem тестирует размещение товара
func TestOperationsRepository_PlaceItem(t *testing.T) {
	// Arrange
	db, mock, err := sqlmock.New()
	require.NoError(t, err)
	defer db.Close()

	repo := repository.NewOperationsRepository(db)

	placementID := uuid.New()
	orgID := uuid.New()
	itemID := uuid.New()
	locationID := uuid.New()
	placement := &models.ItemPlacement{
		ID:             placementID,
		OrganizationID: orgID,
		ItemID:         itemID,
		LocationID:     locationID,
		Quantity:       5,
		CreatedAt:      time.Now(),
	}

	// Ожидаем SQL запрос
	mock.ExpectExec("INSERT INTO item_placements").
		WithArgs(placementID, orgID, itemID, locationID, placement.Quantity, sqlmock.AnyArg()).
		WillReturnResult(sqlmock.NewResult(1, 1))

	// Act
	err = repo.PlaceItem(context.Background(), placement)

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

// TestOperationsRepository_Search тестирует поиск товаров с местами размещения
func TestOperationsRepository_Search(t *testing.T) {
	// Arrange
	db, mock, err := sqlmock.New()
	require.NoError(t, err)
	defer db.Close()

	repo := repository.NewOperationsRepository(db)

	orgID := uuid.New()
	itemID := uuid.New()
	locationID := uuid.New()

	// Ожидаем SQL запрос с JOIN
	rows := sqlmock.NewRows([]string{
		"i.id", "i.organization_id", "i.name", "i.description", "i.category", "i.created_at",
		"sl.id", "sl.organization_id", "sl.parent_id", "sl.name", "sl.address", "sl.type", "sl.coordinates", "sl.created_at",
		"ip.quantity",
	}).AddRow(
		itemID, orgID, "Test Item", "Test Description", "Test Category", time.Now(),
		locationID, orgID, nil, "Test Location", "Test Address", "warehouse", `{"lat":55.7558,"lng":37.6176}`, time.Now(),
		5,
	)

	mock.ExpectQuery("SELECT (.+) FROM items i").
		WithArgs(orgID, "%test%").
		WillReturnRows(rows)

	// Act
	results, err := repo.Search(context.Background(), orgID, "test", "", "")

	// Assert
	assert.NoError(t, err)
	assert.NotNil(t, results)
	assert.Len(t, results, 1)
	assert.NoError(t, mock.ExpectationsWereMet())
}
