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

func NewMockUserDataSource() *MockUserDataSource {
	return &MockUserDataSource{
		Store: make(map[string]*entities.User),
	}
}

func (ds *MockUserDataSource) Create(ctx context.Context, user entities.UserCreate) E.Either[error, entities.User] {
	ds.mu.Lock()
	defer ds.mu.Unlock()

	id := GenerateID()
	newUser := entities.User{
		ID:       id,
		Name:     user.Name,
		Email:    user.Email,
		Password: impl.GenerateSecureHash(user.Password),
	}

	ds.Store[id] = &newUser

	return E.Right[error](newUser)
}

func (ds *MockUserDataSource) FindByID(ctx context.Context, id string) E.Either[error, entities.User] {
	ds.mu.RLock()
	defer ds.mu.RUnlock()
	return F.Pipe2(
		O.FromNillable(ds.Store[id]),
		E.FromOption[*entities.User](F.Constant(fmt.Errorf("user with ID %s not found", id))),
		E.Chain(
			impl.TryDereference[entities.User],
		),
	)
}

func (ds *MockUserDataSource) FindByEmail(ctx context.Context, email string) E.Either[error, entities.User] {
	ds.mu.RLock()
	defer ds.mu.RUnlock()

	for _, user := range ds.Store {
		if user.Email == email {
			return E.Right[error](*user)
		}
	}
	return E.Left[entities.User](fmt.Errorf("user with email %s not found", email))
}

func (ds *MockUserDataSource) Update(ctx context.Context, user entities.UserUpdate) E.Either[error, entities.User] {
	ds.mu.Lock()
	defer ds.mu.Unlock()

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

	if user.Name != nil {
		existing.Name = *user.Name
	}
	if user.Email != nil {
		existing.Email = *user.Email
	}
	if user.Password != nil {
		existing.Password = impl.GenerateSecureHash(*user.Password)
	}

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

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

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

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

func (ds *MockUserDataSource) FindAll(ctx context.Context) E.Either[error, []entities.User] {
	ds.mu.RLock()
	defer ds.mu.RUnlock()

	users := make([]entities.User, 0, len(ds.Store))
	for _, user := range ds.Store {
		users = append(users, *user)
	}

	return E.Right[error](users)
}

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