package handlers

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

	"github.com/gin-gonic/gin"
	"github.com/google/uuid"
	"github.com/stretchr/testify/assert"
	"github.com/stretchr/testify/mock"
)

// MockHLSServiceForHLSHandler mocks HLSService
type MockHLSServiceForHLSHandler struct {
	mock.Mock
}

func (m *MockHLSServiceForHLSHandler) GetMasterPlaylist(ctx context.Context, trackID uuid.UUID) (string, error) {
	args := m.Called(ctx, trackID)
	return args.String(0), args.Error(1)
}

func (m *MockHLSServiceForHLSHandler) GetQualityPlaylist(ctx context.Context, trackID uuid.UUID, bitrate string) (string, error) {
	args := m.Called(ctx, trackID, bitrate)
	return args.String(0), args.Error(1)
}

func (m *MockHLSServiceForHLSHandler) GetSegmentPath(ctx context.Context, trackID uuid.UUID, bitrate string, segment string) (string, error) {
	args := m.Called(ctx, trackID, bitrate, segment)
	return args.String(0), args.Error(1)
}

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

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

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

func setupTestHLSRouter(mockService *MockHLSServiceForHLSHandler) *gin.Engine {
	gin.SetMode(gin.TestMode)
	router := gin.New()

	handler := NewHLSHandlerWithInterface(mockService)

	api := router.Group("/api/v1/hls")
	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.GET("/tracks/:id/master.m3u8", handler.ServeMasterPlaylist)
		api.GET("/tracks/:id/:bitrate/playlist.m3u8", handler.ServeQualityPlaylist)
		api.GET("/tracks/:id/:bitrate/:segment", handler.ServeSegment)
		api.GET("/tracks/:id/info", handler.GetStreamInfo)
		api.GET("/tracks/:id/status", handler.GetStreamStatus)
		api.POST("/tracks/:id/transcode", handler.TriggerTranscode)
	}

	return router
}

func TestHLSHandler_ServeMasterPlaylist_Success(t *testing.T) {
	// Setup
	mockService := new(MockHLSServiceForHLSHandler)
	router := setupTestHLSRouter(mockService)

	trackID := uuid.New()
	expectedPlaylist := "#EXTM3U\n#EXT-X-VERSION:3\n"

	mockService.On("GetMasterPlaylist", mock.Anything, trackID).Return(expectedPlaylist, nil)

	// Execute
	req, _ := http.NewRequest("GET", "/api/v1/hls/tracks/"+trackID.String()+"/master.m3u8", nil)
	w := httptest.NewRecorder()
	router.ServeHTTP(w, req)

	// Assert
	assert.Equal(t, http.StatusOK, w.Code)
	assert.Equal(t, "application/vnd.apple.mpegurl", w.Header().Get("Content-Type"))
	assert.Equal(t, expectedPlaylist, w.Body.String())
	mockService.AssertExpectations(t)
}

func TestHLSHandler_ServeMasterPlaylist_InvalidTrackID(t *testing.T) {
	// Setup
	mockService := new(MockHLSServiceForHLSHandler)
	router := setupTestHLSRouter(mockService)

	// Execute - Invalid UUID
	req, _ := http.NewRequest("GET", "/api/v1/hls/tracks/invalid-id/master.m3u8", nil)
	w := httptest.NewRecorder()
	router.ServeHTTP(w, req)

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

func TestHLSHandler_ServeMasterPlaylist_NotFound(t *testing.T) {
	// Setup
	mockService := new(MockHLSServiceForHLSHandler)
	router := setupTestHLSRouter(mockService)

	trackID := uuid.New()

	mockService.On("GetMasterPlaylist", mock.Anything, trackID).Return("", assert.AnError)

	// Execute
	req, _ := http.NewRequest("GET", "/api/v1/hls/tracks/"+trackID.String()+"/master.m3u8", nil)
	w := httptest.NewRecorder()
	router.ServeHTTP(w, req)

	// Assert
	assert.Equal(t, http.StatusNotFound, w.Code)
	mockService.AssertExpectations(t)
}

func TestHLSHandler_ServeQualityPlaylist_Success(t *testing.T) {
	// Setup
	mockService := new(MockHLSServiceForHLSHandler)
	router := setupTestHLSRouter(mockService)

	trackID := uuid.New()
	bitrate := "128000"
	expectedPlaylist := "#EXTM3U\n#EXT-X-VERSION:3\n"

	mockService.On("GetQualityPlaylist", mock.Anything, trackID, bitrate).Return(expectedPlaylist, nil)

	// Execute
	req, _ := http.NewRequest("GET", "/api/v1/hls/tracks/"+trackID.String()+"/"+bitrate+"/playlist.m3u8", nil)
	w := httptest.NewRecorder()
	router.ServeHTTP(w, req)

	// Assert
	assert.Equal(t, http.StatusOK, w.Code)
	assert.Equal(t, "application/vnd.apple.mpegurl", w.Header().Get("Content-Type"))
	mockService.AssertExpectations(t)
}

func TestHLSHandler_ServeSegment_Success(t *testing.T) {
	// Setup
	mockService := new(MockHLSServiceForHLSHandler)
	router := setupTestHLSRouter(mockService)

	trackID := uuid.New()
	bitrate := "128000"
	segment := "segment001.ts"
	expectedPath := "/path/to/segment.ts"

	mockService.On("GetSegmentPath", mock.Anything, trackID, bitrate, segment).Return(expectedPath, nil)

	// Execute
	req, _ := http.NewRequest("GET", "/api/v1/hls/tracks/"+trackID.String()+"/"+bitrate+"/"+segment, nil)
	w := httptest.NewRecorder()
	router.ServeHTTP(w, req)

	// Assert
	assert.Equal(t, http.StatusOK, w.Code)
	assert.Equal(t, "video/mp2t", w.Header().Get("Content-Type"))
	mockService.AssertExpectations(t)
}

func TestHLSHandler_GetStreamInfo_Success(t *testing.T) {
	// Setup
	mockService := new(MockHLSServiceForHLSHandler)
	router := setupTestHLSRouter(mockService)

	trackID := uuid.New()
	expectedInfo := map[string]interface{}{
		"bitrates": []string{"128000", "256000"},
		"status":   "ready",
	}

	mockService.On("GetStreamInfo", mock.Anything, trackID).Return(expectedInfo, nil)

	// Execute
	req, _ := http.NewRequest("GET", "/api/v1/hls/tracks/"+trackID.String()+"/info", nil)
	w := httptest.NewRecorder()
	router.ServeHTTP(w, req)

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

func TestHLSHandler_GetStreamStatus_Success(t *testing.T) {
	// Setup
	mockService := new(MockHLSServiceForHLSHandler)
	router := setupTestHLSRouter(mockService)

	trackID := uuid.New()
	expectedStatus := map[string]interface{}{
		"transcoding": false,
		"progress":    0.5,
	}

	mockService.On("GetStreamStatus", mock.Anything, trackID).Return(expectedStatus, nil)

	// Execute
	req, _ := http.NewRequest("GET", "/api/v1/hls/tracks/"+trackID.String()+"/status", nil)
	w := httptest.NewRecorder()
	router.ServeHTTP(w, req)

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

func TestHLSHandler_TriggerTranscode_Success(t *testing.T) {
	// Setup
	mockService := new(MockHLSServiceForHLSHandler)
	router := setupTestHLSRouter(mockService)

	userID := uuid.New()
	trackID := uuid.New()
	jobID := uuid.New()

	mockService.On("TriggerTranscodeQueue", mock.Anything, trackID, userID).Return(jobID, nil)

	// Execute
	req, _ := http.NewRequest("POST", "/api/v1/hls/tracks/"+trackID.String()+"/transcode", nil)
	req.Header.Set("X-User-ID", userID.String())
	w := httptest.NewRecorder()
	router.ServeHTTP(w, req)

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

func TestHLSHandler_TriggerTranscode_Unauthorized(t *testing.T) {
	// Setup
	mockService := new(MockHLSServiceForHLSHandler)
	router := setupTestHLSRouter(mockService)

	trackID := uuid.New()

	// Execute - No X-User-ID header
	req, _ := http.NewRequest("POST", "/api/v1/hls/tracks/"+trackID.String()+"/transcode", nil)
	w := httptest.NewRecorder()
	router.ServeHTTP(w, req)

	// Assert
	assert.True(t, w.Code == http.StatusUnauthorized || w.Code == http.StatusForbidden)
	mockService.AssertNotCalled(t, "TriggerTranscodeQueue")
}
