package human

import (
	"context"
	"errors"
	"strings"

	"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)
}

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, created_at FROM humans WHERE email = $1`,
		email,
	).Scan(&h.ID, &h.Email, &h.CreatedAt)
	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, created_at FROM humans WHERE id = $1`,
		id,
	).Scan(&h.ID, &h.Email, &h.CreatedAt)
	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, created_at`,
		id.String(), email,
	).Scan(&h.ID, &h.Email, &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
}
