package services

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

	"github.com/DATA-DOG/go-sqlmock"
	"github.com/stretchr/testify/assert"
	"github.com/stretchr/testify/require"
	"go.uber.org/zap"

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

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

	dbWrapper := &database.Database{
		DB:     db,
		Logger: zap.NewNop(),
	}

	return dbWrapper, mock
}

func TestOAuthService_GenerateStateToken_Success(t *testing.T) {
	// Setup
	db, mock := setupMockDB(t)
	defer db.DB.Close()

	logger := zap.NewNop()
	service := &OAuthService{
		db:     db,
		logger: logger,
	}

	provider := "google"
	redirectURL := "http://example.com"

	// Expectation
	mock.ExpectExec(regexp.QuoteMeta(`INSERT INTO oauth_states`)).
		WithArgs(sqlmock.AnyArg(), provider, redirectURL, sqlmock.AnyArg()).
		WillReturnResult(sqlmock.NewResult(1, 1))

	// Execute
	token, err := service.GenerateStateToken(provider, redirectURL)

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

func TestOAuthService_ValidateStateToken_Success(t *testing.T) {
	// Setup
	db, mock := setupMockDB(t)
	defer db.DB.Close()

	logger := zap.NewNop()
	service := &OAuthService{
		db:     db,
		logger: logger,
	}

	token := "valid_token"
	now := time.Now()

	// Expectation
	rows := sqlmock.NewRows([]string{"id", "state_token", "provider", "redirect_url", "expires_at", "created_at"}).
		AddRow(1, token, "google", "http://example.com", now.Add(time.Hour), now)

	mock.ExpectQuery(regexp.QuoteMeta(`SELECT id, state_token, provider, redirect_url, expires_at, created_at FROM oauth_states WHERE state_token = $1`)).
		WithArgs(token).
		WillReturnRows(rows)

	mock.ExpectExec(regexp.QuoteMeta(`DELETE FROM oauth_states WHERE id = $1`)).
		WithArgs(1).
		WillReturnResult(sqlmock.NewResult(1, 1))

	// Execute
	state, err := service.ValidateStateToken(token)

	// Assert
	assert.NoError(t, err)
	assert.NotNil(t, state)
	assert.Equal(t, token, state.StateToken)
	assert.NoError(t, mock.ExpectationsWereMet())
}

func TestOAuthService_ValidateStateToken_NotFound(t *testing.T) {
	// Setup
	db, mock := setupMockDB(t)
	defer db.DB.Close()

	logger := zap.NewNop()
	service := &OAuthService{
		db:     db,
		logger: logger,
	}

	token := "invalid_token"

	// Expectation
	mock.ExpectQuery(regexp.QuoteMeta(`SELECT id, state_token, provider, redirect_url, expires_at, created_at FROM oauth_states WHERE state_token = $1`)).
		WithArgs(token).
		WillReturnError(sql.ErrNoRows)

	// Execute
	state, err := service.ValidateStateToken(token)

	// Assert
	assert.Error(t, err)
	assert.Equal(t, "invalid state token", err.Error())
	assert.Nil(t, state)
	assert.NoError(t, mock.ExpectationsWereMet())
}

func TestOAuthService_ValidateStateToken_Expired(t *testing.T) {
	// Setup
	db, mock := setupMockDB(t)
	defer db.DB.Close()

	logger := zap.NewNop()
	service := &OAuthService{
		db:     db,
		logger: logger,
	}

	token := "expired_token"
	now := time.Now()

	// Expectation
	rows := sqlmock.NewRows([]string{"id", "state_token", "provider", "redirect_url", "expires_at", "created_at"}).
		AddRow(1, token, "google", "http://example.com", now.Add(-time.Hour), now.Add(-2*time.Hour))

	mock.ExpectQuery(regexp.QuoteMeta(`SELECT id, state_token, provider, redirect_url, expires_at, created_at FROM oauth_states WHERE state_token = $1`)).
		WithArgs(token).
		WillReturnRows(rows)

	// Execute
	state, err := service.ValidateStateToken(token)

	// Assert
	assert.Error(t, err)
	assert.Equal(t, "state token expired", err.Error())
	assert.Nil(t, state)
	assert.NoError(t, mock.ExpectationsWereMet())
}
