package middleware

import (
	"testing"
	"time"

	"github.com/stretchr/testify/assert"
)

func TestPasswordValidator_ValidatePassword(t *testing.T) {
	tests := []struct {
		name      string
		password  string
		wantValid bool
	}{
		{
			name:      "valid password with all requirements",
			password:  "Test@Pass123!",
			wantValid: true,
		},
		{
			name:      "valid password with special chars",
			password:  "MyP@ssw0rd#2024",
			wantValid: true,
		},
		{
			name:      "too short",
			password:  "Test1!",
			wantValid: false,
		},
		{
			name:      "no uppercase",
			password:  "test@pass123!",
			wantValid: false,
		},
		{
			name:      "no lowercase",
			password:  "TEST@PASS123!",
			wantValid: false,
		},
		{
			name:      "no number",
			password:  "Test@Password!",
			wantValid: false,
		},
		{
			name:      "no special character",
			password:  "TestPassword123",
			wantValid: false,
		},
		{
			name:      "empty string",
			password:  "",
			wantValid: false,
		},
	}

	for _, tt := range tests {
		t.Run(tt.name, func(t *testing.T) {
			err := ValidatePassword(tt.password)
			if tt.wantValid {
				assert.NoError(t, err)
			} else {
				assert.Error(t, err)
			}
		})
	}
}

func TestPasswordValidator_ErrorMessages(t *testing.T) {
	tests := []struct {
		name            string
		password        string
		expectedInError string
	}{
		{
			name:            "too short error",
			password:        "Short1!",
			expectedInError: "at least 8 characters",
		},
		{
			name:            "no uppercase error",
			password:        "alllower123!",
			expectedInError: "uppercase letter",
		},
		{
			name:            "no lowercase error",
			password:        "ALLUPPER123!",
			expectedInError: "lowercase letter",
		},
		{
			name:            "no number error",
			password:        "NoNumbers!",
			expectedInError: "number",
		},
		{
			name:            "no special char error",
			password:        "NoSpecialChars123",
			expectedInError: "special character",
		},
	}

	for _, tt := range tests {
		t.Run(tt.name, func(t *testing.T) {
			err := ValidatePassword(tt.password)
			assert.Error(t, err)
			assert.Contains(t, err.Error(), tt.expectedInError)
		})
	}
}

func TestGetPasswordRequirements(t *testing.T) {
	requirements := GetPasswordRequirements()

	assert.NotEmpty(t, requirements)
	assert.Greater(t, len(requirements), 3)

	// Check for common requirements
	requirementText := ""
	for _, req := range requirements {
		requirementText += req + " "
	}

	assert.Contains(t, requirementText, "8")
	assert.Contains(t, requirementText, "uppercase")
	assert.Contains(t, requirementText, "lowercase")
	assert.Contains(t, requirementText, "number")
	assert.Contains(t, requirementText, "special")
}

func TestLoginAttemptTracker_RecordFailedAttempt(t *testing.T) {
	tracker := NewLoginAttemptTracker(3, 5*time.Minute, 5*time.Minute)

	username := "testuser"

	// First failed attempt
	locked, remainingTime := tracker.RecordFailedAttempt(username)
	assert.False(t, locked)
	assert.Equal(t, time.Duration(0), remainingTime)

	// Second failed attempt
	locked, remainingTime = tracker.RecordFailedAttempt(username)
	assert.False(t, locked)
	assert.Equal(t, time.Duration(0), remainingTime)

	// Third failed attempt - should lock
	locked, remainingTime = tracker.RecordFailedAttempt(username)
	assert.True(t, locked)
	assert.Greater(t, remainingTime, time.Duration(0))

	// Verify user is locked
	locked, _ = tracker.IsLocked(username)
	assert.True(t, locked)

	// Clear attempts
	tracker.ClearAttempts(username)

	// Should no longer be locked
	locked, _ = tracker.IsLocked(username)
	assert.False(t, locked)
}

func TestLoginAttemptTracker_IsLocked(t *testing.T) {
	tracker := NewLoginAttemptTracker(3, 5*time.Minute, 5*time.Minute)

	username := "lockeduser"

	// Record failed attempts up to max
	for i := 0; i < 3; i++ {
		tracker.RecordFailedAttempt(username)
	}

	// Verify user is locked
	locked, remainingTime := tracker.IsLocked(username)
	assert.True(t, locked)
	assert.Greater(t, remainingTime, time.Duration(0))

	// Clear attempts
	tracker.ClearAttempts(username)

	// Should no longer be locked
	locked, remainingTime = tracker.IsLocked(username)
	assert.False(t, locked)
	assert.Equal(t, time.Duration(0), remainingTime)
}

func TestLoginAttemptTracker_ConcurrentAccess(t *testing.T) {
	tracker := NewLoginAttemptTracker(5, 5*time.Minute, 5*time.Minute)

	done := make(chan bool, 10)

	// Concurrent access from multiple goroutines
	for i := 0; i < 10; i++ {
		go func(index int) {
			username := "user" + string(rune('0'+index))
			tracker.RecordFailedAttempt(username)
			tracker.IsLocked(username)
			tracker.ClearAttempts(username)
			done <- true
		}(i)
	}

	// Wait for all goroutines
	for i := 0; i < 10; i++ {
		<-done
	}

	// Should complete without deadlock or race
}

func TestDeviceRateLimiter_CheckRateLimit(t *testing.T) {
	limiter := NewDeviceRateLimiter()

	config := DeviceRateLimitConfig{
		SyncRequestsPerMinute: 5,
	}

	deviceID := "test-device-123"
	requestType := "sync"

	// First 5 requests should succeed
	for i := 0; i < 5; i++ {
		allowed := limiter.CheckRateLimit(deviceID, requestType, config)
		assert.True(t, allowed, "Request %d should be allowed", i+1)
	}

	// 6th request should be rate limited
	allowed := limiter.CheckRateLimit(deviceID, requestType, config)
	assert.False(t, allowed, "Request 6 should be rate limited")

	// Get remaining requests
	remaining := limiter.GetRemainingRequests(deviceID, requestType, config)
	assert.Equal(t, 0, remaining)

	// Reset and verify
	limiter.Reset(deviceID)

	// Should be allowed again
	allowed = limiter.CheckRateLimit(deviceID, requestType, config)
	assert.True(t, allowed, "Request after reset should be allowed")
}

func TestDeviceRateLimiter_DifferentDevices(t *testing.T) {
	limiter := NewDeviceRateLimiter()

	config := DeviceRateLimitConfig{
		SyncRequestsPerMinute: 2,
	}

	// Exhaust limit for device1
	for i := 0; i < 2; i++ {
		limiter.CheckRateLimit("device1", "sync", config)
	}

	// Device1 should be rate limited
	allowed := limiter.CheckRateLimit("device1", "sync", config)
	assert.False(t, allowed)

	// Device2 should still work
	allowed = limiter.CheckRateLimit("device2", "sync", config)
	assert.True(t, allowed)
}

func TestDeviceRateLimiter_GetRemainingRequests(t *testing.T) {
	limiter := NewDeviceRateLimiter()

	config := DeviceRateLimitConfig{
		SyncRequestsPerMinute: 10,
	}

	deviceID := "test-device-456"

	// Initially should have all requests remaining
	remaining := limiter.GetRemainingRequests(deviceID, "sync", config)
	assert.Equal(t, 10, remaining)

	// Use 3 requests
	for i := 0; i < 3; i++ {
		limiter.CheckRateLimit(deviceID, "sync", config)
	}

	// Should have 7 remaining
	remaining = limiter.GetRemainingRequests(deviceID, "sync", config)
	assert.Equal(t, 7, remaining)
}

func TestNewRateLimiter(t *testing.T) {
	config := DefaultRateLimiterConfig()
	limiter := NewRateLimiter(config)

	assert.NotNil(t, limiter)
}

func TestHTTPError_Error(t *testing.T) {
	err := NewHTTPError(404, "Not Found", nil)

	assert.NotNil(t, err)
	assert.Equal(t, 404, err.Code)
	assert.Equal(t, "Not Found", err.Message)
}

func TestHTTPError_ErrorWithInternal(t *testing.T) {
	internalErr := assert.AnError
	err := NewHTTPError(500, "Internal Error", internalErr)

	assert.Equal(t, "Internal Error: assert.AnError general error for testing", err.Error())
	assert.Equal(t, 500, err.Code)
	assert.Equal(t, "Internal Error", err.Message)
	assert.Equal(t, internalErr, err.Err)
}

func TestNewHTTPError(t *testing.T) {
	err := NewHTTPError(404, "Not Found", nil)

	assert.NotNil(t, err)
	assert.Equal(t, 404, err.Code)
	assert.Equal(t, "Not Found", err.Message)
}
