package mocks

import (
	"actatempus_backend/internal/domain/entities"
	impl "actatempus_backend/internal/infrastructure/data"
	"context"
	"fmt"
	"sync"

	E "github.com/IBM/fp-go/either"
	F "github.com/IBM/fp-go/function"
	O "github.com/IBM/fp-go/option"
)

type MockProjectTaskDataSource struct {
	Store map[string]*entities.ProjectTask
	mu    sync.RWMutex
}

func NewMockProjectTaskDataSource() *MockProjectTaskDataSource {
	return &MockProjectTaskDataSource{
		Store: make(map[string]*entities.ProjectTask),
	}
}

// Create a new ProjectTask
func (ds *MockProjectTaskDataSource) Create(ctx context.Context, task entities.ProjectTaskCreate) E.Either[error, entities.ProjectTask] {
	ds.mu.Lock()
	defer ds.mu.Unlock()

	id := GenerateID()
	newTask := entities.ProjectTask{
		ID:          id,
		Name:        task.Name,
		ProjectID:   task.ProjectID,
		Description: task.Description,
	}

	ds.Store[id] = &newTask

	return E.Right[error](newTask)
}

// Find ProjectTask by ID
func (ds *MockProjectTaskDataSource) FindByID(ctx context.Context, id string) E.Either[error, entities.ProjectTask] {
	ds.mu.RLock()
	defer ds.mu.RUnlock()

	return F.Pipe2(
		O.FromNillable(ds.Store[id]),
		E.FromOption[*entities.ProjectTask](F.Constant(fmt.Errorf("project task with ID %s not found", id))),
		E.Chain(impl.TryDereference[entities.ProjectTask]),
	)
}

// Update an existing ProjectTask
func (ds *MockProjectTaskDataSource) Update(ctx context.Context, task entities.ProjectTaskUpdate) E.Either[error, entities.ProjectTask] {
	ds.mu.Lock()
	defer ds.mu.Unlock()

	existing, found := ds.Store[task.ID]
	if !found {
		return E.Left[entities.ProjectTask](fmt.Errorf("project task with ID %s not found", task.ID))
	}

	if task.Name != nil {
		existing.Name = *task.Name
	}
	if task.Description != nil {
		existing.Description = task.Description
	}
	if task.ProjectID != nil {
		existing.ProjectID = *task.ProjectID
	}

	return E.Right[error](*existing)
}

// Delete a ProjectTask
func (ds *MockProjectTaskDataSource) Delete(ctx context.Context, id string) E.Either[error, entities.ProjectTask] {
	ds.mu.Lock()
	defer ds.mu.Unlock()

	existing, found := ds.Store[id]
	if !found {
		return E.Left[entities.ProjectTask](fmt.Errorf("project task with ID %s not found", id))
	}

	delete(ds.Store, id)
	return E.Right[error](*existing)
}

// FindAll retrieves all ProjectTasks
func (ds *MockProjectTaskDataSource) FindAll(ctx context.Context) E.Either[error, []entities.ProjectTask] {
	ds.mu.RLock()
	defer ds.mu.RUnlock()

	tasks := make([]entities.ProjectTask, 0, len(ds.Store))
	for _, task := range ds.Store {
		tasks = append(tasks, *task)
	}

	return E.Right[error](tasks)
}

// FindByProjectID retrieves all ProjectTasks for a given Project
func (ds *MockProjectTaskDataSource) FindByProjectID(ctx context.Context, projectID string) E.Either[error, []entities.ProjectTask] {
	ds.mu.RLock()
	defer ds.mu.RUnlock()

	tasks := make([]entities.ProjectTask, 0)
	for _, task := range ds.Store {
		if task.ProjectID == projectID {
			tasks = append(tasks, *task)
		}
	}

	return E.Right[error](tasks)
}

func (ds *MockProjectTaskDataSource) Clear() {
	ds.mu.Lock()
	defer ds.mu.Unlock()
	ds.Store = make(map[string]*entities.ProjectTask)
}
