package services

import (
	"context"
	"testing"

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

// MockJobEnqueuer is a mock implementation of JobEnqueuer interface
type MockJobEnqueuer struct {
	mock.Mock
}

func (m *MockJobEnqueuer) EnqueueEmailJob(to, subject, body string) {
	m.Called(to, subject, body)
}

func (m *MockJobEnqueuer) EnqueueEmailJobWithTemplate(to, subject, templateName string, templateData map[string]interface{}) {
	m.Called(to, subject, templateName, templateData)
}

func (m *MockJobEnqueuer) EnqueueThumbnailJob(inputPath, outputPath string, width, height int) {
	m.Called(inputPath, outputPath, width, height)
}

func (m *MockJobEnqueuer) EnqueueAnalyticsJob(eventName string, userID *uuid.UUID, payload map[string]interface{}) {
	m.Called(eventName, userID, payload)
}

func setupTestJobService(t *testing.T) (*JobService, *MockJobEnqueuer) {
	logger := zap.NewNop()
	service := NewJobService(logger)
	mockEnqueuer := new(MockJobEnqueuer)
	service.SetJobEnqueuer(mockEnqueuer)
	return service, mockEnqueuer
}

func TestJobService_NewJobService(t *testing.T) {
	logger := zap.NewNop()
	service := NewJobService(logger)

	assert.NotNil(t, service)
	assert.NotNil(t, service.logger)
	assert.Nil(t, service.jobEnqueuer) // Should be nil initially
}

func TestJobService_SetJobEnqueuer(t *testing.T) {
	logger := zap.NewNop()
	service := NewJobService(logger)
	mockEnqueuer := new(MockJobEnqueuer)

	service.SetJobEnqueuer(mockEnqueuer)

	assert.Equal(t, mockEnqueuer, service.jobEnqueuer)
}

func TestJobService_EnqueueEmail_Success(t *testing.T) {
	service, mockEnqueuer := setupTestJobService(t)

	ctx := context.Background()
	payload := &EmailPayload{
		To:      "test@example.com",
		Subject: "Test Subject",
		Body:    "Test Body",
	}

	mockEnqueuer.On("EnqueueEmailJob", payload.To, payload.Subject, payload.Body).Return()

	err := service.EnqueueEmail(ctx, payload)

	assert.NoError(t, err)
	mockEnqueuer.AssertExpectations(t)
}

func TestJobService_EnqueueEmail_NoEnqueuer(t *testing.T) {
	logger := zap.NewNop()
	service := NewJobService(logger)
	// Don't set enqueuer

	ctx := context.Background()
	payload := &EmailPayload{
		To:      "test@example.com",
		Subject: "Test Subject",
		Body:    "Test Body",
	}

	// Should not error, just log warning
	err := service.EnqueueEmail(ctx, payload)

	assert.NoError(t, err)
}

func TestJobService_EnqueueEmail_EmptyPayload(t *testing.T) {
	service, mockEnqueuer := setupTestJobService(t)

	ctx := context.Background()
	payload := &EmailPayload{
		To:      "",
		Subject: "",
		Body:    "",
	}

	mockEnqueuer.On("EnqueueEmailJob", "", "", "").Return()

	err := service.EnqueueEmail(ctx, payload)

	assert.NoError(t, err)
	mockEnqueuer.AssertExpectations(t)
}

func TestJobService_EnqueueThumbnail_Success(t *testing.T) {
	service, mockEnqueuer := setupTestJobService(t)

	ctx := context.Background()
	payload := &ThumbnailPayload{
		TrackID:  123,
		FileID:   "file123",
		FilePath: "/path/to/file.jpg",
	}

	mockEnqueuer.On("EnqueueThumbnailJob", "/path/to/file.jpg", "/path/to/file.jpg.thumb", 300, 300).Return()

	err := service.EnqueueThumbnail(ctx, payload)

	assert.NoError(t, err)
	mockEnqueuer.AssertExpectations(t)
}

func TestJobService_EnqueueThumbnail_NoEnqueuer(t *testing.T) {
	logger := zap.NewNop()
	service := NewJobService(logger)
	// Don't set enqueuer

	ctx := context.Background()
	payload := &ThumbnailPayload{
		TrackID:  123,
		FileID:   "file123",
		FilePath: "/path/to/file.jpg",
	}

	// Should not error, just log warning
	err := service.EnqueueThumbnail(ctx, payload)

	assert.NoError(t, err)
}

func TestJobService_EnqueueThumbnail_EmptyFilePath(t *testing.T) {
	service, mockEnqueuer := setupTestJobService(t)

	ctx := context.Background()
	payload := &ThumbnailPayload{
		TrackID:  123,
		FileID:   "file123",
		FilePath: "", // Empty file path
	}

	// Should not call enqueuer if FilePath is empty
	err := service.EnqueueThumbnail(ctx, payload)

	assert.NoError(t, err)
	mockEnqueuer.AssertNotCalled(t, "EnqueueThumbnailJob")
}

func TestJobService_JobTypes_Constants(t *testing.T) {
	assert.Equal(t, "email:send", TypeEmailSend)
	assert.Equal(t, "thumbnail:generate", TypeThumbnailGenerate)
	assert.Equal(t, "analytics:process", TypeAnalyticsProcess)
	assert.Equal(t, "webhook:delivery", TypeWebhookDelivery)
}

func TestJobService_EmailPayload_Fields(t *testing.T) {
	payload := &EmailPayload{
		To:      "test@example.com",
		Subject: "Test Subject",
		Body:    "Test Body",
	}

	assert.Equal(t, "test@example.com", payload.To)
	assert.Equal(t, "Test Subject", payload.Subject)
	assert.Equal(t, "Test Body", payload.Body)
}

func TestJobService_ThumbnailPayload_Fields(t *testing.T) {
	payload := &ThumbnailPayload{
		TrackID:  123,
		FileID:   "file123",
		FilePath: "/path/to/file.jpg",
	}

	assert.Equal(t, uint(123), payload.TrackID)
	assert.Equal(t, "file123", payload.FileID)
	assert.Equal(t, "/path/to/file.jpg", payload.FilePath)
}

func TestJobService_EnqueueEmail_MultipleCalls(t *testing.T) {
	service, mockEnqueuer := setupTestJobService(t)

	ctx := context.Background()

	payload1 := &EmailPayload{
		To:      "user1@example.com",
		Subject: "Subject 1",
		Body:    "Body 1",
	}

	payload2 := &EmailPayload{
		To:      "user2@example.com",
		Subject: "Subject 2",
		Body:    "Body 2",
	}

	mockEnqueuer.On("EnqueueEmailJob", payload1.To, payload1.Subject, payload1.Body).Return()
	mockEnqueuer.On("EnqueueEmailJob", payload2.To, payload2.Subject, payload2.Body).Return()

	err1 := service.EnqueueEmail(ctx, payload1)
	err2 := service.EnqueueEmail(ctx, payload2)

	assert.NoError(t, err1)
	assert.NoError(t, err2)
	mockEnqueuer.AssertExpectations(t)
}

func TestJobService_EnqueueThumbnail_DifferentPaths(t *testing.T) {
	service, mockEnqueuer := setupTestJobService(t)

	ctx := context.Background()

	testCases := []struct {
		name           string
		filePath       string
		expectedOutput string
	}{
		{"JPG", "/path/to/image.jpg", "/path/to/image.jpg.thumb"},
		{"PNG", "/path/to/image.png", "/path/to/image.png.thumb"},
		{"Nested path", "/very/deep/path/to/image.jpg", "/very/deep/path/to/image.jpg.thumb"},
	}

	for _, tc := range testCases {
		t.Run(tc.name, func(t *testing.T) {
			payload := &ThumbnailPayload{
				TrackID:  123,
				FileID:   "file123",
				FilePath: tc.filePath,
			}

			mockEnqueuer.On("EnqueueThumbnailJob", tc.filePath, tc.expectedOutput, 300, 300).Return()

			err := service.EnqueueThumbnail(ctx, payload)
			assert.NoError(t, err)
		})
	}

	mockEnqueuer.AssertExpectations(t)
}
