package middleware

import (
	"context"
	"fmt"
	"net/http"
	"net/http/httptest"
	"testing"

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

// MockAuthService implements ports.AuthService for testing
type MockAuthService struct {
	mock.Mock
}

func (m *MockAuthService) ValidateToken(token string) (map[string]interface{}, error) {
	fmt.Printf("[TEST LOG] ValidateToken called with token: '%s...'\n", token[:min(20, len(token))])
	args := m.Called(token)
	if args.Get(0) == nil {
		return nil, args.Error(1)
	}
	return args.Get(0).(map[string]interface{}), args.Error(1)
}

func (m *MockAuthService) GenerateToken(userID, tenantID string, roles []string) (string, error) {
	fmt.Printf("[TEST LOG] GenerateToken called: userID=%s, tenantID=%s, roles=%v\n", userID, tenantID, roles)
	args := m.Called(userID, tenantID, roles)
	return args.String(0), args.Error(1)
}

func (m *MockAuthService) HashPassword(password string) (string, error) {
	fmt.Printf("[TEST LOG] HashPassword called\n")
	args := m.Called(password)
	return args.String(0), args.Error(1)
}

func (m *MockAuthService) VerifyPassword(hash, password string) bool {
	fmt.Printf("[TEST LOG] VerifyPassword called\n")
	args := m.Called(hash, password)
	return args.Bool(0)
}

// ============================================================================
// TestHeaderAuthGuard - Tests for the main auth middleware
// ============================================================================

func TestHeaderAuthGuard_ValidTokenFromHeader(t *testing.T) {
	fmt.Println("\n[TEST] === TestHeaderAuthGuard_ValidTokenFromHeader ===")

	mockAuth := new(MockAuthService)
	mw := NewMiddleware(mockAuth)

	claims := map[string]interface{}{
		"sub":    "user-123",
		"tenant": "tenant-456",
		"roles":  []interface{}{"admin", "user"},
	}
	mockAuth.On("ValidateToken", "valid-jwt-token").Return(claims, nil)

	handler := http.HandlerFunc(func(w http.ResponseWriter, r *http.Request) {
		fmt.Println("[TEST LOG] Handler reached - checking context values")
		userID := r.Context().Value(ContextUserID)
		tenantID := r.Context().Value(ContextTenantID)
		roles := r.Context().Value(ContextRoles)

		fmt.Printf("[TEST LOG] Context: userID=%v, tenantID=%v, roles=%v\n", userID, tenantID, roles)

		assert.Equal(t, "user-123", userID)
		assert.Equal(t, "tenant-456", tenantID)
		assert.NotNil(t, roles)

		w.WriteHeader(http.StatusOK)
	})

	req := httptest.NewRequest("GET", "/protected", nil)
	req.Header.Set("Authorization", "Bearer valid-jwt-token")
	rr := httptest.NewRecorder()

	mw.HeaderAuthGuard(handler).ServeHTTP(rr, req)

	fmt.Printf("[TEST LOG] Response status: %d (expected: %d)\n", rr.Code, http.StatusOK)
	assert.Equal(t, http.StatusOK, rr.Code)
	mockAuth.AssertExpectations(t)
}

func TestHeaderAuthGuard_ValidTokenFromCookie(t *testing.T) {
	fmt.Println("\n[TEST] === TestHeaderAuthGuard_ValidTokenFromCookie ===")

	mockAuth := new(MockAuthService)
	mw := NewMiddleware(mockAuth)

	claims := map[string]interface{}{
		"sub":    "user-cookie-123",
		"tenant": "tenant-cookie-456",
		"roles":  []string{"candidate"},
	}
	mockAuth.On("ValidateToken", "cookie-jwt-token").Return(claims, nil)

	handler := http.HandlerFunc(func(w http.ResponseWriter, r *http.Request) {
		fmt.Println("[TEST LOG] Handler reached via cookie auth")
		userID := r.Context().Value(ContextUserID)
		fmt.Printf("[TEST LOG] Context userID: %v\n", userID)
		assert.Equal(t, "user-cookie-123", userID)
		w.WriteHeader(http.StatusOK)
	})

	req := httptest.NewRequest("GET", "/protected", nil)
	req.AddCookie(&http.Cookie{Name: "jwt", Value: "cookie-jwt-token"})
	rr := httptest.NewRecorder()

	mw.HeaderAuthGuard(handler).ServeHTTP(rr, req)

	fmt.Printf("[TEST LOG] Response status: %d (expected: %d)\n", rr.Code, http.StatusOK)
	assert.Equal(t, http.StatusOK, rr.Code)
	mockAuth.AssertExpectations(t)
}

func TestHeaderAuthGuard_MissingToken(t *testing.T) {
	fmt.Println("\n[TEST] === TestHeaderAuthGuard_MissingToken ===")

	mockAuth := new(MockAuthService)
	mw := NewMiddleware(mockAuth)

	handler := http.HandlerFunc(func(w http.ResponseWriter, r *http.Request) {
		t.Error("Handler should not be called when token is missing")
	})

	req := httptest.NewRequest("GET", "/protected", nil)
	rr := httptest.NewRecorder()

	mw.HeaderAuthGuard(handler).ServeHTTP(rr, req)

	fmt.Printf("[TEST LOG] Response status: %d (expected: %d)\n", rr.Code, http.StatusUnauthorized)
	assert.Equal(t, http.StatusUnauthorized, rr.Code)
	assert.Contains(t, rr.Body.String(), "Missing Authorization Header or Cookie")
}

func TestHeaderAuthGuard_InvalidTokenFormat(t *testing.T) {
	fmt.Println("\n[TEST] === TestHeaderAuthGuard_InvalidTokenFormat ===")

	mockAuth := new(MockAuthService)
	mw := NewMiddleware(mockAuth)

	handler := http.HandlerFunc(func(w http.ResponseWriter, r *http.Request) {
		t.Error("Handler should not be called with invalid token format")
	})

	// Test with "Basic" instead of "Bearer"
	req := httptest.NewRequest("GET", "/protected", nil)
	req.Header.Set("Authorization", "Basic some-token")
	rr := httptest.NewRecorder()

	mw.HeaderAuthGuard(handler).ServeHTTP(rr, req)

	fmt.Printf("[TEST LOG] Response status: %d (expected: %d)\n", rr.Code, http.StatusUnauthorized)
	assert.Equal(t, http.StatusUnauthorized, rr.Code)
}

func TestHeaderAuthGuard_InvalidToken(t *testing.T) {
	fmt.Println("\n[TEST] === TestHeaderAuthGuard_InvalidToken ===")

	mockAuth := new(MockAuthService)
	mw := NewMiddleware(mockAuth)

	mockAuth.On("ValidateToken", "invalid-token").Return(nil, fmt.Errorf("token expired"))

	handler := http.HandlerFunc(func(w http.ResponseWriter, r *http.Request) {
		t.Error("Handler should not be called with invalid token")
	})

	req := httptest.NewRequest("GET", "/protected", nil)
	req.Header.Set("Authorization", "Bearer invalid-token")
	rr := httptest.NewRecorder()

	mw.HeaderAuthGuard(handler).ServeHTTP(rr, req)

	fmt.Printf("[TEST LOG] Response status: %d (expected: %d)\n", rr.Code, http.StatusUnauthorized)
	assert.Equal(t, http.StatusUnauthorized, rr.Code)
	assert.Contains(t, rr.Body.String(), "Invalid Token")
	mockAuth.AssertExpectations(t)
}

// ============================================================================
// TestOptionalHeaderAuthGuard - Tests for optional auth middleware
// ============================================================================

func TestOptionalHeaderAuthGuard_NoToken(t *testing.T) {
	fmt.Println("\n[TEST] === TestOptionalHeaderAuthGuard_NoToken ===")

	mockAuth := new(MockAuthService)
	mw := NewMiddleware(mockAuth)

	handlerCalled := false
	handler := http.HandlerFunc(func(w http.ResponseWriter, r *http.Request) {
		fmt.Println("[TEST LOG] Handler reached without token - context should be empty")
		handlerCalled = true

		// Context values should be nil/empty
		userID := r.Context().Value(ContextUserID)
		fmt.Printf("[TEST LOG] Context userID (should be nil): %v\n", userID)
		assert.Nil(t, userID)

		w.WriteHeader(http.StatusOK)
	})

	req := httptest.NewRequest("GET", "/public", nil)
	rr := httptest.NewRecorder()

	mw.OptionalHeaderAuthGuard(handler).ServeHTTP(rr, req)

	fmt.Printf("[TEST LOG] Handler was called: %v (expected: true)\n", handlerCalled)
	assert.True(t, handlerCalled)
	assert.Equal(t, http.StatusOK, rr.Code)
}

func TestOptionalHeaderAuthGuard_ValidToken(t *testing.T) {
	fmt.Println("\n[TEST] === TestOptionalHeaderAuthGuard_ValidToken ===")

	mockAuth := new(MockAuthService)
	mw := NewMiddleware(mockAuth)

	claims := map[string]interface{}{
		"sub":    "optional-user",
		"tenant": "optional-tenant",
		"roles":  []string{"viewer"},
	}
	mockAuth.On("ValidateToken", "optional-token").Return(claims, nil)

	handler := http.HandlerFunc(func(w http.ResponseWriter, r *http.Request) {
		fmt.Println("[TEST LOG] Handler reached with optional token")
		userID := r.Context().Value(ContextUserID)
		fmt.Printf("[TEST LOG] Context userID: %v\n", userID)
		assert.Equal(t, "optional-user", userID)
		w.WriteHeader(http.StatusOK)
	})

	req := httptest.NewRequest("GET", "/public", nil)
	req.Header.Set("Authorization", "Bearer optional-token")
	rr := httptest.NewRecorder()

	mw.OptionalHeaderAuthGuard(handler).ServeHTTP(rr, req)

	assert.Equal(t, http.StatusOK, rr.Code)
	mockAuth.AssertExpectations(t)
}

func TestOptionalHeaderAuthGuard_InvalidToken(t *testing.T) {
	fmt.Println("\n[TEST] === TestOptionalHeaderAuthGuard_InvalidToken ===")

	mockAuth := new(MockAuthService)
	mw := NewMiddleware(mockAuth)

	mockAuth.On("ValidateToken", "bad-optional-token").Return(nil, fmt.Errorf("invalid"))

	handler := http.HandlerFunc(func(w http.ResponseWriter, r *http.Request) {
		t.Error("Handler should not be called with invalid optional token")
	})

	req := httptest.NewRequest("GET", "/public", nil)
	req.Header.Set("Authorization", "Bearer bad-optional-token")
	rr := httptest.NewRecorder()

	mw.OptionalHeaderAuthGuard(handler).ServeHTTP(rr, req)

	fmt.Printf("[TEST LOG] Response status: %d (expected: %d)\n", rr.Code, http.StatusUnauthorized)
	assert.Equal(t, http.StatusUnauthorized, rr.Code)
	mockAuth.AssertExpectations(t)
}

func TestOptionalHeaderAuthGuard_TokenFromCookie(t *testing.T) {
	fmt.Println("\n[TEST] === TestOptionalHeaderAuthGuard_TokenFromCookie ===")

	mockAuth := new(MockAuthService)
	mw := NewMiddleware(mockAuth)

	claims := map[string]interface{}{
		"sub":    "cookie-user",
		"tenant": "cookie-tenant",
		"roles":  []string{"user"},
	}
	mockAuth.On("ValidateToken", "cookie-optional-token").Return(claims, nil)

	handler := http.HandlerFunc(func(w http.ResponseWriter, r *http.Request) {
		userID := r.Context().Value(ContextUserID)
		assert.Equal(t, "cookie-user", userID)
		w.WriteHeader(http.StatusOK)
	})

	req := httptest.NewRequest("GET", "/public", nil)
	req.AddCookie(&http.Cookie{Name: "jwt", Value: "cookie-optional-token"})
	rr := httptest.NewRecorder()

	mw.OptionalHeaderAuthGuard(handler).ServeHTTP(rr, req)

	assert.Equal(t, http.StatusOK, rr.Code)
	mockAuth.AssertExpectations(t)
}

// ============================================================================
// TestRequireRoles - Tests for role-based access control
// ============================================================================

func TestRequireRoles_UserHasRequiredRole(t *testing.T) {
	fmt.Println("\n[TEST] === TestRequireRoles_UserHasRequiredRole ===")

	mockAuth := new(MockAuthService)
	mw := NewMiddleware(mockAuth)

	handler := http.HandlerFunc(func(w http.ResponseWriter, r *http.Request) {
		fmt.Println("[TEST LOG] Handler reached - user has required role")
		w.WriteHeader(http.StatusOK)
	})

	// Create request with roles in context
	req := httptest.NewRequest("GET", "/admin", nil)
	ctx := context.WithValue(req.Context(), ContextRoles, []string{"admin", "user"})
	req = req.WithContext(ctx)
	rr := httptest.NewRecorder()

	mw.RequireRoles("admin")(handler).ServeHTTP(rr, req)

	fmt.Printf("[TEST LOG] Response status: %d (expected: %d)\n", rr.Code, http.StatusOK)
	assert.Equal(t, http.StatusOK, rr.Code)
}

func TestRequireRoles_UserLacksRequiredRole(t *testing.T) {
	fmt.Println("\n[TEST] === TestRequireRoles_UserLacksRequiredRole ===")

	mockAuth := new(MockAuthService)
	mw := NewMiddleware(mockAuth)

	handler := http.HandlerFunc(func(w http.ResponseWriter, r *http.Request) {
		t.Error("Handler should not be called when user lacks role")
	})

	req := httptest.NewRequest("GET", "/admin", nil)
	ctx := context.WithValue(req.Context(), ContextRoles, []string{"user", "viewer"})
	req = req.WithContext(ctx)
	rr := httptest.NewRecorder()

	mw.RequireRoles("admin", "superadmin")(handler).ServeHTTP(rr, req)

	fmt.Printf("[TEST LOG] Response status: %d (expected: %d)\n", rr.Code, http.StatusForbidden)
	assert.Equal(t, http.StatusForbidden, rr.Code)
	assert.Contains(t, rr.Body.String(), "Forbidden")
}

func TestRequireRoles_CaseInsensitive(t *testing.T) {
	fmt.Println("\n[TEST] === TestRequireRoles_CaseInsensitive ===")

	mockAuth := new(MockAuthService)
	mw := NewMiddleware(mockAuth)

	handler := http.HandlerFunc(func(w http.ResponseWriter, r *http.Request) {
		fmt.Println("[TEST LOG] Handler reached - case insensitive match worked")
		w.WriteHeader(http.StatusOK)
	})

	req := httptest.NewRequest("GET", "/admin", nil)
	ctx := context.WithValue(req.Context(), ContextRoles, []string{"ADMIN", "USER"})
	req = req.WithContext(ctx)
	rr := httptest.NewRecorder()

	mw.RequireRoles("admin")(handler).ServeHTTP(rr, req)

	fmt.Printf("[TEST LOG] Response status: %d (expected: %d)\n", rr.Code, http.StatusOK)
	assert.Equal(t, http.StatusOK, rr.Code)
}

func TestRequireRoles_NoRolesInContext(t *testing.T) {
	fmt.Println("\n[TEST] === TestRequireRoles_NoRolesInContext ===")

	mockAuth := new(MockAuthService)
	mw := NewMiddleware(mockAuth)

	handler := http.HandlerFunc(func(w http.ResponseWriter, r *http.Request) {
		t.Error("Handler should not be called when no roles in context")
	})

	req := httptest.NewRequest("GET", "/admin", nil)
	// No roles in context
	rr := httptest.NewRecorder()

	mw.RequireRoles("admin")(handler).ServeHTTP(rr, req)

	fmt.Printf("[TEST LOG] Response status: %d (expected: %d)\n", rr.Code, http.StatusForbidden)
	assert.Equal(t, http.StatusForbidden, rr.Code)
	assert.Contains(t, rr.Body.String(), "Roles not found")
}

func TestRequireRoles_MultipleAllowedRoles(t *testing.T) {
	fmt.Println("\n[TEST] === TestRequireRoles_MultipleAllowedRoles ===")

	mockAuth := new(MockAuthService)
	mw := NewMiddleware(mockAuth)

	handler := http.HandlerFunc(func(w http.ResponseWriter, r *http.Request) {
		fmt.Println("[TEST LOG] Handler reached - matched one of multiple allowed roles")
		w.WriteHeader(http.StatusOK)
	})

	req := httptest.NewRequest("GET", "/manage", nil)
	ctx := context.WithValue(req.Context(), ContextRoles, []string{"moderator"})
	req = req.WithContext(ctx)
	rr := httptest.NewRecorder()

	// Allow admin, moderator, or superadmin
	mw.RequireRoles("admin", "moderator", "superadmin")(handler).ServeHTTP(rr, req)

	fmt.Printf("[TEST LOG] Response status: %d (expected: %d)\n", rr.Code, http.StatusOK)
	assert.Equal(t, http.StatusOK, rr.Code)
}

// ============================================================================
// TestTenantGuard - Tests for tenant enforcement
// ============================================================================

func TestTenantGuard_ValidTenant(t *testing.T) {
	fmt.Println("\n[TEST] === TestTenantGuard_ValidTenant ===")

	mockAuth := new(MockAuthService)
	mw := NewMiddleware(mockAuth)

	handler := http.HandlerFunc(func(w http.ResponseWriter, r *http.Request) {
		fmt.Println("[TEST LOG] Handler reached - tenant is valid")
		w.WriteHeader(http.StatusOK)
	})

	req := httptest.NewRequest("GET", "/tenant-resource", nil)
	ctx := context.WithValue(req.Context(), ContextTenantID, "tenant-123")
	req = req.WithContext(ctx)
	rr := httptest.NewRecorder()

	mw.TenantGuard(handler).ServeHTTP(rr, req)

	fmt.Printf("[TEST LOG] Response status: %d (expected: %d)\n", rr.Code, http.StatusOK)
	assert.Equal(t, http.StatusOK, rr.Code)
}

func TestTenantGuard_MissingTenant(t *testing.T) {
	fmt.Println("\n[TEST] === TestTenantGuard_MissingTenant ===")

	mockAuth := new(MockAuthService)
	mw := NewMiddleware(mockAuth)

	handler := http.HandlerFunc(func(w http.ResponseWriter, r *http.Request) {
		t.Error("Handler should not be called when tenant is missing")
	})

	req := httptest.NewRequest("GET", "/tenant-resource", nil)
	// No tenant in context
	rr := httptest.NewRecorder()

	mw.TenantGuard(handler).ServeHTTP(rr, req)

	fmt.Printf("[TEST LOG] Response status: %d (expected: %d)\n", rr.Code, http.StatusForbidden)
	assert.Equal(t, http.StatusForbidden, rr.Code)
	assert.Contains(t, rr.Body.String(), "Tenant Context Missing")
}

func TestTenantGuard_EmptyTenant(t *testing.T) {
	fmt.Println("\n[TEST] === TestTenantGuard_EmptyTenant ===")

	mockAuth := new(MockAuthService)
	mw := NewMiddleware(mockAuth)

	handler := http.HandlerFunc(func(w http.ResponseWriter, r *http.Request) {
		t.Error("Handler should not be called when tenant is empty")
	})

	req := httptest.NewRequest("GET", "/tenant-resource", nil)
	ctx := context.WithValue(req.Context(), ContextTenantID, "")
	req = req.WithContext(ctx)
	rr := httptest.NewRecorder()

	mw.TenantGuard(handler).ServeHTTP(rr, req)

	fmt.Printf("[TEST LOG] Response status: %d (expected: %d)\n", rr.Code, http.StatusForbidden)
	assert.Equal(t, http.StatusForbidden, rr.Code)
}

// ============================================================================
// TestExtractRoles - Tests for the helper function
// ============================================================================

func TestExtractRoles_FromStringSlice(t *testing.T) {
	fmt.Println("\n[TEST] === TestExtractRoles_FromStringSlice ===")

	input := []string{"admin", "user", "viewer"}
	result := ExtractRoles(input)

	fmt.Printf("[TEST LOG] Input: %v, Result: %v\n", input, result)
	assert.Equal(t, []string{"admin", "user", "viewer"}, result)
}

func TestExtractRoles_FromInterfaceSlice(t *testing.T) {
	fmt.Println("\n[TEST] === TestExtractRoles_FromInterfaceSlice ===")

	input := []interface{}{"admin", "moderator"}
	result := ExtractRoles(input)

	fmt.Printf("[TEST LOG] Input: %v, Result: %v\n", input, result)
	assert.Equal(t, []string{"admin", "moderator"}, result)
}

func TestExtractRoles_FromNil(t *testing.T) {
	fmt.Println("\n[TEST] === TestExtractRoles_FromNil ===")

	result := ExtractRoles(nil)

	fmt.Printf("[TEST LOG] Input: nil, Result: %v\n", result)
	assert.Equal(t, []string{}, result)
}

func TestExtractRoles_FromUnknownType(t *testing.T) {
	fmt.Println("\n[TEST] === TestExtractRoles_FromUnknownType ===")

	input := "not-a-slice"
	result := ExtractRoles(input)

	fmt.Printf("[TEST LOG] Input: %v (type: string), Result: %v\n", input, result)
	assert.Equal(t, []string{}, result)
}

func TestExtractRoles_FromMixedInterfaceSlice(t *testing.T) {
	fmt.Println("\n[TEST] === TestExtractRoles_FromMixedInterfaceSlice ===")

	input := []interface{}{"admin", 123, "user", nil}
	result := ExtractRoles(input)

	fmt.Printf("[TEST LOG] Input: %v, Result: %v\n", input, result)
	// Should only extract strings
	assert.Equal(t, []string{"admin", "user"}, result)
}

// ============================================================================
// TestHasRole - Tests for the role matching helper
// ============================================================================

func TestHasRole_SingleMatch(t *testing.T) {
	fmt.Println("\n[TEST] === TestHasRole_SingleMatch ===")

	userRoles := []string{"admin", "user"}
	allowedRoles := []string{"admin"}

	result := hasRole(userRoles, allowedRoles)

	fmt.Printf("[TEST LOG] User roles: %v, Allowed: %v, Result: %v\n", userRoles, allowedRoles, result)
	assert.True(t, result)
}

func TestHasRole_NoMatch(t *testing.T) {
	fmt.Println("\n[TEST] === TestHasRole_NoMatch ===")

	userRoles := []string{"user", "viewer"}
	allowedRoles := []string{"admin", "superadmin"}

	result := hasRole(userRoles, allowedRoles)

	fmt.Printf("[TEST LOG] User roles: %v, Allowed: %v, Result: %v\n", userRoles, allowedRoles, result)
	assert.False(t, result)
}

func TestHasRole_CaseInsensitive(t *testing.T) {
	fmt.Println("\n[TEST] === TestHasRole_CaseInsensitive ===")

	userRoles := []string{"ADMIN", "USER"}
	allowedRoles := []string{"admin"}

	result := hasRole(userRoles, allowedRoles)

	fmt.Printf("[TEST LOG] User roles: %v, Allowed: %v, Result: %v\n", userRoles, allowedRoles, result)
	assert.True(t, result)
}

func TestHasRole_EmptyUserRoles(t *testing.T) {
	fmt.Println("\n[TEST] === TestHasRole_EmptyUserRoles ===")

	userRoles := []string{}
	allowedRoles := []string{"admin"}

	result := hasRole(userRoles, allowedRoles)

	fmt.Printf("[TEST LOG] User roles: %v, Allowed: %v, Result: %v\n", userRoles, allowedRoles, result)
	assert.False(t, result)
}

func TestHasRole_EmptyAllowedRoles(t *testing.T) {
	fmt.Println("\n[TEST] === TestHasRole_EmptyAllowedRoles ===")

	userRoles := []string{"admin"}
	allowedRoles := []string{}

	result := hasRole(userRoles, allowedRoles)

	fmt.Printf("[TEST LOG] User roles: %v, Allowed: %v, Result: %v\n", userRoles, allowedRoles, result)
	assert.False(t, result)
}
