package services

import (
	"context"
	"testing"
	"time"

	"github.com/google/uuid"
	"github.com/stretchr/testify/assert"
	"github.com/stretchr/testify/require"
	"go.uber.org/zap"
	"gorm.io/driver/sqlite"
	"gorm.io/gorm"
	"veza-backend-api/internal/database"
	"veza-backend-api/internal/models"
)

func setupTestSessionService(t *testing.T) (*SessionService, *gorm.DB, *database.Database) {
	gormDB, err := gorm.Open(sqlite.Open(":memory:"), &gorm.Config{})
	require.NoError(t, err)

	err = gormDB.AutoMigrate(&models.User{})
	require.NoError(t, err)

	// Create sessions table manually to match what service expects (or if models exist)
	// Since models.Session might not be in models package or used by AutoMigrate?
	// SessionService setup says:
	// query := INSERT INTO sessions (id, user_id, token_hash, created_at, expires_at, ip_address, user_agent)
	// The service uses raw SQL, so we need to ensure table exists.
	// But SessionService struct 'Session' has db tags.
	// Let's generic DB wrapper handling. wrapper uses sql.DB.

	// Let's create table manually to be safe or check if Session model is exported in internal/models
	// The service defines its OWN Session struct in internal/services/session_service.go
	// So we must manually create table in test.

	err = gormDB.Exec(`
		CREATE TABLE sessions (
			id TEXT PRIMARY KEY,
			user_id TEXT NOT NULL,
			token_hash TEXT NOT NULL,
			created_at TIMESTAMP NOT NULL,
			expires_at TIMESTAMP NOT NULL,
			revoked_at TIMESTAMP,
			ip_address TEXT,
			user_agent TEXT
		)
	`).Error
	require.NoError(t, err)

	sqlDB, err := gormDB.DB()
	require.NoError(t, err)

	testDB := &database.Database{
		DB: sqlDB,
	}

	logger := zap.NewNop()
	service := NewSessionService(testDB, logger)

	return service, gormDB, testDB
}

func TestSessionService_CreateAndValidate(t *testing.T) {
	service, _, _ := setupTestSessionService(t)
	ctx := context.Background()

	userID := uuid.New()
	token := "test-token"
	req := &SessionCreateRequest{
		UserID:    userID,
		Token:     token,
		IPAddress: "127.0.0.1",
		UserAgent: "TestAgent",
		ExpiresIn: time.Hour,
	}

	session, err := service.CreateSession(ctx, req)
	assert.NoError(t, err)
	assert.NotNil(t, session)
	assert.Equal(t, userID, session.UserID)

	// Validate
	validSession, err := service.ValidateSession(ctx, token)
	assert.NoError(t, err)
	assert.NotNil(t, validSession)
	assert.Equal(t, session.ID, validSession.ID)
}

func TestSessionService_Revoke(t *testing.T) {
	service, _, _ := setupTestSessionService(t)
	ctx := context.Background()

	userID := uuid.New()
	token := "test-token-revoke"
	req := &SessionCreateRequest{
		UserID:    userID,
		Token:     token,
		ExpiresIn: time.Hour,
	}

	_, err := service.CreateSession(ctx, req)
	require.NoError(t, err)

	err = service.RevokeSession(ctx, token)
	assert.NoError(t, err)

	// Validate should fail
	_, err = service.ValidateSession(ctx, token)
	assert.Error(t, err)
}

func TestSessionService_Cleanup(t *testing.T) {
	service, _, _ := setupTestSessionService(t)
	ctx := context.Background()

	userID := uuid.New()
	// Create expired session
	// Since CreateSession sets expiresAt based on Now(), we'll hack it by short duration and sleeping,
	// OR just manually insert an expired one?
	// Creating with very short duration is easier if possible, but 1ms might be flaky.
	// We can cheat by passing negative duration if logic allows?
	// CreateSession: expiresAt := time.Now().Add(expiresIn).

	req := &SessionCreateRequest{
		UserID:    userID,
		Token:     "expired-token",
		ExpiresIn: -1 * time.Hour,
	}
	// CreateSession checks if expiresIn == 0 defaults to 24h. But negative is fine.
	_, err := service.CreateSession(ctx, req)
	require.NoError(t, err)

	err = service.CleanupExpiredSessions(ctx)
	assert.NoError(t, err)

	// Check count
	stats, err := service.GetSessionStats(ctx)
	assert.NoError(t, err)
	assert.Equal(t, int64(0), stats["total_active"])
}
