//go:build integration
// +build integration

package integration

import (
	"bytes"
	"context"
	"encoding/json"
	"fmt"
	"io"
	"mime/multipart"
	"net/http"
	"net/http/httptest"
	"os"
	"testing"

	"github.com/gin-gonic/gin"
	"github.com/google/uuid"
	"github.com/redis/go-redis/v9"
	"github.com/stretchr/testify/assert"
	"github.com/stretchr/testify/require"
	"go.uber.org/zap"
	"gorm.io/driver/postgres"
	"gorm.io/gorm"

	"veza-backend-api/internal/core/track"
	"veza-backend-api/internal/models"
	"veza-backend-api/internal/services"
	"veza-backend-api/tests/testutils"
)

// TestResumeUploadEndpoint_ChunkedUploads tests the GET /api/v1/tracks/resume/:uploadId endpoint
// BE-API-025: Verify GET /api/v1/tracks/resume/:uploadId works for chunked uploads
func TestResumeUploadEndpoint_ChunkedUploads(t *testing.T) {
	ctx := context.Background()
	gin.SetMode(gin.TestMode)

	// Setup PostgreSQL via testcontainers
	dsn, err := testutils.GetTestContainerDB(ctx)
	if err != nil {
		t.Skipf("Skipping test: PostgreSQL testcontainer not available: %v", err)
		return
	}

	db, err := gorm.Open(postgres.Open(dsn), &gorm.Config{})
	require.NoError(t, err)

	// Create test user
	userID := uuid.New()
	userIDShort := userID.String()[:8]
	usernameSafe := fmt.Sprintf("test_%s", userIDShort)
	user := &models.User{
		ID:       userID,
		Email:    fmt.Sprintf("test_resume_%s@example.com", userIDShort),
		Username: usernameSafe,
		IsActive: true,
	}
	require.NoError(t, db.Create(user).Error)

	// Setup Redis
	redisAddr := os.Getenv("REDIS_ADDR")
	if redisAddr == "" {
		redisAddr = "localhost:6379"
	}
	rdb := redis.NewClient(&redis.Options{
		Addr: redisAddr,
	})
	defer rdb.Close()

	if err := rdb.Ping(ctx).Err(); err != nil {
		t.Skipf("Skipping test: Redis not available at %s: %v", redisAddr, err)
		return
	}

	logger := zap.NewNop()
	uploadDir := t.TempDir()
	chunksDir := uploadDir + "/chunks"

	trackService := track.NewTrackService(db, logger, uploadDir)
	trackUploadService := services.NewTrackUploadService(db, logger)
	chunkService := services.NewTrackChunkService(chunksDir, rdb, logger)
	likeService := services.NewTrackLikeService(db, logger)
	streamService := services.NewStreamService("", logger)

	trackHandler := track.NewTrackHandler(trackService, trackUploadService, chunkService, likeService, streamService)

	// Setup router
	router := gin.New()
	router.Use(func(c *gin.Context) {
		c.Set("user_id", userID)
		c.Next()
	})

	api := router.Group("/api/v1")
	{
		api.POST("/tracks/initiate", trackHandler.InitiateChunkedUpload)
		api.POST("/tracks/chunk", trackHandler.UploadChunk)
		api.GET("/tracks/resume/:uploadId", trackHandler.ResumeUpload)
	}

	// Step 1: Initiate chunked upload
	initReqBody := bytes.NewBufferString(`{
		"total_chunks": 5,
		"total_size": 5242880,
		"filename": "test_audio.mp3"
	}`)
	initReq := httptest.NewRequest("POST", "/api/v1/tracks/initiate", initReqBody)
	initReq.Header.Set("Content-Type", "application/json")
	initW := httptest.NewRecorder()
	router.ServeHTTP(initW, initReq)

	require.Equal(t, http.StatusOK, initW.Code, "Initiate should succeed: %s", initW.Body.String())

	var initResp map[string]interface{}
	err = json.Unmarshal(initW.Body.Bytes(), &initResp)
	require.NoError(t, err)
	require.True(t, initResp["success"].(bool))

	uploadID := initResp["data"].(map[string]interface{})["upload_id"].(string)
	require.NotEmpty(t, uploadID)

	// Step 2: Upload some chunks (not all)
	chunksToUpload := []int{1, 2, 4} // Upload chunks 1, 2, and 4 (missing 3 and 5)

	for _, chunkNum := range chunksToUpload {
		body := &bytes.Buffer{}
		writer := multipart.NewWriter(body)

		// Add form fields
		writer.WriteField("upload_id", uploadID)
		writer.WriteField("chunk_number", fmt.Sprintf("%d", chunkNum))
		writer.WriteField("total_chunks", "5")
		writer.WriteField("total_size", "5242880")
		writer.WriteField("filename", "test_audio.mp3")

		// Add chunk file (dummy content)
		chunkContent := bytes.NewBuffer(make([]byte, 1024*1024)) // 1MB dummy chunk
		fileWriter, err := writer.CreateFormFile("chunk", fmt.Sprintf("chunk_%d.bin", chunkNum))
		require.NoError(t, err)
		_, err = io.Copy(fileWriter, chunkContent)
		require.NoError(t, err)
		writer.Close()

		chunkReq := httptest.NewRequest("POST", "/api/v1/tracks/chunk", body)
		chunkReq.Header.Set("Content-Type", writer.FormDataContentType())
		chunkW := httptest.NewRecorder()
		router.ServeHTTP(chunkW, chunkReq)

		assert.Equal(t, http.StatusOK, chunkW.Code, "Chunk %d upload should succeed: %s", chunkNum, chunkW.Body.String())
	}

	// Step 3: Test resume endpoint
	resumeReq := httptest.NewRequest("GET", fmt.Sprintf("/api/v1/tracks/resume/%s", uploadID), nil)
	resumeW := httptest.NewRecorder()
	router.ServeHTTP(resumeW, resumeReq)

	// Verify response
	require.Equal(t, http.StatusOK, resumeW.Code, "Resume should succeed: %s", resumeW.Body.String())

	var resumeResp map[string]interface{}
	err = json.Unmarshal(resumeW.Body.Bytes(), &resumeResp)
	require.NoError(t, err)
	require.True(t, resumeResp["success"].(bool))

	data := resumeResp["data"].(map[string]interface{})
	assert.Equal(t, uploadID, data["upload_id"])
	assert.Equal(t, userID.String(), data["user_id"])
	assert.Equal(t, float64(5), data["total_chunks"])
	assert.Equal(t, float64(5242880), data["total_size"])
	assert.Equal(t, "test_audio.mp3", data["filename"])

	// Verify chunks received
	receivedCount := int(data["received_count"].(float64))
	assert.Equal(t, 3, receivedCount, "Should have received 3 chunks")

	chunksReceived := data["chunks_received"].([]interface{})
	assert.Len(t, chunksReceived, 3, "Should have 3 chunks in chunks_received array")

	// Verify progress
	progress := int(data["progress"].(float64))
	assert.Equal(t, 60, progress, "Progress should be 60% (3/5 chunks)")

	// Verify last chunk
	lastChunk := int(data["last_chunk"].(float64))
	assert.Equal(t, 4, lastChunk, "Last chunk received should be 4")
}

// TestResumeUploadEndpoint_NotFound tests that resume returns 404 for non-existent upload
func TestResumeUploadEndpoint_NotFound(t *testing.T) {
	ctx := context.Background()
	gin.SetMode(gin.TestMode)

	dsn, err := testutils.GetTestContainerDB(ctx)
	if err != nil {
		t.Skipf("Skipping test: PostgreSQL testcontainer not available: %v", err)
		return
	}

	db, err := gorm.Open(postgres.Open(dsn), &gorm.Config{})
	require.NoError(t, err)

	userID := uuid.New()
	userIDShort := userID.String()[:8]
	usernameSafe := fmt.Sprintf("test_%s", userIDShort)
	user := &models.User{
		ID:       userID,
		Email:    fmt.Sprintf("test_resume_404_%s@example.com", userIDShort),
		Username: usernameSafe,
		IsActive: true,
	}
	require.NoError(t, db.Create(user).Error)

	redisAddr := os.Getenv("REDIS_ADDR")
	if redisAddr == "" {
		redisAddr = "localhost:6379"
	}
	rdb := redis.NewClient(&redis.Options{
		Addr: redisAddr,
	})
	defer rdb.Close()

	if err := rdb.Ping(ctx).Err(); err != nil {
		t.Skipf("Skipping test: Redis not available: %v", err)
		return
	}

	logger := zap.NewNop()
	uploadDir := t.TempDir()
	chunksDir := uploadDir + "/chunks"

	trackService := track.NewTrackService(db, logger, uploadDir)
	trackUploadService := services.NewTrackUploadService(db, logger)
	chunkService := services.NewTrackChunkService(chunksDir, rdb, logger)
	likeService := services.NewTrackLikeService(db, logger)
	streamService := services.NewStreamService("", logger)

	trackHandler := track.NewTrackHandler(trackService, trackUploadService, chunkService, likeService, streamService)

	router := gin.New()
	router.Use(func(c *gin.Context) {
		c.Set("user_id", userID)
		c.Next()
	})

	api := router.Group("/api/v1")
	api.GET("/tracks/resume/:uploadId", trackHandler.ResumeUpload)

	// Try to resume non-existent upload
	nonExistentID := uuid.New().String()
	resumeReq := httptest.NewRequest("GET", fmt.Sprintf("/api/v1/tracks/resume/%s", nonExistentID), nil)
	resumeW := httptest.NewRecorder()
	router.ServeHTTP(resumeW, resumeReq)

	assert.Equal(t, http.StatusNotFound, resumeW.Code, "Should return 404 for non-existent upload")
}

// TestResumeUploadEndpoint_Unauthorized tests that resume returns 403 for other user's upload
func TestResumeUploadEndpoint_Unauthorized(t *testing.T) {
	ctx := context.Background()
	gin.SetMode(gin.TestMode)

	dsn, err := testutils.GetTestContainerDB(ctx)
	if err != nil {
		t.Skipf("Skipping test: PostgreSQL testcontainer not available: %v", err)
		return
	}

	db, err := gorm.Open(postgres.Open(dsn), &gorm.Config{})
	require.NoError(t, err)

	// Create two users
	user1ID := uuid.New()
	user2ID := uuid.New()

	user1 := &models.User{
		ID:       user1ID,
		Email:    fmt.Sprintf("test_user1_%s@example.com", user1ID.String()[:8]),
		Username: fmt.Sprintf("test_user1_%s", user1ID.String()[:8]),
		IsActive: true,
	}
	user2 := &models.User{
		ID:       user2ID,
		Email:    fmt.Sprintf("test_user2_%s@example.com", user2ID.String()[:8]),
		Username: fmt.Sprintf("test_user2_%s", user2ID.String()[:8]),
		IsActive: true,
	}
	require.NoError(t, db.Create(user1).Error)
	require.NoError(t, db.Create(user2).Error)

	redisAddr := os.Getenv("REDIS_ADDR")
	if redisAddr == "" {
		redisAddr = "localhost:6379"
	}
	rdb := redis.NewClient(&redis.Options{
		Addr: redisAddr,
	})
	defer rdb.Close()

	if err := rdb.Ping(ctx).Err(); err != nil {
		t.Skipf("Skipping test: Redis not available: %v", err)
		return
	}

	logger := zap.NewNop()
	uploadDir := t.TempDir()
	chunksDir := uploadDir + "/chunks"

	trackService := track.NewTrackService(db, logger, uploadDir)
	trackUploadService := services.NewTrackUploadService(db, logger)
	chunkService := services.NewTrackChunkService(chunksDir, rdb, logger)
	likeService := services.NewTrackLikeService(db, logger)
	streamService := services.NewStreamService("", logger)

	trackHandler := track.NewTrackHandler(trackService, trackUploadService, chunkService, likeService, streamService)

	router := gin.New()

	api := router.Group("/api/v1")
	{
		api.POST("/tracks/initiate", trackHandler.InitiateChunkedUpload)
		api.GET("/tracks/resume/:uploadId", trackHandler.ResumeUpload)
	}

	// User1 initiates upload
	router.Use(func(c *gin.Context) {
		c.Set("user_id", user1ID)
		c.Next()
	})

	initReqBody := bytes.NewBufferString(`{
		"total_chunks": 3,
		"total_size": 3145728,
		"filename": "user1_file.mp3"
	}`)
	initReq := httptest.NewRequest("POST", "/api/v1/tracks/initiate", initReqBody)
	initReq.Header.Set("Content-Type", "application/json")
	initW := httptest.NewRecorder()
	router.ServeHTTP(initW, initReq)

	require.Equal(t, http.StatusOK, initW.Code)
	var initResp map[string]interface{}
	json.Unmarshal(initW.Body.Bytes(), &initResp)
	uploadID := initResp["data"].(map[string]interface{})["upload_id"].(string)

	// User2 tries to resume user1's upload
	router2 := gin.New()
	router2.Use(func(c *gin.Context) {
		c.Set("user_id", user2ID)
		c.Next()
	})
	api2 := router2.Group("/api/v1")
	api2.GET("/tracks/resume/:uploadId", trackHandler.ResumeUpload)

	resumeReq := httptest.NewRequest("GET", fmt.Sprintf("/api/v1/tracks/resume/%s", uploadID), nil)
	resumeW := httptest.NewRecorder()
	router2.ServeHTTP(resumeW, resumeReq)

	assert.Equal(t, http.StatusForbidden, resumeW.Code, "Should return 403 for other user's upload")
}
