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 TestProjectService(t *testing.T) {
	// Setup
	gin.SetMode(gin.TestMode)
	mockAuthRepo := mocks.NewMockAuthRepository()
	projectDS := mocks.NewMockProjectDataSource()
	projectRepo := repository.NewProjectRepository(projectDS)
	projectService := services.NewProjectService(mockAuthRepo, projectRepo)

	router := gin.Default()
	router.RedirectTrailingSlash = false
	api := router.Group("/api/projects")
	projectService.RegisterRoutes(api)

	populateProject := func(userID, name, description string) entities.Project {
		newProject, _ := E.Unwrap(F.Pipe1(
			entities.ProjectCreate{
				UserID:      userID,
				Name:        name,
				Description: &description,
			},
			projectRepo.Create(context.Background()),
		))
		return newProject
	}

	t.Run("CreateProject - Success", func(t *testing.T) {
		projectCreateDTO := dto.ProjectCreateDTO{
			UserID:      "user123",
			Name:        "Test Project",
			Description: StringPtr("Test project description"),
		}

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

	t.Run("GetProjectByID - Success", func(t *testing.T) {
		project := populateProject("user123", "Sample Project", "Sample description")

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

	t.Run("GetAllProjects - Success", func(t *testing.T) {
		projectDS.Clear()
		populateProject("user123", "Project 1", "Description 1")
		populateProject("user456", "Project 2", "Description 2")

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

	t.Run("UpdateProject - Success", func(t *testing.T) {
		project := populateProject("user123", "Old Project", "Old description")

		projectUpdateDTO := dto.ProjectUpdateDTO{
			Name:        StringPtr("Updated Project"),
			Description: StringPtr("Updated description"),
		}

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

	t.Run("DeleteProject - Success", func(t *testing.T) {
		project := populateProject("user123", "To Be Deleted", "Description to be deleted")

		req := httptest.NewRequest(http.MethodDelete, fmt.Sprintf("/api/projects/%s", project.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.ProjectDTO
		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)
	})
}
