package db

import (
	"database/sql"
	"errors"
	"fmt"

	"git.wntrmute.dev/mc/mcr/internal/gc"
)

// FindAndDeleteUnreferencedBlobs finds all blob rows with no manifest_blobs
// entries, deletes them in a single transaction, and returns the digests
// and sizes of the deleted blobs.
func (d *DB) FindAndDeleteUnreferencedBlobs() ([]gc.UnreferencedBlob, error) {
	tx, err := d.Begin()
	if err != nil {
		return nil, fmt.Errorf("db: begin gc transaction: %w", err)
	}

	// Find unreferenced blobs.
	rows, err := tx.Query(
		`SELECT b.id, b.digest, b.size FROM blobs b
		 LEFT JOIN manifest_blobs mb ON mb.blob_id = b.id
		 WHERE mb.manifest_id IS NULL`,
	)
	if err != nil {
		_ = tx.Rollback()
		return nil, fmt.Errorf("db: find unreferenced blobs: %w", err)
	}

	var unreferenced []gc.UnreferencedBlob
	var ids []int64
	for rows.Next() {
		var id int64
		var blob gc.UnreferencedBlob
		if err := rows.Scan(&id, &blob.Digest, &blob.Size); err != nil {
			_ = rows.Close()
			_ = tx.Rollback()
			return nil, fmt.Errorf("db: scan unreferenced blob: %w", err)
		}
		unreferenced = append(unreferenced, blob)
		ids = append(ids, id)
	}
	if err := rows.Err(); err != nil {
		_ = rows.Close()
		_ = tx.Rollback()
		return nil, fmt.Errorf("db: iterate unreferenced blobs: %w", err)
	}
	_ = rows.Close()

	// Delete the unreferenced blob rows.
	for _, id := range ids {
		if _, err := tx.Exec(`DELETE FROM blobs WHERE id = ?`, id); err != nil {
			_ = tx.Rollback()
			return nil, fmt.Errorf("db: delete blob %d: %w", id, err)
		}
	}

	if err := tx.Commit(); err != nil {
		return nil, fmt.Errorf("db: commit gc transaction: %w", err)
	}

	return unreferenced, nil
}

// BlobExistsByDigest checks whether a blob row exists for the given digest.
func (d *DB) BlobExistsByDigest(digest string) (bool, error) {
	var count int
	err := d.QueryRow(`SELECT COUNT(*) FROM blobs WHERE digest = ?`, digest).Scan(&count)
	if err != nil {
		if errors.Is(err, sql.ErrNoRows) {
			return false, nil
		}
		return false, fmt.Errorf("db: blob exists by digest: %w", err)
	}
	return count > 0, nil
}
