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

func NewPrismaTimeEntryDataSource(client *db.PrismaClient) data.TimeEntryDataSource {
	return &PrismaTimeEntryDataSource{client: client}
}

// Create a new TimeEntry
func (ds *PrismaTimeEntryDataSource) Create(ctx context.Context, entry entities.TimeEntryCreate) E.Either[error, entities.TimeEntry] {
	return F.Flow4(
		E.Eitherize1(ds.client.TimeEntryDbo.CreateOne(
			db.TimeEntryDbo.StartTime.Set(entry.StartTime),
			db.TimeEntryDbo.User.Link(
				db.UserDbo.ID.Equals(entry.UserID),
			), db.TimeEntryDbo.Project.Link(
				db.ProjectDbo.ID.Equals(entry.ProjectID),
			),
			db.TimeEntryDbo.EndTime.SetIfPresent(entry.EndTime),
			db.TimeEntryDbo.Description.SetIfPresent(entry.Description),
		).Exec),
		E.MapLeft[*db.TimeEntryDboModel](WrapDBError("Could not create time entry")),
		E.Chain(
			TryDereference[db.TimeEntryDboModel],
		),
		E.Map[error](mapPrismaTimeEntryToDomain),
	)(ctx)
}

// Find TimeEntry by ID
func (ds *PrismaTimeEntryDataSource) FindByID(ctx context.Context, id string) E.Either[error, entities.TimeEntry] {
	return F.Flow4(
		E.Eitherize1(ds.client.TimeEntryDbo.FindUnique(
			db.TimeEntryDbo.ID.Equals(id),
		).Exec),
		E.MapLeft[*db.TimeEntryDboModel](WrapDBError(fmt.Sprintf("Could not retrieve time entry with ID %s", id))),
		E.Chain(
			TryDereference[db.TimeEntryDboModel],
		),
		E.Map[error](mapPrismaTimeEntryToDomain),
	)(ctx)
}

// Update an existing TimeEntry
func (ds *PrismaTimeEntryDataSource) Update(ctx context.Context, entry entities.TimeEntryUpdate) E.Either[error, entities.TimeEntry] {
	return F.Flow4(
		E.Eitherize1(ds.client.TimeEntryDbo.FindUnique(
			db.TimeEntryDbo.ID.Equals(entry.ID),
		).Update(
			db.TimeEntryDbo.StartTime.SetIfPresent(entry.StartTime),
			db.TimeEntryDbo.EndTime.SetIfPresent(entry.EndTime),
			db.TimeEntryDbo.Description.SetIfPresent(entry.Description),
			db.TimeEntryDbo.User.Link(
				db.UserDbo.ID.EqualsIfPresent(entry.UserID),
			),
			db.TimeEntryDbo.Project.Link(
				db.ProjectDbo.ID.EqualsIfPresent(entry.ProjectID),
			),
		).Exec),
		E.MapLeft[*db.TimeEntryDboModel](WrapDBError(fmt.Sprintf("Could not update time entry with ID %s", entry.ID))),
		E.Chain(
			TryDereference[db.TimeEntryDboModel],
		),
		E.Map[error](mapPrismaTimeEntryToDomain),
	)(ctx)
}

// Delete a TimeEntry
func (ds *PrismaTimeEntryDataSource) Delete(ctx context.Context, id string) E.Either[error, entities.TimeEntry] {
	return F.Flow4(
		E.Eitherize1(ds.client.TimeEntryDbo.FindUnique(
			db.TimeEntryDbo.ID.Equals(id),
		).Delete().Exec),
		E.MapLeft[*db.TimeEntryDboModel](WrapDBError(fmt.Sprintf("Could not delete time entry with ID %s", id))),
		E.Chain(
			TryDereference[db.TimeEntryDboModel],
		),
		E.Map[error](mapPrismaTimeEntryToDomain),
	)(ctx)
}

// FindAll retrieves all TimeEntries
func (ds *PrismaTimeEntryDataSource) FindAll(ctx context.Context) E.Either[error, []entities.TimeEntry] {
	return F.Flow3(
		E.Eitherize1(ds.client.TimeEntryDbo.FindMany().Exec),
		E.MapLeft[[]db.TimeEntryDboModel](WrapDBError("Could not retrieve time entries")),
		E.Map[error](A.Map(mapPrismaTimeEntryToDomain)),
	)(ctx)
}

// FindByUserID retrieves all TimeEntries by UserID
func (ds *PrismaTimeEntryDataSource) FindByUserID(ctx context.Context, userID string) E.Either[error, []entities.TimeEntry] {
	return F.Flow3(
		E.Eitherize1(ds.client.TimeEntryDbo.FindMany(
			db.TimeEntryDbo.UserID.Equals(userID),
		).Exec),
		E.MapLeft[[]db.TimeEntryDboModel](WrapDBError(fmt.Sprintf("Could not retrieve time entries for user with ID %s", userID))),
		E.Map[error](A.Map(mapPrismaTimeEntryToDomain)),
	)(ctx)
}

// FindByProjectID retrieves all TimeEntries by ProjectID
func (ds *PrismaTimeEntryDataSource) FindByProjectID(ctx context.Context, projectID string) E.Either[error, []entities.TimeEntry] {
	return F.Flow3(
		E.Eitherize1(ds.client.TimeEntryDbo.FindMany(
			db.TimeEntryDbo.ProjectID.Equals(projectID),
		).Exec),
		E.MapLeft[[]db.TimeEntryDboModel](WrapDBError(fmt.Sprintf("Could not retrieve time entries for project with ID %s", projectID))),
		E.Map[error](A.Map(mapPrismaTimeEntryToDomain)),
	)(ctx)
}
