package human

import (
	"context"
	"errors"
	"strings"
	"time"

	"github.com/jackc/pgx/v5"
	"github.com/jackc/pgx/v5/pgxpool"
	"go.jetify.com/typeid"
)

var errNotFound = errors.New("not found")

type humanIDPrefix struct{}

func (humanIDPrefix) Prefix() string { return "human" }

type humanID struct {
	typeid.TypeID[humanIDPrefix]
}

func newHumanID() (humanID, error) {
	return typeid.New[humanID]()
}

func emailPrefix(email string) string {
	return strings.Split(email, "@")[0]
}

type repository interface {
	getByEmail(ctx context.Context, email string) (*Human, error)
	getByID(ctx context.Context, id string) (*Human, error)
	create(ctx context.Context, email string) (*Human, error)
	exists(ctx context.Context, email string) (bool, error)
	listAll(ctx context.Context) ([]*Human, error)
	updateEmailNotificationsEnabled(ctx context.Context, id string, enabled bool) error
	updateLastEmailNotificationSentAt(ctx context.Context, id string, t time.Time) error
	updateAvatarObjectID(ctx context.Context, id string, objectID *string) error
}

type repositoryImpl struct {
	pool *pgxpool.Pool
}

func newRepository(pool *pgxpool.Pool) repository {
	return &repositoryImpl{pool: pool}
}

func (r *repositoryImpl) getByEmail(ctx context.Context, email string) (*Human, error) {
	var h Human
	err := r.pool.QueryRow(ctx,
		`SELECT id, email, email_notifications_enabled, last_email_notification_sent_at, created_at, avatar_object_id FROM humans WHERE email = $1`,
		email,
	).Scan(&h.ID, &h.Email, &h.EmailNotificationsEnabled, &h.LastEmailNotificationSentAt, &h.CreatedAt, &h.AvatarObjectID)
	if err != nil {
		if errors.Is(err, pgx.ErrNoRows) {
			return nil, errNotFound
		}
		return nil, err
	}
	h.EmailPrefix = emailPrefix(h.Email)
	return &h, nil
}

func (r *repositoryImpl) getByID(ctx context.Context, id string) (*Human, error) {
	var h Human
	err := r.pool.QueryRow(ctx,
		`SELECT id, email, email_notifications_enabled, last_email_notification_sent_at, created_at, avatar_object_id FROM humans WHERE id = $1`,
		id,
	).Scan(&h.ID, &h.Email, &h.EmailNotificationsEnabled, &h.LastEmailNotificationSentAt, &h.CreatedAt, &h.AvatarObjectID)
	if err != nil {
		if errors.Is(err, pgx.ErrNoRows) {
			return nil, errNotFound
		}
		return nil, err
	}
	h.EmailPrefix = emailPrefix(h.Email)
	return &h, nil
}

func (r *repositoryImpl) create(ctx context.Context, email string) (*Human, error) {
	id, err := newHumanID()
	if err != nil {
		return nil, err
	}

	var h Human
	err = r.pool.QueryRow(ctx,
		`INSERT INTO humans (id, email) VALUES ($1, $2)
		 RETURNING id, email, email_notifications_enabled, last_email_notification_sent_at, created_at`,
		id.String(), email,
	).Scan(&h.ID, &h.Email, &h.EmailNotificationsEnabled, &h.LastEmailNotificationSentAt, &h.CreatedAt)
	if err != nil {
		return nil, err
	}

	h.EmailPrefix = emailPrefix(h.Email)
	return &h, nil
}

func (r *repositoryImpl) exists(ctx context.Context, email string) (bool, error) {
	var exists bool
	err := r.pool.QueryRow(ctx,
		`SELECT EXISTS(SELECT 1 FROM humans WHERE email = $1)`,
		email,
	).Scan(&exists)
	if err != nil {
		return false, err
	}
	return exists, nil
}

func (r *repositoryImpl) listAll(ctx context.Context) ([]*Human, error) {
	rows, err := r.pool.Query(ctx,
		`SELECT id, email, email_notifications_enabled, last_email_notification_sent_at, created_at, avatar_object_id FROM humans`,
	)
	if err != nil {
		return nil, err
	}
	defer rows.Close()

	var humans []*Human
	for rows.Next() {
		var h Human
		if err := rows.Scan(&h.ID, &h.Email, &h.EmailNotificationsEnabled, &h.LastEmailNotificationSentAt, &h.CreatedAt, &h.AvatarObjectID); err != nil {
			return nil, err
		}
		h.EmailPrefix = emailPrefix(h.Email)
		humans = append(humans, &h)
	}
	return humans, rows.Err()
}

func (r *repositoryImpl) updateEmailNotificationsEnabled(ctx context.Context, id string, enabled bool) error {
	result, err := r.pool.Exec(ctx,
		`UPDATE humans SET email_notifications_enabled = $2 WHERE id = $1`,
		id, enabled,
	)
	if err != nil {
		return err
	}
	if result.RowsAffected() == 0 {
		return errNotFound
	}
	return nil
}

func (r *repositoryImpl) updateLastEmailNotificationSentAt(ctx context.Context, id string, t time.Time) error {
	result, err := r.pool.Exec(ctx,
		`UPDATE humans SET last_email_notification_sent_at = $2 WHERE id = $1`,
		id, t,
	)
	if err != nil {
		return err
	}
	if result.RowsAffected() == 0 {
		return errNotFound
	}
	return nil
}

func (r *repositoryImpl) updateAvatarObjectID(ctx context.Context, id string, objectID *string) error {
	result, err := r.pool.Exec(ctx,
		`UPDATE humans SET avatar_object_id = $2 WHERE id = $1`,
		id, objectID,
	)
	if err != nil {
		return err
	}
	if result.RowsAffected() == 0 {
		return errNotFound
	}
	return nil
}
