package postgres

import (
	"context"
	"regexp"
	"testing"
	"time"

	"github.com/DATA-DOG/go-sqlmock"
	"github.com/gofrs/uuid/v5"
	"github.com/jmoiron/sqlx"
	"github.com/saveinmed/backend-go/internal/domain"
	"github.com/stretchr/testify/assert"
)

func newMockRepo(t *testing.T) (*Repository, sqlmock.Sqlmock) {
	db, mock, err := sqlmock.New()
	if err != nil {
		t.Fatalf("an error '%s' was not expected when opening a stub database connection", err)
	}
	sqlxDB := sqlx.NewDb(db, "sqlmock")
	repo := New(sqlxDB)
	return repo, mock
}

func TestCreateCompany(t *testing.T) {
	repo, mock := newMockRepo(t)
	defer repo.db.Close()

	company := &domain.Company{
		ID:             uuid.Must(uuid.NewV7()),
		CNPJ:           "12345678901234",
		CorporateName:  "Test Pharmacy",
		Category:       "farmacia",
		LicenseNumber:  "123",
		IsVerified:     false,
		Latitude:       -10.0,
		Longitude:      -20.0,
		City:           "Test City",
		State:          "TS",
		Phone:          "(11) 99999-9999",
		OperatingHours: "08:00-18:00",
		Is24Hours:      false,
		CreatedAt:      time.Now(),
		UpdatedAt:      time.Now(),
	}

	query := `INSERT INTO companies`
	mock.ExpectExec(regexp.QuoteMeta(query)).
		WithArgs(
			company.ID,
			company.CNPJ,
			company.CorporateName,
			company.Category,
			company.LicenseNumber,
			company.IsVerified,
			company.Latitude,
			company.Longitude,
			company.City,
			company.State,
			company.Phone,
			company.OperatingHours,
			company.Is24Hours,
			sqlmock.AnyArg(), // CreatedAt
			sqlmock.AnyArg(), // UpdatedAt
		).
		WillReturnResult(sqlmock.NewResult(1, 1))

	err := repo.CreateCompany(context.Background(), company)
	assert.NoError(t, err)

	if err := mock.ExpectationsWereMet(); err != nil {
		t.Errorf("there were unfulfilled expectations: %s", err)
	}
}

func TestGetCompany(t *testing.T) {
	repo, mock := newMockRepo(t)
	defer repo.db.Close()

	id := uuid.Must(uuid.NewV7())

	rows := sqlmock.NewRows([]string{"id", "cnpj", "corporate_name", "category", "license_number", "is_verified", "latitude", "longitude", "city", "state", "created_at", "updated_at"}).
		AddRow(id, "123", "Test", "farmacia", "123", false, 0.0, 0.0, "City", "ST", time.Now(), time.Now())

	// query := `SELECT .* FROM companies WHERE id = \$1`
	// Use explicit regex without QuoteMeta for the wildcard part
	mock.ExpectQuery(`SELECT .* FROM companies WHERE id = \$1`).
		WithArgs(id).
		WillReturnRows(rows)

	company, err := repo.GetCompany(context.Background(), id)
	assert.NoError(t, err)
	if company != nil {
		assert.Equal(t, id, company.ID)
	} else {
		t.Error("expected company to not be nil")
	}
}

func TestCreateProduct(t *testing.T) {
	repo, mock := newMockRepo(t)
	defer repo.db.Close()

	product := &domain.Product{
		ID:           uuid.Must(uuid.NewV7()),
		SellerID:     uuid.Must(uuid.NewV7()),
		EANCode:      "7891234567890",
		Name:         "Test Product",
		Description:  "Desc",
		Manufacturer: "Test Manufacturer",
		Category:     "medicamento",
		Subcategory:  "analgésico",
		// Batch:        "B1", // Removed
		// ExpiresAt:    time.Now().AddDate(1, 0, 0), // Removed
		PriceCents: 1000,
		// Stock:        10, // Removed
		Observations: "Test observations",
	}

	query := `INSERT INTO products`
	rows := sqlmock.NewRows([]string{"created_at", "updated_at"}).
		AddRow(time.Now(), time.Now())
	mock.ExpectQuery(regexp.QuoteMeta(query)).
		WithArgs(
			product.ID,
			product.SellerID,
			product.EANCode,
			product.Name,
			product.Description,
			product.Manufacturer,
			product.Category,
			product.Subcategory,
			// product.Batch,
			// product.ExpiresAt,
			product.PriceCents,
			// product.Stock,
			product.Observations,
		).
		WillReturnRows(rows)

	err := repo.CreateProduct(context.Background(), product)
	assert.NoError(t, err)
}

func TestListProducts(t *testing.T) {
	repo, mock := newMockRepo(t)
	defer repo.db.Close()

	rows := sqlmock.NewRows([]string{"id", "name"}).AddRow(uuid.Must(uuid.NewV7()), "P1")

	// We expect two queries: count and select list
	mock.ExpectQuery(`SELECT count\(\*\) FROM products`).WillReturnRows(sqlmock.NewRows([]string{"count"}).AddRow(1))
	mock.ExpectQuery(`SELECT .* FROM products`).WithArgs(10, 0).WillReturnRows(rows)

	list, count, err := repo.ListProducts(context.Background(), domain.ProductFilter{Limit: 10})
	assert.NoError(t, err)
	assert.Equal(t, int64(1), count)
	assert.Len(t, list, 1)
}

func TestCreateUser(t *testing.T) {
	repo, mock := newMockRepo(t)
	defer repo.db.Close()

	user := &domain.User{
		ID:           uuid.Must(uuid.NewV7()),
		CompanyID:    uuid.Must(uuid.NewV7()),
		Role:         "Colaborador",
		Name:         "Test User",
		Username:     "testuser",
		Email:        "test@example.com",
		PasswordHash: "hashed_password",
	}

	query := `INSERT INTO users`
	mock.ExpectExec(regexp.QuoteMeta(query)).
		WithArgs(
			user.ID,
			user.CompanyID,
			user.Role,
			user.Name,
			user.Username,
			user.Email,
			user.EmailVerified, // email_verified
			user.PasswordHash,  // password_hash
			sqlmock.AnyArg(),   // CreatedAt
			sqlmock.AnyArg(),   // UpdatedAt
		).
		WillReturnResult(sqlmock.NewResult(1, 1))

	err := repo.CreateUser(context.Background(), user)
	assert.NoError(t, err)
}

func TestListUsers(t *testing.T) {
	repo, mock := newMockRepo(t)
	defer repo.db.Close()

	companyID := uuid.Must(uuid.NewV7())
	userID := uuid.Must(uuid.NewV7())

	rows := sqlmock.NewRows([]string{
		"id", "company_id", "role", "name", "username", "email",
		"email_verified", "password_hash", "created_at", "updated_at",
	}).AddRow(
		userID, companyID, "Owner", "Test", "test", "test@test.com",
		true, "hash", time.Now(), time.Now(),
	)

	mock.ExpectQuery(`SELECT count\(\*\) FROM users`).
		WillReturnRows(sqlmock.NewRows([]string{"count"}).AddRow(1))
	mock.ExpectQuery(`SELECT .* FROM users`).
		WithArgs(20, 0).
		WillReturnRows(rows)

	list, count, err := repo.ListUsers(context.Background(), domain.UserFilter{Limit: 20})
	assert.NoError(t, err)
	assert.Equal(t, int64(1), count)
	assert.Len(t, list, 1)
}

func TestListOrders(t *testing.T) {
	repo, mock := newMockRepo(t)
	defer repo.db.Close()

	orderID := uuid.Must(uuid.NewV7())
	buyerID := uuid.Must(uuid.NewV7())
	sellerID := uuid.Must(uuid.NewV7())

	rows := sqlmock.NewRows([]string{
		"id", "buyer_id", "seller_id", "status", "total_cents", "payment_method",
		"shipping_recipient_name", "shipping_street", "shipping_number", "shipping_complement",
		"shipping_district", "shipping_city", "shipping_state", "shipping_zip_code",
		"shipping_country", "created_at", "updated_at",
	}).AddRow(
		orderID, buyerID, sellerID, "Pendente", 10000, "pix",
		"Test User", "Test Street", "123", "", "Centro", "City", "ST", "12345-678", "Brasil",
		time.Now(), time.Now(),
	)

	mock.ExpectQuery(`SELECT count\(\*\) FROM orders`).
		WillReturnRows(sqlmock.NewRows([]string{"count"}).AddRow(1))
	mock.ExpectQuery(`SELECT .* FROM orders`).
		WithArgs(20, 0).
		WillReturnRows(rows)

	// Expect query for order items
	mock.ExpectQuery(`SELECT .* FROM order_items WHERE order_id`).
		WithArgs(orderID).
		WillReturnRows(sqlmock.NewRows([]string{"id", "order_id", "product_id", "quantity", "unit_cents", "batch", "expires_at"}))

	list, count, err := repo.ListOrders(context.Background(), domain.OrderFilter{Limit: 20})
	assert.NoError(t, err)
	assert.Equal(t, int64(1), count)
	assert.Len(t, list, 1)
}

func TestGetShippingSettings(t *testing.T) {
	repo, mock := newMockRepo(t)
	defer repo.db.Close()

	vendorID := uuid.Must(uuid.NewV7())

	rows := sqlmock.NewRows([]string{
		"vendor_id", "active", "max_radius_km", "price_per_km_cents", "min_fee_cents",
		"free_shipping_threshold_cents", "pickup_active", "pickup_address", "pickup_hours",
		"latitude", "longitude", "created_at", "updated_at",
	}).AddRow(
		vendorID, true, 50.0, 150, 1000, nil, true, "Rua Test, 123", "08:00-18:00",
		-23.55, -46.63, time.Now(), time.Now(),
	)

	mock.ExpectQuery(`SELECT \* FROM shipping_settings WHERE vendor_id`).
		WithArgs(vendorID).
		WillReturnRows(rows)

	settings, err := repo.GetShippingSettings(context.Background(), vendorID)
	assert.NoError(t, err)
	assert.NotNil(t, settings)
	assert.Equal(t, vendorID, settings.VendorID)
	assert.True(t, settings.Active)
}
