package depot

import (
	"context"
	"errors"

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

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

type depotIDPrefix struct{}

func (depotIDPrefix) Prefix() string { return "dpo" }

type depotID struct {
	typeid.TypeID[depotIDPrefix]
}

func newDepotID() (depotID, error) {
	return typeid.New[depotID]()
}

type repository interface {
	create(ctx context.Context, obj *Object) (*Object, error)
	getByID(ctx context.Context, id string) (*Object, error)
	setContainsContent(ctx context.Context, id string, containsContent bool) error
	delete(ctx context.Context, id string) error
	exists(ctx context.Context, id string) (bool, error)
}

type repositoryImpl struct {
	pool *pgxpool.Pool
}

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

func (r *repositoryImpl) create(ctx context.Context, obj *Object) (*Object, error) {
	id, err := newDepotID()
	if err != nil {
		return nil, err
	}

	var result Object
	err = r.pool.QueryRow(ctx,
		`INSERT INTO depot_objects (id, name, content_type, content_length, bucket_name, object_key, contains_content)
		 VALUES ($1, $2, $3, $4, $5, $6, $7)
		 RETURNING id, name, content_type, content_length, bucket_name, object_key, contains_content, created_at`,
		id.String(), obj.Name, obj.ContentType, obj.ContentLength, obj.BucketName, obj.ObjectKey, obj.ContainsContent,
	).Scan(&result.ID, &result.Name, &result.ContentType, &result.ContentLength,
		&result.BucketName, &result.ObjectKey, &result.ContainsContent, &result.CreatedAt)
	if err != nil {
		return nil, err
	}

	return &result, nil
}

func (r *repositoryImpl) getByID(ctx context.Context, id string) (*Object, error) {
	var obj Object
	err := r.pool.QueryRow(ctx,
		`SELECT id, name, content_type, content_length, bucket_name, object_key, contains_content, created_at
		 FROM depot_objects WHERE id = $1`,
		id,
	).Scan(&obj.ID, &obj.Name, &obj.ContentType, &obj.ContentLength,
		&obj.BucketName, &obj.ObjectKey, &obj.ContainsContent, &obj.CreatedAt)
	if err != nil {
		if errors.Is(err, pgx.ErrNoRows) {
			return nil, errNotFound
		}
		return nil, err
	}
	return &obj, nil
}

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

func (r *repositoryImpl) delete(ctx context.Context, id string) error {
	result, err := r.pool.Exec(ctx, `DELETE FROM depot_objects WHERE id = $1`, id)
	if err != nil {
		return err
	}
	if result.RowsAffected() == 0 {
		return errNotFound
	}
	return nil
}

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