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"

	"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 TestProjectTaskService(t *testing.T) {
	// Setup
	gin.SetMode(gin.TestMode)
	mockAuthRepo := mocks.NewMockAuthRepository()
	projectTaskDS := mocks.NewMockProjectTaskDataSource()
	projectTaskRepo := repository.NewProjectTaskRepository(projectTaskDS)
	projectTaskService := services.NewProjectTaskService(mockAuthRepo, projectTaskRepo)

	router := gin.Default()
	router.RedirectTrailingSlash = false
	api := router.Group("/api/project-tasks")
	projectTaskService.RegisterRoutes(api)

	populateTask := func(projectID, name, description string) entities.ProjectTask {
		newTask, _ := E.Unwrap(F.Pipe1(
			entities.ProjectTaskCreate{
				ProjectID:   projectID,
				Name:        name,
				Description: &description,
			},
			projectTaskRepo.Create(context.Background()),
		))
		return newTask
	}

	t.Run("CreateTask - Success", func(t *testing.T) {
		taskCreateDTO := dto.ProjectTaskCreateDTO{
			ProjectID:   "project123",
			Name:        "Test Task",
			Description: StringPtr("Test task description"),
		}

		body, _ := json.Marshal(taskCreateDTO)
		req := httptest.NewRequest(http.MethodPost, "/api/project-tasks/", 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.ProjectTaskDTO
		err := json.Unmarshal(rec.Body.Bytes(), &response)
		assert.NoError(t, err)
		assert.Equal(t, taskCreateDTO.Name, response.Name)
		assert.Equal(t, *taskCreateDTO.Description, *response.Description)
		assert.Equal(t, taskCreateDTO.ProjectID, response.ProjectID)
	})

	t.Run("GetTaskByID - Success", func(t *testing.T) {
		task := populateTask("project123", "Test Task", "Sample description")

		req := httptest.NewRequest(http.MethodGet, fmt.Sprintf("/api/project-tasks/%s", task.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.ProjectTaskDTO
		err := json.Unmarshal(rec.Body.Bytes(), &response)
		assert.NoError(t, err)
		assert.Equal(t, task.Name, response.Name)
		assert.Equal(t, *task.Description, *response.Description)
	})

	t.Run("GetAllTasks - Success", func(t *testing.T) {
		projectTaskDS.Clear()
		populateTask("project123", "Task 1", "Description 1")
		populateTask("project456", "Task 2", "Description 2")

		req := httptest.NewRequest(http.MethodGet, "/api/project-tasks/", 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.ProjectTaskDTO
		err := json.Unmarshal(rec.Body.Bytes(), &response)
		assert.NoError(t, err)
		assert.Len(t, response, 2)
	})

	t.Run("UpdateTask - Success", func(t *testing.T) {
		task := populateTask("project123", "Old Task", "Old description")

		taskUpdateDTO := dto.ProjectTaskUpdateDTO{
			Name:        StringPtr("Updated Task"),
			Description: StringPtr("Updated description"),
		}

		body, _ := json.Marshal(taskUpdateDTO)
		req := httptest.NewRequest(http.MethodPut, fmt.Sprintf("/api/project-tasks/%s", task.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.ProjectTaskDTO
		err := json.Unmarshal(rec.Body.Bytes(), &response)
		assert.NoError(t, err)
		assert.Equal(t, *taskUpdateDTO.Name, response.Name)
		assert.Equal(t, *taskUpdateDTO.Description, *response.Description)
	})

	t.Run("DeleteTask - Success", func(t *testing.T) {
		task := populateTask("project123", "To Be Deleted", "Description to be deleted")

		req := httptest.NewRequest(http.MethodDelete, fmt.Sprintf("/api/project-tasks/%s", task.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.ProjectTaskDTO
		err := json.Unmarshal(rec.Body.Bytes(), &response)
		assert.NoError(t, err)
		assert.Equal(t, "To Be Deleted", response.Name)
		assert.Equal(t, "Description to be deleted", *response.Description)
	})
}
