package services_test

import (
	"actatempus_backend/internal/application/repository"
	"actatempus_backend/internal/application/services"
	"actatempus_backend/internal/application/services/dto"
	"actatempus_backend/internal/domain/entities"
	"fmt"
	"time"

	"github.com/gin-gonic/gin"

	"actatempus_backend/tests/mocks"
	"bytes"
	"context"
	"encoding/json"
	"net/http"
	"net/http/httptest"
	"testing"

	E "github.com/IBM/fp-go/either"
	F "github.com/IBM/fp-go/function"
	"github.com/stretchr/testify/assert"
)

func TestTimeEntryService(t *testing.T) {
	// Setup
	gin.SetMode(gin.TestMode)
	mockAuthRepo := mocks.NewMockAuthRepository()
	timeEntryDS := mocks.NewMockTimeEntryDataSource()
	timeEntryRepo := repository.NewTimeEntryRepository(timeEntryDS)
	timeEntryService := services.NewTimeEntryService(mockAuthRepo, timeEntryRepo)

	router := gin.Default()
	router.RedirectTrailingSlash = false
	api := router.Group("/api/time-entries")
	timeEntryService.RegisterRoutes(api)

	populateTimeEntry := func(userID, projectID, description string) entities.TimeEntry {
		newEntry, _ := E.Unwrap(F.Pipe1(
			entities.TimeEntryCreate{
				UserID:      userID,
				ProjectID:   projectID,
				Description: &description,
				StartTime:   time.Now(),
			},
			timeEntryRepo.Create(context.Background()),
		))
		return newEntry
	}

	t.Run("CreateTimeEntry - Success", func(t *testing.T) {
		timeEntryCreateDTO := dto.TimeEntryCreateDTO{
			UserID:      "user123",
			ProjectID:   "project123",
			Description: StringPtr("Test time entry"),
			StartTime:   "2021-01-01T00:00:00Z",
		}

		body, _ := json.Marshal(timeEntryCreateDTO)
		req := httptest.NewRequest(http.MethodPost, "/api/time-entries/", bytes.NewBuffer(body))
		req.Header.Set("Content-Type", "application/json")
		req.Header.Set("Cookie", "session_token=fake_test_token")
		rec := httptest.NewRecorder()

		router.ServeHTTP(rec, req)

		assert.Equal(t, http.StatusCreated, rec.Code)
		var response dto.TimeEntryDTO
		err := json.Unmarshal(rec.Body.Bytes(), &response)
		assert.NoError(t, err)
		assert.Equal(t, *timeEntryCreateDTO.Description, *response.Description)
		assert.Equal(t, timeEntryCreateDTO.UserID, response.UserID)
		assert.Equal(t, timeEntryCreateDTO.ProjectID, response.ProjectID)
	})

	t.Run("GetTimeEntryByID - Success", func(t *testing.T) {
		timeEntry := populateTimeEntry("user123", "project123", "Sample description")

		req := httptest.NewRequest(http.MethodGet, fmt.Sprintf("/api/time-entries/%s", timeEntry.ID), nil)
		req.Header.Set("Cookie", "session_token=fake_test_token")
		rec := httptest.NewRecorder()

		router.ServeHTTP(rec, req)

		assert.Equal(t, http.StatusOK, rec.Code)
		var response dto.TimeEntryDTO
		err := json.Unmarshal(rec.Body.Bytes(), &response)
		assert.NoError(t, err)
		assert.Equal(t, timeEntry.Description, response.Description)
	})

	t.Run("GetAllTimeEntries - Success", func(t *testing.T) {
		timeEntryDS.Clear()
		populateTimeEntry("user123", "project123", "Entry 1")
		populateTimeEntry("user456", "project456", "Entry 2")

		req := httptest.NewRequest(http.MethodGet, "/api/time-entries/", nil)
		req.Header.Set("Cookie", "session_token=fake_test_token")
		rec := httptest.NewRecorder()

		router.ServeHTTP(rec, req)

		assert.Equal(t, http.StatusOK, rec.Code)
		var response []dto.TimeEntryDTO
		err := json.Unmarshal(rec.Body.Bytes(), &response)
		assert.NoError(t, err)
		assert.Len(t, response, 2)
	})

	t.Run("UpdateTimeEntry - Success", func(t *testing.T) {
		timeEntry := populateTimeEntry("user123", "project123", "Old description")

		timeEntryUpdateDTO := dto.TimeEntryUpdateDTO{
			Description: StringPtr("Updated description"),
		}

		body, _ := json.Marshal(timeEntryUpdateDTO)
		req := httptest.NewRequest(http.MethodPut, fmt.Sprintf("/api/time-entries/%s", timeEntry.ID), bytes.NewBuffer(body))
		req.Header.Set("Content-Type", "application/json")
		req.Header.Set("Cookie", "session_token=fake_test_token")
		rec := httptest.NewRecorder()

		router.ServeHTTP(rec, req)

		assert.Equal(t, http.StatusOK, rec.Code)
		var response dto.TimeEntryDTO
		err := json.Unmarshal(rec.Body.Bytes(), &response)
		assert.NoError(t, err)
		assert.Equal(t, *timeEntryUpdateDTO.Description, *response.Description)
	})

	t.Run("DeleteTimeEntry - Success", func(t *testing.T) {
		timeEntry := populateTimeEntry("user123", "project123", "To be deleted")

		req := httptest.NewRequest(http.MethodDelete, fmt.Sprintf("/api/time-entries/%s", timeEntry.ID), nil)
		req.Header.Set("Cookie", "session_token=fake_test_token")
		rec := httptest.NewRecorder()

		router.ServeHTTP(rec, req)

		assert.Equal(t, http.StatusOK, rec.Code)
		var response dto.TimeEntryDTO
		err := json.Unmarshal(rec.Body.Bytes(), &response)
		assert.NoError(t, err)
		assert.Equal(t, "To be deleted", *response.Description)
	})
}
