package data

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

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

type PrismaProjectDataSource struct {
	client *db.PrismaClient
}

func NewPrismaProjectDataSource(client *db.PrismaClient) data.ProjectDataSource {
	return &PrismaProjectDataSource{client: client}
}

// Create a new project
func (ds *PrismaProjectDataSource) Create(ctx context.Context, project entities.ProjectCreate) E.Either[error, entities.Project] {
	return F.Flow4(
		E.Eitherize1(ds.client.ProjectDbo.CreateOne(
			db.ProjectDbo.Name.Set(project.Name),
			db.ProjectDbo.User.Link(
				db.UserDbo.ID.Equals(project.UserID),
			),
			db.ProjectDbo.Description.SetIfPresent(project.Description),
			db.ProjectDbo.ClientID.SetIfPresent(project.ClientID),
		).Exec),
		E.MapLeft[*db.ProjectDboModel](WrapDBError("Could not create project")),
		E.Chain(
			TryDereference[db.ProjectDboModel],
		),
		E.Map[error](mapPrismaProjectToDomain),
	)(ctx)
}

// FindByID retrieves a project by ID
func (ds *PrismaProjectDataSource) FindByID(ctx context.Context, id string) E.Either[error, entities.Project] {

	return F.Flow4(
		E.Eitherize1(ds.client.ProjectDbo.FindUnique(
			db.ProjectDbo.ID.Equals(id),
		).Exec),
		E.MapLeft[*db.ProjectDboModel](WrapDBError(fmt.Sprintf("Could not retrieve project with ID %s", id))),
		E.Chain(
			TryDereference[db.ProjectDboModel],
		),
		E.Map[error](mapPrismaProjectToDomain),
	)(ctx)
}

// Update updates a project
func (ds *PrismaProjectDataSource) Update(ctx context.Context, project entities.ProjectUpdate) E.Either[error, entities.Project] {
	return F.Flow4(
		E.Eitherize1(ds.client.ProjectDbo.FindUnique(
			db.ProjectDbo.ID.Equals(project.ID),
		).Update(
			db.ProjectDbo.Name.SetIfPresent(project.Name),
			db.ProjectDbo.Description.SetIfPresent(project.Description),
			db.ProjectDbo.ClientID.SetIfPresent(project.ClientID),
			db.ProjectDbo.User.Link(
				db.UserDbo.ID.EqualsIfPresent(project.UserID),
			),
		).Exec),
		E.MapLeft[*db.ProjectDboModel](WrapDBError(fmt.Sprintf("Could not update project with ID %s", project.ID))),
		E.Chain(
			TryDereference[db.ProjectDboModel],
		),
		E.Map[error](mapPrismaProjectToDomain),
	)(ctx)
}

// Delete removes a project
func (ds *PrismaProjectDataSource) Delete(ctx context.Context, id string) E.Either[error, entities.Project] {

	return F.Flow4(
		E.Eitherize1(ds.client.ProjectDbo.FindUnique(
			db.ProjectDbo.ID.Equals(id),
		).Delete().Exec),
		E.MapLeft[*db.ProjectDboModel](WrapDBError(fmt.Sprintf("Could not delete project with ID %s", id))),
		E.Chain(
			TryDereference[db.ProjectDboModel],
		),
		E.Map[error](mapPrismaProjectToDomain),
	)(ctx)
}

// FindAll retrieves all projects
func (ds *PrismaProjectDataSource) FindAll(ctx context.Context) E.Either[error, []entities.Project] {
	return F.Flow3(
		E.Eitherize1(ds.client.ProjectDbo.FindMany().Exec),
		E.MapLeft[[]db.ProjectDboModel](WrapAppError),
		E.Map[error](A.Map(mapPrismaProjectToDomain)),
	)(ctx)
}

// FindByUserID retrieves all projects for a user
func (ds *PrismaProjectDataSource) FindByUserID(ctx context.Context, userID string) E.Either[error, []entities.Project] {
	return F.Flow3(
		E.Eitherize1(ds.client.ProjectDbo.FindMany(
			db.ProjectDbo.UserID.Equals(userID),
		).Exec),
		E.MapLeft[[]db.ProjectDboModel](WrapDBError(fmt.Sprintf("Could not retrieve projects for user with ID %s", userID))),
		E.Map[error](A.Map(mapPrismaProjectToDomain)),
	)(ctx)
}
