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 PrismaProjectTaskDataSource struct {
	client *db.PrismaClient
}

func NewPrismaProjectTaskDataSource(client *db.PrismaClient) data.ProjectTaskDataSource {
	return &PrismaProjectTaskDataSource{client: client}
}

// Create a new ProjectTask
func (ds *PrismaProjectTaskDataSource) Create(ctx context.Context, task entities.ProjectTaskCreate) E.Either[error, entities.ProjectTask] {
	return F.Flow4(
		E.Eitherize1(ds.client.ProjectTaskDbo.CreateOne(
			db.ProjectTaskDbo.Name.Set(task.Name),
			db.ProjectTaskDbo.Project.Link(
				db.ProjectDbo.ID.Equals(task.ProjectID),
			),
			db.ProjectTaskDbo.Description.SetIfPresent(task.Description),
		).Exec),
		E.MapLeft[*db.ProjectTaskDboModel](WrapDBError("Could not create project task")),
		E.Chain(
			TryDereference[db.ProjectTaskDboModel],
		),
		E.Map[error](mapPrismaProjectTaskToDomain),
	)(ctx)
}

// Find ProjectTask by ID
func (ds *PrismaProjectTaskDataSource) FindByID(ctx context.Context, id string) E.Either[error, entities.ProjectTask] {
	return F.Flow4(
		E.Eitherize1(ds.client.ProjectTaskDbo.FindUnique(
			db.ProjectTaskDbo.ID.Equals(id),
		).Exec),
		E.MapLeft[*db.ProjectTaskDboModel](WrapDBError(fmt.Sprintf("Could not retrieve project task with ID %s", id))),
		E.Chain(
			TryDereference[db.ProjectTaskDboModel],
		),
		E.Map[error](mapPrismaProjectTaskToDomain),
	)(ctx)
}

// Update an existing ProjectTask
func (ds *PrismaProjectTaskDataSource) Update(ctx context.Context, task entities.ProjectTaskUpdate) E.Either[error, entities.ProjectTask] {
	return F.Flow4(
		E.Eitherize1(ds.client.ProjectTaskDbo.FindUnique(
			db.ProjectTaskDbo.ID.Equals(task.ID),
		).Update(
			db.ProjectTaskDbo.Name.SetIfPresent(task.Name),
			db.ProjectTaskDbo.Description.SetIfPresent(task.Description),
			db.ProjectTaskDbo.Project.Link(
				db.ProjectDbo.ID.EqualsIfPresent(task.ProjectID),
			),
		).Exec),
		E.MapLeft[*db.ProjectTaskDboModel](WrapDBError(fmt.Sprintf("Could not update project task with ID %s", task.ID))),
		E.Chain(
			TryDereference[db.ProjectTaskDboModel],
		),
		E.Map[error](mapPrismaProjectTaskToDomain),
	)(ctx)
}

// Delete a ProjectTask
func (ds *PrismaProjectTaskDataSource) Delete(ctx context.Context, id string) E.Either[error, entities.ProjectTask] {
	return F.Flow4(
		E.Eitherize1(ds.client.ProjectTaskDbo.FindUnique(
			db.ProjectTaskDbo.ID.Equals(id),
		).Delete().Exec),
		E.MapLeft[*db.ProjectTaskDboModel](WrapDBError(fmt.Sprintf("Could not delete project task with ID %s", id))),
		E.Chain(
			TryDereference[db.ProjectTaskDboModel],
		),
		E.Map[error](mapPrismaProjectTaskToDomain),
	)(ctx)
}

// FindAll retrieves all ProjectTasks
func (ds *PrismaProjectTaskDataSource) FindAll(ctx context.Context) E.Either[error, []entities.ProjectTask] {
	return F.Flow3(
		E.Eitherize1(ds.client.ProjectTaskDbo.FindMany().Exec),
		E.MapLeft[[]db.ProjectTaskDboModel](WrapDBError("Could not retrieve project tasks")),
		E.Map[error](A.Map(mapPrismaProjectTaskToDomain)),
	)(ctx)
}

// FindByProjectID retrieves all ProjectTasks for a given Project
func (ds *PrismaProjectTaskDataSource) FindByProjectID(ctx context.Context, projectID string) E.Either[error, []entities.ProjectTask] {
	return F.Flow3(
		E.Eitherize1(ds.client.ProjectTaskDbo.FindMany(
			db.ProjectTaskDbo.ProjectID.Equals(projectID),
		).Exec),
		E.MapLeft[[]db.ProjectTaskDboModel](WrapDBError(fmt.Sprintf("Could not retrieve project tasks for project with ID %s", projectID))),
		E.Map[error](A.Map(mapPrismaProjectTaskToDomain)),
	)(ctx)
}
