package handlers

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

	"veza-backend-api/internal/services"

	"github.com/gin-gonic/gin"
	"github.com/google/uuid"
	"github.com/stretchr/testify/assert"
	"github.com/stretchr/testify/mock"
	"go.uber.org/zap"
)

// MockSessionServiceForHandler mocks SessionService for session handler
type MockSessionServiceForHandler struct {
	mock.Mock
}

func (m *MockSessionServiceForHandler) RevokeSession(ctx context.Context, token string) error {
	args := m.Called(ctx, token)
	return args.Error(0)
}

func (m *MockSessionServiceForHandler) RevokeAllUserSessions(ctx context.Context, userID uuid.UUID) (int64, error) {
	args := m.Called(ctx, userID)
	return args.Get(0).(int64), args.Error(1)
}

func (m *MockSessionServiceForHandler) GetUserSessions(userID uuid.UUID) ([]*services.Session, error) {
	args := m.Called(userID)
	if args.Get(0) == nil {
		return nil, args.Error(1)
	}
	return args.Get(0).([]*services.Session), args.Error(1)
}

func (m *MockSessionServiceForHandler) DeleteSession(tokenHash string) error {
	args := m.Called(tokenHash)
	return args.Error(0)
}

func (m *MockSessionServiceForHandler) RefreshSession(ctx context.Context, token string, newExpiresIn time.Duration) error {
	args := m.Called(ctx, token, newExpiresIn)
	return args.Error(0)
}

func (m *MockSessionServiceForHandler) GetSessionStats(ctx context.Context) (map[string]interface{}, error) {
	args := m.Called(ctx)
	if args.Get(0) == nil {
		return nil, args.Error(1)
	}
	return args.Get(0).(map[string]interface{}), args.Error(1)
}

func (m *MockSessionServiceForHandler) HashTokenForMiddleware(token string) string {
	args := m.Called(token)
	return args.String(0)
}

// MockAuditServiceForSession mocks AuditService for session operations
type MockAuditServiceForSession struct {
	mock.Mock
}

func (m *MockAuditServiceForSession) LogPasswordResetRequest(ctx context.Context, userID *uuid.UUID, email, ip, userAgent string) error {
	args := m.Called(ctx, userID, email, ip, userAgent)
	return args.Error(0)
}

func (m *MockAuditServiceForSession) LogPasswordReset(ctx context.Context, userID uuid.UUID, success bool, ip, userAgent string) error {
	args := m.Called(ctx, userID, success, ip, userAgent)
	return args.Error(0)
}

// LogSessionRevocation is not needed for AuditServiceInterfaceForSession

func setupTestSessionRouter(mockSessionService *MockSessionServiceForHandler, mockAuditService *MockAuditServiceForSession) *gin.Engine {
	gin.SetMode(gin.TestMode)
	router := gin.New()

	logger := zap.NewNop()
	handler := NewSessionHandlerWithInterface(mockSessionService, mockAuditService, logger)

	api := router.Group("/api/v1/sessions")
	api.Use(func(c *gin.Context) {
		userIDStr := c.GetHeader("X-User-ID")
		if userIDStr != "" {
			uid, err := uuid.Parse(userIDStr)
			if err == nil {
				c.Set("user_id", uid)
			}
		}
		c.Next()
	})
	{
		api.POST("/logout", handler.Logout())
		api.POST("/logout-all", handler.LogoutAll())
		api.GET("/", handler.GetSessions())
		api.DELETE("/:session_id", handler.RevokeSession())
		api.GET("/stats", handler.GetSessionStats())
		api.POST("/refresh", handler.RefreshSession())
	}

	return router
}

func TestSessionHandler_Logout_Success(t *testing.T) {
	// Setup
	mockSessionService := new(MockSessionServiceForHandler)
	mockAuditService := new(MockAuditServiceForSession)
	router := setupTestSessionRouter(mockSessionService, mockAuditService)

	userID := uuid.New()
	token := "test-token"

	mockSessionService.On("RevokeSession", mock.Anything, token).Return(nil)

	// Execute
	req, _ := http.NewRequest("POST", "/api/v1/sessions/logout", nil)
	req.Header.Set("X-User-ID", userID.String())
	req.Header.Set("Authorization", "Bearer "+token)
	w := httptest.NewRecorder()
	router.ServeHTTP(w, req)

	// Assert
	assert.Equal(t, http.StatusOK, w.Code)
	mockSessionService.AssertExpectations(t)
}

func TestSessionHandler_Logout_Unauthorized(t *testing.T) {
	// Setup
	mockSessionService := new(MockSessionServiceForHandler)
	mockAuditService := new(MockAuditServiceForSession)
	router := setupTestSessionRouter(mockSessionService, mockAuditService)

	// Execute - No X-User-ID header
	req, _ := http.NewRequest("POST", "/api/v1/sessions/logout", nil)
	w := httptest.NewRecorder()
	router.ServeHTTP(w, req)

	// Assert
	assert.Equal(t, http.StatusUnauthorized, w.Code)
	mockSessionService.AssertNotCalled(t, "RevokeSession")
}

func TestSessionHandler_Logout_NoAuthHeader(t *testing.T) {
	// Setup
	mockSessionService := new(MockSessionServiceForHandler)
	mockAuditService := new(MockAuditServiceForSession)
	router := setupTestSessionRouter(mockSessionService, mockAuditService)

	userID := uuid.New()

	// Execute - No Authorization header
	req, _ := http.NewRequest("POST", "/api/v1/sessions/logout", nil)
	req.Header.Set("X-User-ID", userID.String())
	w := httptest.NewRecorder()
	router.ServeHTTP(w, req)

	// Assert
	assert.Equal(t, http.StatusBadRequest, w.Code)
	mockSessionService.AssertNotCalled(t, "RevokeSession")
}

func TestSessionHandler_LogoutAll_Success(t *testing.T) {
	// Setup
	mockSessionService := new(MockSessionServiceForHandler)
	mockAuditService := new(MockAuditServiceForSession)
	router := setupTestSessionRouter(mockSessionService, mockAuditService)

	userID := uuid.New()
	revokedCount := int64(3)

	mockSessionService.On("RevokeAllUserSessions", mock.Anything, userID).Return(revokedCount, nil)

	// Execute
	req, _ := http.NewRequest("POST", "/api/v1/sessions/logout-all", nil)
	req.Header.Set("X-User-ID", userID.String())
	w := httptest.NewRecorder()
	router.ServeHTTP(w, req)

	// Assert
	assert.Equal(t, http.StatusOK, w.Code)
	mockSessionService.AssertExpectations(t)
}

func TestSessionHandler_GetSessions_Success(t *testing.T) {
	// Setup
	mockSessionService := new(MockSessionServiceForHandler)
	mockAuditService := new(MockAuditServiceForSession)
	router := setupTestSessionRouter(mockSessionService, mockAuditService)

	userID := uuid.New()
	sessionID := uuid.New()
	expectedSessions := []*services.Session{
		{
			ID:        sessionID,
			UserID:    userID,
			CreatedAt: time.Now(),
			ExpiresAt: time.Now().Add(24 * time.Hour),
			IPAddress: "127.0.0.1",
			UserAgent: "test-agent",
		},
	}

	mockSessionService.On("GetUserSessions", userID).Return(expectedSessions, nil)
	mockSessionService.On("HashTokenForMiddleware", mock.Anything).Return("hashed-token")

	// Execute
	req, _ := http.NewRequest("GET", "/api/v1/sessions/", nil)
	req.Header.Set("X-User-ID", userID.String())
	w := httptest.NewRecorder()
	router.ServeHTTP(w, req)

	// Assert
	assert.Equal(t, http.StatusOK, w.Code)
	mockSessionService.AssertExpectations(t)
}

func TestSessionHandler_RevokeSession_Success(t *testing.T) {
	// Setup
	mockSessionService := new(MockSessionServiceForHandler)
	mockAuditService := new(MockAuditServiceForSession)
	router := setupTestSessionRouter(mockSessionService, mockAuditService)

	userID := uuid.New()
	sessionID := uuid.New()
	expectedSessions := []*services.Session{
		{
			ID:        sessionID,
			UserID:    userID,
			TokenHash: "hashed-token",
		},
	}

	mockSessionService.On("GetUserSessions", userID).Return(expectedSessions, nil)
	mockSessionService.On("DeleteSession", "hashed-token").Return(nil)

	// Execute
	req, _ := http.NewRequest("DELETE", "/api/v1/sessions/"+sessionID.String(), nil)
	req.Header.Set("X-User-ID", userID.String())
	w := httptest.NewRecorder()
	router.ServeHTTP(w, req)

	// Assert
	assert.Equal(t, http.StatusOK, w.Code)
	mockSessionService.AssertExpectations(t)
}

func TestSessionHandler_RevokeSession_NotFound(t *testing.T) {
	// Setup
	mockSessionService := new(MockSessionServiceForHandler)
	mockAuditService := new(MockAuditServiceForSession)
	router := setupTestSessionRouter(mockSessionService, mockAuditService)

	userID := uuid.New()
	sessionID := uuid.New()
	expectedSessions := []*services.Session{
		{
			ID:     uuid.New(), // Different session ID
			UserID: userID,
		},
	}

	mockSessionService.On("GetUserSessions", userID).Return(expectedSessions, nil)

	// Execute
	req, _ := http.NewRequest("DELETE", "/api/v1/sessions/"+sessionID.String(), nil)
	req.Header.Set("X-User-ID", userID.String())
	w := httptest.NewRecorder()
	router.ServeHTTP(w, req)

	// Assert
	assert.Equal(t, http.StatusNotFound, w.Code)
	mockSessionService.AssertNotCalled(t, "DeleteSession")
}

func TestSessionHandler_GetSessionStats_Success(t *testing.T) {
	// Setup
	mockSessionService := new(MockSessionServiceForHandler)
	mockAuditService := new(MockAuditServiceForSession)
	router := setupTestSessionRouter(mockSessionService, mockAuditService)

	userID := uuid.New()
	expectedStats := map[string]interface{}{
		"total_sessions":  10,
		"active_sessions": 5,
	}

	mockSessionService.On("GetSessionStats", mock.Anything).Return(expectedStats, nil)

	// Execute
	req, _ := http.NewRequest("GET", "/api/v1/sessions/stats", nil)
	req.Header.Set("X-User-ID", userID.String())
	w := httptest.NewRecorder()
	router.ServeHTTP(w, req)

	// Assert
	assert.Equal(t, http.StatusOK, w.Code)
	mockSessionService.AssertExpectations(t)
}

func TestSessionHandler_RefreshSession_Success(t *testing.T) {
	// Setup
	mockSessionService := new(MockSessionServiceForHandler)
	mockAuditService := new(MockAuditServiceForSession)
	router := setupTestSessionRouter(mockSessionService, mockAuditService)

	userID := uuid.New()
	token := "test-token"

	mockSessionService.On("RefreshSession", mock.Anything, token, 24*time.Hour).Return(nil)

	// Execute
	req, _ := http.NewRequest("POST", "/api/v1/sessions/refresh", nil)
	req.Header.Set("X-User-ID", userID.String())
	req.Header.Set("Authorization", "Bearer "+token)
	w := httptest.NewRecorder()
	router.ServeHTTP(w, req)

	// Assert
	assert.Equal(t, http.StatusOK, w.Code)
	mockSessionService.AssertExpectations(t)
}

func TestSessionHandler_RefreshSession_NoAuthHeader(t *testing.T) {
	// Setup
	mockSessionService := new(MockSessionServiceForHandler)
	mockAuditService := new(MockAuditServiceForSession)
	router := setupTestSessionRouter(mockSessionService, mockAuditService)

	userID := uuid.New()

	// Execute - No Authorization header
	req, _ := http.NewRequest("POST", "/api/v1/sessions/refresh", nil)
	req.Header.Set("X-User-ID", userID.String())
	w := httptest.NewRecorder()
	router.ServeHTTP(w, req)

	// Assert
	assert.Equal(t, http.StatusBadRequest, w.Code)
	mockSessionService.AssertNotCalled(t, "RefreshSession")
}
