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 MockProjectDataSource struct {
	Store map[string]*entities.Project
	mu    sync.RWMutex
}

func NewMockProjectDataSource() *MockProjectDataSource {
	return &MockProjectDataSource{
		Store: make(map[string]*entities.Project),
	}
}

// Create adds a new project to the store
func (ds *MockProjectDataSource) Create(ctx context.Context, project entities.ProjectCreate) E.Either[error, entities.Project] {
	ds.mu.Lock()
	defer ds.mu.Unlock()

	id := GenerateID()
	newProject := entities.Project{
		ID:          id,
		Name:        project.Name,
		UserID:      project.UserID,
		Description: project.Description,
		ClientID:    project.ClientID,
	}

	ds.Store[id] = &newProject

	return E.Right[error](newProject)
}

// FindByID retrieves a project by its ID
func (ds *MockProjectDataSource) FindByID(ctx context.Context, id string) E.Either[error, entities.Project] {
	ds.mu.RLock()
	defer ds.mu.RUnlock()

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

// Update modifies an existing project
func (ds *MockProjectDataSource) Update(ctx context.Context, project entities.ProjectUpdate) E.Either[error, entities.Project] {
	ds.mu.Lock()
	defer ds.mu.Unlock()

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

	if project.Name != nil {
		existing.Name = *project.Name
	}
	if project.Description != nil {
		existing.Description = project.Description
	}
	if project.ClientID != nil {
		existing.ClientID = project.ClientID
	}
	if project.UserID != nil {
		existing.UserID = *project.UserID
	}

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

// Delete removes a project from the store
func (ds *MockProjectDataSource) Delete(ctx context.Context, id string) E.Either[error, entities.Project] {
	ds.mu.Lock()
	defer ds.mu.Unlock()

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

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

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

	projects := make([]entities.Project, 0, len(ds.Store))
	for _, project := range ds.Store {
		projects = append(projects, *project)
	}

	return E.Right[error](projects)
}

// FindByUserID retrieves all projects for a specific user
func (ds *MockProjectDataSource) FindByUserID(ctx context.Context, userID string) E.Either[error, []entities.Project] {
	ds.mu.RLock()
	defer ds.mu.RUnlock()

	projects := make([]entities.Project, 0)
	for _, project := range ds.Store {
		if project.UserID == userID {
			projects = append(projects, *project)
		}
	}

	return E.Right[error](projects)
}

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