package data

import (
	"actatempus_backend/internal/domain/data"
	"actatempus_backend/internal/domain/entities"
	"actatempus_backend/internal/infrastructure/data/db"
	"actatempus_backend/internal/utils"
	"context"
	"fmt"

	A "github.com/IBM/fp-go/array"
	E "github.com/IBM/fp-go/either"
	F "github.com/IBM/fp-go/function"
)

type PrismaUserDataSource struct {
	client *db.PrismaClient
}

func NewPrismaUserDataSource(client *db.PrismaClient) data.UserDataSource {
	return &PrismaUserDataSource{client: client}
}

func (ds *PrismaUserDataSource) Create(ctx context.Context, user entities.UserCreate) E.Either[error, entities.User] {
	return F.Flow4(
		E.Eitherize1(ds.client.UserDbo.CreateOne(
			db.UserDbo.Name.Set(user.Name),
			db.UserDbo.Email.Set(user.Email),
			db.UserDbo.Password.Set(GenerateSecureHash(user.Password)),
		).Exec),
		E.MapLeft[*db.UserDboModel](WrapDBError("Could not create user")),
		E.Chain(
			TryDereference[db.UserDboModel],
		),
		E.Map[error](mapPrismaUserToDomain),
	)(ctx)
}

func (ds *PrismaUserDataSource) FindByID(ctx context.Context, id string) E.Either[error, entities.User] {
	return F.Flow4(
		E.Eitherize1(ds.client.UserDbo.FindUnique(
			db.UserDbo.ID.Equals(id),
		).Exec),
		E.MapLeft[*db.UserDboModel](WrapDBError(fmt.Sprintf("Could not retrieve user with ID %s", id))),
		E.Chain(
			TryDereference[db.UserDboModel],
		),
		E.Map[error](mapPrismaUserToDomain),
	)(ctx)
}

func (ds *PrismaUserDataSource) FindByEmail(ctx context.Context, email string) E.Either[error, entities.User] {
	return F.Flow4(
		E.Eitherize1(ds.client.UserDbo.FindUnique(
			db.UserDbo.Email.Equals(email),
		).Exec),
		E.MapLeft[*db.UserDboModel](WrapDBError(fmt.Sprintf("Could not retrieve user with email %s", email))),
		E.Chain(
			TryDereference[db.UserDboModel],
		),
		E.Map[error](mapPrismaUserToDomain),
	)(ctx)
}

func (ds *PrismaUserDataSource) Update(ctx context.Context, user entities.UserUpdate) E.Either[error, entities.User] {

	return F.Flow4(
		E.Eitherize1(ds.client.UserDbo.FindUnique(
			db.UserDbo.ID.Equals(user.ID),
		).Update(
			db.UserDbo.Name.SetIfPresent(user.Name),
			db.UserDbo.Email.SetIfPresent(user.Email),
			db.UserDbo.Password.SetIfPresent(utils.Let(user.Password, GenerateSecureHash)),
		).Exec),
		E.MapLeft[*db.UserDboModel](WrapDBError(fmt.Sprintf("Could not update user with ID %s", user.ID))),
		E.Chain(
			TryDereference[db.UserDboModel],
		),
		E.Map[error](mapPrismaUserToDomain),
	)(ctx)
}

func (ds *PrismaUserDataSource) Delete(ctx context.Context, id string) E.Either[error, entities.User] {
	return F.Flow4(
		E.Eitherize1(ds.client.UserDbo.FindUnique(
			db.UserDbo.ID.Equals(id),
		).Delete().Exec),
		E.MapLeft[*db.UserDboModel](WrapDBError(fmt.Sprintf("Could not delete user with ID %s", id))),
		E.Chain(
			TryDereference[db.UserDboModel],
		),
		E.Map[error](mapPrismaUserToDomain),
	)(ctx)
}

func (ds *PrismaUserDataSource) FindAll(ctx context.Context) E.Either[error, []entities.User] {
	return F.Flow3(
		E.Eitherize1(ds.client.UserDbo.FindMany().Exec),
		E.MapLeft[[]db.UserDboModel](WrapDBError("Could not retrieve users")),
		E.Map[error](A.Map(mapPrismaUserToDomain)),
	)(ctx)
}
