package services

import (
	"bookhoard/internal/database"
	"context"
	"errors"
	"fmt"

	"github.com/jackc/pgx/v5"
	"github.com/jackc/pgx/v5/pgtype"
)

// ResolveMethod describes how a media item was resolved from a client-supplied identifier.
type ResolveMethod string

const (
	MethodNone         ResolveMethod = ""
	MethodSHA256       ResolveMethod = "sha256"        // matched on media_items.file_sha256
	MethodSHA256Format ResolveMethod = "sha256_format" // matched on media_item_formats.file_sha256 (converted/alternate format)
)

// BookResolver is the single shared path from a client-supplied identifier to a
// media_item.
//
// All client/sync interfaces (koreader, kobo, OPDS, the device-link UI, and any
// future mobile app) should resolve books through BookResolver so they share
// identical matching semantics. In particular it provides format-aware SHA-256
// matching: a converted file (KEPUB/PDF) whose hash lives in media_item_formats
// resolves just as well as the primary format. The import-time SHA-256 is the
// canonical shared identifier across every client.
type BookResolver struct {
	db *database.Queries
}

// NewBookResolver constructs a resolver backed by the given queries.
func NewBookResolver(db *database.Queries) *BookResolver {
	return &BookResolver{db: db}
}

// ResolveBySHA256 resolves a media item by its content hash. It checks the
// primary media_items.file_sha256 first, then media_item_formats.file_sha256 so
// that a converted/alternate format (KEPUB, PDF, ...) also matches. Returns the
// matched item and how it matched, or pgx.ErrNoRows when no item has this hash.
func (r *BookResolver) ResolveBySHA256(ctx context.Context, sha256 string) (database.MediaItems, ResolveMethod, error) {
	if sha256 == "" {
		return database.MediaItems{}, MethodNone, pgx.ErrNoRows
	}
	sha := pgtype.Text{String: sha256, Valid: true}

	// 1. Primary content hash (the file the media item was imported from).
	if mi, err := r.db.GetMediaItemBySHA256(ctx, sha); err == nil {
		return mi, MethodSHA256, nil
	} else if !errors.Is(err, pgx.ErrNoRows) {
		return database.MediaItems{}, MethodNone, fmt.Errorf("resolve by sha256 (primary): %w", err)
	}

	// 2. Per-format hash (a converted/alternate format: KEPUB, PDF, ...).
	formatRow, err := r.db.GetMediaItemFormatBySHA256(ctx, sha)
	if err == nil {
		if mi, err := r.db.GetMediaItem(ctx, formatRow.MediaItemID); err == nil {
			return mi, MethodSHA256Format, nil
		}
	} else if !errors.Is(err, pgx.ErrNoRows) {
		return database.MediaItems{}, MethodNone, fmt.Errorf("resolve by sha256 (format): %w", err)
	}

	return database.MediaItems{}, MethodNone, pgx.ErrNoRows
}
