package mocks

import (
	impl "actatempus_backend/internal/infrastructure/data"

	"actatempus_backend/internal/domain/entities"
	"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 MockTimeEntryDataSource struct {
	Store map[string]*entities.TimeEntry
	mu    sync.RWMutex
}

func NewMockTimeEntryDataSource() *MockTimeEntryDataSource {
	return &MockTimeEntryDataSource{
		Store: make(map[string]*entities.TimeEntry),
	}
}

// Create a new TimeEntry
func (ds *MockTimeEntryDataSource) Create(ctx context.Context, entry entities.TimeEntryCreate) E.Either[error, entities.TimeEntry] {
	ds.mu.Lock()
	defer ds.mu.Unlock()

	id := GenerateID()
	newEntry := entities.TimeEntry{
		ID:          id,
		StartTime:   entry.StartTime,
		EndTime:     entry.EndTime,
		Description: entry.Description,
		UserID:      entry.UserID,
		ProjectID:   entry.ProjectID,
	}

	ds.Store[id] = &newEntry

	return E.Right[error](newEntry)
}

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

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

// Update an existing TimeEntry
func (ds *MockTimeEntryDataSource) Update(ctx context.Context, entry entities.TimeEntryUpdate) E.Either[error, entities.TimeEntry] {
	ds.mu.Lock()
	defer ds.mu.Unlock()

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

	if entry.StartTime != nil {
		existing.StartTime = *entry.StartTime
	}
	if entry.EndTime != nil {
		existing.EndTime = entry.EndTime
	}
	if entry.Description != nil {
		existing.Description = entry.Description
	}
	if entry.UserID != nil {
		existing.UserID = *entry.UserID
	}
	if entry.ProjectID != nil {
		existing.ProjectID = *entry.ProjectID
	}

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

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

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

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

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

	entries := make([]entities.TimeEntry, 0, len(ds.Store))
	for _, entry := range ds.Store {
		entries = append(entries, *entry)
	}

	return E.Right[error](entries)
}

// FindByUserID retrieves all TimeEntries by UserID
func (ds *MockTimeEntryDataSource) FindByUserID(ctx context.Context, userID string) E.Either[error, []entities.TimeEntry] {
	ds.mu.RLock()
	defer ds.mu.RUnlock()

	entries := make([]entities.TimeEntry, 0)
	for _, entry := range ds.Store {
		if entry.UserID == userID {
			entries = append(entries, *entry)
		}
	}

	return E.Right[error](entries)
}

// FindByProjectID retrieves all TimeEntries by ProjectID
func (ds *MockTimeEntryDataSource) FindByProjectID(ctx context.Context, projectID string) E.Either[error, []entities.TimeEntry] {
	ds.mu.RLock()
	defer ds.mu.RUnlock()

	entries := make([]entities.TimeEntry, 0)
	for _, entry := range ds.Store {
		if entry.ProjectID == projectID {
			entries = append(entries, *entry)
		}
	}

	return E.Right[error](entries)
}

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