package handlers

import (
	"bytes"
	"context"
	"encoding/json"
	"errors"
	"mime/multipart"
	"net/http"
	"net/http/httptest"
	"testing"

	"veza-backend-api/internal/services"
	"veza-backend-api/internal/upload"

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

// MockUploadValidator implements UploadValidatorInterface
type MockUploadValidator struct {
	mock.Mock
}

func (m *MockUploadValidator) ValidateFile(ctx context.Context, fileHeader *multipart.FileHeader, fileType string) (*services.ValidationResult, error) {
	args := m.Called(ctx, fileHeader, fileType)
	if args.Get(0) == nil {
		return nil, args.Error(1)
	}
	return args.Get(0).(*services.ValidationResult), args.Error(1)
}

func (m *MockUploadValidator) GetFileTypeFromPath(filename string) string {
	args := m.Called(filename)
	return args.String(0)
}

// MockUploadAuditService implements UploadAuditServiceInterface
type MockUploadAuditService struct {
	mock.Mock
}

func (m *MockUploadAuditService) LogUpload(ctx context.Context, userID uuid.UUID, resourceID uuid.UUID, fileName string, fileSize int64, ipAddress, userAgent string) error {
	args := m.Called(ctx, userID, resourceID, fileName, fileSize, ipAddress, userAgent)
	return args.Error(0)
}

func (m *MockUploadAuditService) LogDeletion(ctx context.Context, userID uuid.UUID, resource string, resourceID uuid.UUID, ipAddress, userAgent string) error {
	args := m.Called(ctx, userID, resource, resourceID, ipAddress, userAgent)
	return args.Error(0)
}

// MockTrackUploadService implements TrackUploadServiceInterface
type MockTrackUploadService struct {
	mock.Mock
}

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

// Setup helper
func setupTestUploadHandler(t *testing.T) (*UploadHandler, *MockUploadValidator, *MockUploadAuditService, *MockTrackUploadService) {
	mockValidator := new(MockUploadValidator)
	mockAudit := new(MockUploadAuditService)
	mockTrackUpload := new(MockTrackUploadService)
	logger := zap.NewNop()

	handler := NewUploadHandlerWithInterface(mockValidator, mockAudit, mockTrackUpload, logger, 10)
	return handler, mockValidator, mockAudit, mockTrackUpload
}

func TestUploadFile_Success(t *testing.T) {
	handler, mockValidator, mockAudit, _ := setupTestUploadHandler(t)

	gin.SetMode(gin.TestMode)
	w := httptest.NewRecorder()
	c, _ := gin.CreateTestContext(w)

	// Create multipart request
	body := new(bytes.Buffer)
	writer := multipart.NewWriter(body)
	part, _ := writer.CreateFormFile("file", "test.mp3")
	part.Write([]byte("dummy content"))
	writer.WriteField("track_id", uuid.New().String())
	writer.WriteField("file_type", "audio")
	writer.WriteField("title", "Test Title")
	writer.WriteField("artist", "Test Artist")
	writer.WriteField("duration", "120")
	writer.Close()

	req, _ := http.NewRequest("POST", "/upload", body)
	req.Header.Set("Content-Type", writer.FormDataContentType())
	c.Request = req

	userID := uuid.New()
	c.Set("user_id", userID)

	// Mocks
	expectedResult := &services.ValidationResult{
		Valid:    true,
		FileType: "audio",
		FileSize: 100,
		Checksum: "abc",
	}
	mockValidator.On("ValidateFile", mock.Anything, mock.Anything, "audio").Return(expectedResult, nil)
	mockAudit.On("LogUpload", mock.Anything, userID, mock.Anything, "test.mp3", int64(100), mock.Anything, mock.Anything).Return(nil)

	// Execute
	handler.UploadFile()(c)

	// Assert
	if w.Code != http.StatusCreated {
		t.Logf("Response Body: %s", w.Body.String())
	}
	assert.Equal(t, http.StatusCreated, w.Code)

	var respWrapper struct {
		Success bool                          `json:"success"`
		Data    upload.StandardUploadResponse `json:"data"`
	}
	err := json.Unmarshal(w.Body.Bytes(), &respWrapper)
	assert.NoError(t, err)
	assert.True(t, respWrapper.Success)
	assert.Equal(t, "test.mp3", respWrapper.Data.FileName)
	assert.Equal(t, int64(100), respWrapper.Data.FileSize)

	mockValidator.AssertExpectations(t)
	mockAudit.AssertExpectations(t)
}

func TestUploadFile_ValidationFailed(t *testing.T) {
	handler, mockValidator, _, _ := setupTestUploadHandler(t)

	gin.SetMode(gin.TestMode)
	w := httptest.NewRecorder()
	c, _ := gin.CreateTestContext(w)

	// Create multipart request
	body := new(bytes.Buffer)
	writer := multipart.NewWriter(body)
	part, _ := writer.CreateFormFile("file", "test.mp3")
	part.Write([]byte("dummy content"))
	writer.WriteField("track_id", uuid.New().String())
	writer.WriteField("file_type", "audio")
	writer.WriteField("title", "Test Title")
	writer.WriteField("artist", "Test Artist")
	writer.Close()

	req, _ := http.NewRequest("POST", "/upload", body)
	req.Header.Set("Content-Type", writer.FormDataContentType())
	c.Request = req

	userID := uuid.New()
	c.Set("user_id", userID)

	// Mocks
	expectedResult := &services.ValidationResult{
		Valid: false,
		Error: "Invalid format",
	}
	mockValidator.On("ValidateFile", mock.Anything, mock.Anything, "audio").Return(expectedResult, nil)

	// Execute
	handler.UploadFile()(c)

	// Assert
	assert.Equal(t, http.StatusBadRequest, w.Code) // AppError converts ErrCodeValidation to 400
}

func TestUploadFile_ClamAVUnavailable(t *testing.T) {
	handler, mockValidator, _, _ := setupTestUploadHandler(t)

	gin.SetMode(gin.TestMode)
	w := httptest.NewRecorder()
	c, _ := gin.CreateTestContext(w)

	// Create multipart request
	body := new(bytes.Buffer)
	writer := multipart.NewWriter(body)
	part, _ := writer.CreateFormFile("file", "test.mp3")
	part.Write([]byte("dummy content"))
	writer.WriteField("track_id", uuid.New().String())
	writer.WriteField("file_type", "audio")
	writer.WriteField("title", "Test Title")
	writer.WriteField("artist", "Test Artist")
	writer.Close()

	req, _ := http.NewRequest("POST", "/upload", body)
	req.Header.Set("Content-Type", writer.FormDataContentType())
	c.Request = req

	userID := uuid.New()
	c.Set("user_id", userID)

	// Mocks
	mockValidator.On("ValidateFile", mock.Anything, mock.Anything, "audio").Return(nil, errors.New("clamav_unavailable"))

	// Execute
	handler.UploadFile()(c)

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

func TestUploadFile_VirusDetected(t *testing.T) {
	handler, mockValidator, _, _ := setupTestUploadHandler(t)

	gin.SetMode(gin.TestMode)
	w := httptest.NewRecorder()
	c, _ := gin.CreateTestContext(w)

	// Create multipart request
	body := new(bytes.Buffer)
	writer := multipart.NewWriter(body)
	part, _ := writer.CreateFormFile("file", "virus.mp3")
	part.Write([]byte("virus signature"))
	writer.WriteField("track_id", uuid.New().String())
	writer.WriteField("file_type", "audio")
	writer.WriteField("title", "Test Title")
	writer.WriteField("artist", "Test Artist")
	writer.Close()

	req, _ := http.NewRequest("POST", "/upload", body)
	req.Header.Set("Content-Type", writer.FormDataContentType())
	c.Request = req

	userID := uuid.New()
	c.Set("user_id", userID)

	// Mocks
	expectedResult := &services.ValidationResult{
		Valid:       false,
		Quarantined: true,
		Error:       "Virus detected",
	}
	mockValidator.On("ValidateFile", mock.Anything, mock.Anything, "audio").Return(expectedResult, nil)

	// Execute
	handler.UploadFile()(c)

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

func TestDeleteUpload_Success(t *testing.T) {
	handler, _, mockAudit, _ := setupTestUploadHandler(t)

	gin.SetMode(gin.TestMode)
	w := httptest.NewRecorder()
	c, _ := gin.CreateTestContext(w)

	uploadID := uuid.New()
	c.Params = []gin.Param{{Key: "id", Value: uploadID.String()}}

	userID := uuid.New()
	c.Set("user_id", userID)

	req, _ := http.NewRequest("DELETE", "/upload/"+uploadID.String(), nil)
	c.Request = req

	// Mocks
	mockAudit.On("LogDeletion", mock.Anything, userID, "upload", uploadID, mock.Anything, mock.Anything).Return(nil)

	// Execute
	handler.DeleteUpload()(c)

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

func TestGetUploadStats_Success(t *testing.T) {
	handler, _, _, mockTrackUpload := setupTestUploadHandler(t)

	gin.SetMode(gin.TestMode)
	w := httptest.NewRecorder()
	c, _ := gin.CreateTestContext(w)

	userID := uuid.New()
	c.Set("user_id", userID) // GetUserIDUUID looks for "user_id" in context which is UUID

	req, _ := http.NewRequest("GET", "/stats", nil)
	c.Request = req

	// Mocks
	stats := map[string]interface{}{"total_uploads": int64(5)}
	mockTrackUpload.On("GetUploadStats", mock.Anything, userID).Return(stats, nil)

	// Execute
	handler.GetUploadStats()(c)

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

	var resp map[string]interface{}
	json.Unmarshal(w.Body.Bytes(), &resp)
	assert.Equal(t, float64(5), resp["stats"].(map[string]interface{})["total_uploads"])
}
