package handlers

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

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

	"veza-backend-api/internal/models"
	"veza-backend-api/internal/services"
)

// MockPlaybackAnalyticsService is a mock implementation of PlaybackAnalyticsService
type MockPlaybackAnalyticsService struct {
	mock.Mock
}

func (m *MockPlaybackAnalyticsService) RecordPlayback(ctx context.Context, analytics *models.PlaybackAnalytics) error {
	args := m.Called(ctx, analytics)
	return args.Error(0)
}

func (m *MockPlaybackAnalyticsService) GetTrackStats(ctx context.Context, trackID uuid.UUID) (*services.PlaybackStats, error) {
	args := m.Called(ctx, trackID)
	if args.Get(0) == nil {
		return nil, args.Error(1)
	}
	return args.Get(0).(*services.PlaybackStats), args.Error(1)
}

func TestNewPlaybackWebSocketHandler(t *testing.T) {
	// Setup
	logger := zaptest.NewLogger(t)
	mockService := &MockPlaybackAnalyticsService{}

	// Execute
	handler := NewPlaybackWebSocketHandlerWithInterface(mockService, logger)

	// Assert
	assert.NotNil(t, handler)
	assert.NotNil(t, handler.analyticsService)
	assert.NotNil(t, handler.logger)
	assert.NotNil(t, handler.clients)
	assert.NotNil(t, handler.broadcast)
	assert.Equal(t, 0, handler.GetTotalConnectedClientsCount())
}

func TestNewPlaybackWebSocketHandler_NilLogger(t *testing.T) {
	// Setup
	mockService := &MockPlaybackAnalyticsService{}

	// Execute
	handler := NewPlaybackWebSocketHandlerWithInterface(mockService, nil)

	// Assert
	assert.NotNil(t, handler)
	assert.NotNil(t, handler.logger)
}

func TestPlaybackWebSocketHandler_GetConnectedClientsCount(t *testing.T) {
	// Setup
	logger := zaptest.NewLogger(t)
	mockService := &MockPlaybackAnalyticsService{}
	handler := NewPlaybackWebSocketHandlerWithInterface(mockService, logger)

	// Execute - No clients connected
	count := handler.GetConnectedClientsCount(123)

	// Assert
	assert.Equal(t, 0, count)
}

func TestPlaybackWebSocketHandler_GetTotalConnectedClientsCount(t *testing.T) {
	// Setup
	logger := zaptest.NewLogger(t)
	mockService := &MockPlaybackAnalyticsService{}
	handler := NewPlaybackWebSocketHandlerWithInterface(mockService, logger)

	// Execute
	total := handler.GetTotalConnectedClientsCount()

	// Assert
	assert.Equal(t, 0, total)
}

func TestPlaybackWebSocketHandler_BroadcastAnalyticsUpdate(t *testing.T) {
	// Setup
	logger := zaptest.NewLogger(t)
	mockService := &MockPlaybackAnalyticsService{}
	handler := NewPlaybackWebSocketHandlerWithInterface(mockService, logger)

	trackID := int64(123)
	trackUUID := uuid.New()
	analytics := &models.PlaybackAnalytics{
		TrackID:        trackUUID,
		UserID:         uuid.New(),
		PlayTime:       45,
		PauseCount:     2,
		SeekCount:      1,
		CompletionRate: 50.0,
		StartedAt:      time.Now(),
	}

	// Execute - Should not panic even with no clients
	handler.BroadcastAnalyticsUpdate(trackID, analytics)

	// Assert - No error should occur
	assert.True(t, true)
}

func TestPlaybackWebSocketHandler_BroadcastAnalyticsUpdate_NilAnalytics(t *testing.T) {
	// Setup
	logger := zaptest.NewLogger(t)
	mockService := &MockPlaybackAnalyticsService{}
	handler := NewPlaybackWebSocketHandlerWithInterface(mockService, logger)

	// Execute - Should not panic with nil analytics
	handler.BroadcastAnalyticsUpdate(123, nil)

	// Assert - No error should occur
	assert.True(t, true)
}

func TestPlaybackWebSocketHandler_BroadcastStatsUpdate(t *testing.T) {
	// Setup
	logger := zaptest.NewLogger(t)
	mockService := &MockPlaybackAnalyticsService{}
	handler := NewPlaybackWebSocketHandlerWithInterface(mockService, logger)

	stats := &services.PlaybackStats{
		TotalSessions:     100,
		TotalPlayTime:     3000,
		AveragePlayTime:   30.0,
		TotalPauses:       50,
		AveragePauses:     0.5,
		TotalSeeks:        25,
		AverageSeeks:      0.25,
		AverageCompletion: 75.0,
		CompletionRate:    60.0,
	}

	// Execute - Should not panic even with no clients
	handler.BroadcastStatsUpdate(123, stats)

	// Assert - No error should occur
	assert.True(t, true)
}

func TestPlaybackWebSocketHandler_BroadcastStatsUpdate_NilStats(t *testing.T) {
	// Setup
	logger := zaptest.NewLogger(t)
	mockService := &MockPlaybackAnalyticsService{}
	handler := NewPlaybackWebSocketHandlerWithInterface(mockService, logger)

	// Execute - Should not panic with nil stats
	handler.BroadcastStatsUpdate(123, nil)

	// Assert - No error should occur
	assert.True(t, true)
}

func TestPlaybackWebSocketHandler_WebSocketHandler_Unauthorized(t *testing.T) {
	// Setup
	gin.SetMode(gin.TestMode)
	router := gin.New()
	logger := zaptest.NewLogger(t)
	mockService := &MockPlaybackAnalyticsService{}
	handler := NewPlaybackWebSocketHandlerWithInterface(mockService, logger)

	router.GET("/ws", handler.WebSocketHandler)

	// Execute - Request without user ID
	req, _ := http.NewRequest("GET", "/ws", nil)
	w := httptest.NewRecorder()
	router.ServeHTTP(w, req)

	// Assert
	assert.Equal(t, http.StatusUnauthorized, w.Code)
}

func TestBroadcastMessage_MarshalJSON(t *testing.T) {
	// Setup
	msg := &BroadcastMessage{
		TrackID:   123,
		Type:      "analytics_update",
		Data:      gin.H{"test": "data"},
		Timestamp: time.Now(),
	}

	// Execute
	data, err := json.Marshal(msg)

	// Assert
	assert.NoError(t, err)
	assert.NotEmpty(t, data)

	var unmarshaled BroadcastMessage
	err = json.Unmarshal(data, &unmarshaled)
	assert.NoError(t, err)
	assert.Equal(t, msg.TrackID, unmarshaled.TrackID)
	assert.Equal(t, msg.Type, unmarshaled.Type)
}

func TestIncomingWebSocketMessage_MarshalJSON(t *testing.T) {
	// Setup
	msg := IncomingWebSocketMessage{
		Type:    "subscribe",
		TrackID: "123",
		Data:    json.RawMessage(`{"test": "data"}`),
	}

	// Execute
	data, err := json.Marshal(msg)

	// Assert
	assert.NoError(t, err)
	assert.NotEmpty(t, data)

	var unmarshaled IncomingWebSocketMessage
	err = json.Unmarshal(data, &unmarshaled)
	assert.NoError(t, err)
	assert.Equal(t, msg.Type, unmarshaled.Type)
	assert.Equal(t, msg.TrackID, unmarshaled.TrackID)
}

func TestPlaybackWebSocketHandler_BroadcastChannelFull(t *testing.T) {
	// Setup
	logger := zaptest.NewLogger(t)
	mockService := &MockPlaybackAnalyticsService{}
	handler := NewPlaybackWebSocketHandlerWithInterface(mockService, logger)

	// Fill the broadcast channel
	trackID := int64(123)
	for i := 0; i < 257; i++ {
		msg := &BroadcastMessage{
			TrackID:   trackID,
			Type:      "test",
			Data:      gin.H{"index": i},
			Timestamp: time.Now(),
		}
		select {
		case handler.broadcast <- msg:
		default:
			// Channel is full, this is expected
		}
	}

	trackUUID := uuid.New()
	analytics := &models.PlaybackAnalytics{
		TrackID:        trackUUID,
		UserID:         uuid.New(),
		PlayTime:       45,
		PauseCount:     2,
		SeekCount:      1,
		CompletionRate: 50.0,
		StartedAt:      time.Now(),
	}

	// Execute - Should not panic even when channel is full
	handler.BroadcastAnalyticsUpdate(123, analytics)

	// Assert - No error should occur
	assert.True(t, true)
}
