// Package bookstore defines the segregated read/write interfaces for book,
// chapter, ranking, progress, audio, and presign data.
//
// Interface segregation:
//   - BookWriter   — used by the runner to persist scraped data.
//   - BookReader   — used by the backend to serve book/chapter data.
//   - RankingStore — used by both runner (write) and backend (read).
//   - PresignStore — used only by the backend for URL signing.
//   - AudioStore   — used by the runner to store audio; backend for presign.
//   - ProgressStore— used only by the backend for reading progress.
//
// Concrete implementations live in internal/storage.
package bookstore

import (
	"context"
	"io"
	"time"

	"github.com/libnovel/backend/internal/domain"
)

// BookWriter is the write side used by the runner after scraping a book.
type BookWriter interface {
	// WriteMetadata upserts all bibliographic fields for a book.
	WriteMetadata(ctx context.Context, meta domain.BookMeta) error

	// WriteChapter stores a fully-scraped chapter's text in MinIO and
	// updates the chapters_idx record in PocketBase.
	WriteChapter(ctx context.Context, slug string, chapter domain.Chapter) error

	// WriteChapterRefs persists chapter metadata (number + title) into
	// chapters_idx without fetching or storing chapter text.
	WriteChapterRefs(ctx context.Context, slug string, refs []domain.ChapterRef) error

	// ChapterExists returns true if the markdown object for ref already exists.
	ChapterExists(ctx context.Context, slug string, ref domain.ChapterRef) bool

	// DeduplicateChapters removes duplicate chapters_idx records for slug,
	// keeping only one record per chapter number (the one with the latest
	// updated timestamp). Returns the number of duplicate records deleted.
	DeduplicateChapters(ctx context.Context, slug string) (int, error)
}

// BookReader is the read side used by the backend to serve content.
type BookReader interface {
	// ReadMetadata returns the metadata for slug.
	// Returns (zero, false, nil) when not found.
	ReadMetadata(ctx context.Context, slug string) (domain.BookMeta, bool, error)

	// ListBooks returns all books sorted alphabetically by title.
	ListBooks(ctx context.Context) ([]domain.BookMeta, error)

	// LocalSlugs returns the set of slugs that have metadata stored.
	LocalSlugs(ctx context.Context) (map[string]bool, error)

	// MetadataMtime returns the Unix-second mtime of the metadata record, or 0.
	MetadataMtime(ctx context.Context, slug string) int64

	// ReadChapter returns the raw markdown for chapter number n.
	ReadChapter(ctx context.Context, slug string, n int) (string, error)

	// ListChapters returns all stored chapters for slug, sorted by number.
	ListChapters(ctx context.Context, slug string) ([]domain.ChapterInfo, error)

	// CountChapters returns the count of stored chapters.
	CountChapters(ctx context.Context, slug string) int

	// ReindexChapters rebuilds chapters_idx from MinIO objects for slug.
	ReindexChapters(ctx context.Context, slug string) (int, error)
}

// RankingStore covers ranking reads and writes.
type RankingStore interface {
	// WriteRankingItem upserts a single ranking entry (keyed on Slug).
	WriteRankingItem(ctx context.Context, item domain.RankingItem) error

	// ReadRankingItems returns all ranking items sorted by rank ascending.
	ReadRankingItems(ctx context.Context) ([]domain.RankingItem, error)

	// RankingFreshEnough returns true when ranking rows exist and the most
	// recent Updated timestamp is within maxAge.
	RankingFreshEnough(ctx context.Context, maxAge time.Duration) (bool, error)
}

// AudioStore covers audio object storage (runner writes; backend reads).
type AudioStore interface {
	// AudioObjectKey returns the MinIO object key for a cached MP3 audio file.
	// Format: {slug}/{n}/{voice}.mp3
	AudioObjectKey(slug string, n int, voice string) string

	// AudioObjectKeyExt returns the MinIO object key for a cached audio file
	// with a custom extension (e.g. "mp3" or "wav").
	AudioObjectKeyExt(slug string, n int, voice, ext string) string

	// AudioExists returns true when the audio object is present in MinIO.
	AudioExists(ctx context.Context, key string) bool

	// PutAudio stores raw audio bytes under the given MinIO object key.
	PutAudio(ctx context.Context, key string, data []byte) error

	// PutAudioStream uploads audio from r to MinIO under key.
	// size must be the exact byte length of r, or -1 to use multipart upload.
	// contentType should be "audio/mpeg" or "audio/wav".
	PutAudioStream(ctx context.Context, key string, r io.Reader, size int64, contentType string) error
}

// PresignStore generates short-lived URLs — used exclusively by the backend.
type PresignStore interface {
	// PresignChapter returns a presigned GET URL for a chapter markdown object.
	PresignChapter(ctx context.Context, slug string, n int, expires time.Duration) (string, error)

	// PresignAudio returns a presigned GET URL for an audio object.
	PresignAudio(ctx context.Context, key string, expires time.Duration) (string, error)

	// PresignAvatarUpload returns a short-lived presigned PUT URL for uploading
	// an avatar image. ext should be "jpg", "png", or "webp".
	PresignAvatarUpload(ctx context.Context, userID, ext string) (uploadURL, key string, err error)

	// PresignAvatarURL returns a presigned GET URL for a user's avatar.
	// Returns ("", false, nil) when no avatar exists.
	PresignAvatarURL(ctx context.Context, userID string) (string, bool, error)

	// PutAvatar stores raw image bytes for a user avatar directly in MinIO.
	// ext should be "jpg", "png", or "webp". Returns the object key.
	PutAvatar(ctx context.Context, userID, ext, contentType string, data []byte) (key string, err error)

	// DeleteAvatar removes all avatar objects for a user.
	DeleteAvatar(ctx context.Context, userID string) error
}

// ProgressStore covers per-session reading progress — backend only.
type ProgressStore interface {
	// GetProgress returns the reading progress for the given session + slug.
	GetProgress(ctx context.Context, sessionID, slug string) (domain.ReadingProgress, bool)

	// SetProgress saves or updates reading progress.
	SetProgress(ctx context.Context, sessionID string, p domain.ReadingProgress) error

	// AllProgress returns all progress entries for a session.
	AllProgress(ctx context.Context, sessionID string) ([]domain.ReadingProgress, error)

	// DeleteProgress removes progress for a specific slug.
	DeleteProgress(ctx context.Context, sessionID, slug string) error
}

// CoverStore covers book cover image storage in MinIO.
// The runner writes covers during catalogue refresh; the backend reads them.
type CoverStore interface {
	// PutCover stores a raw cover image for a book identified by slug.
	PutCover(ctx context.Context, slug string, data []byte, contentType string) error

	// GetCover retrieves the cover image for a book. Returns (nil, false, nil)
	// when no cover exists for the given slug.
	GetCover(ctx context.Context, slug string) ([]byte, string, bool, error)

	// CoverExists returns true when a cover image is stored for slug.
	CoverExists(ctx context.Context, slug string) bool
}

// AIJobStore manages AI generation jobs tracked in PocketBase.
type AIJobStore interface {
	// CreateAIJob inserts a new ai_job record with status=running and returns its ID.
	CreateAIJob(ctx context.Context, job domain.AIJob) (string, error)
	// GetAIJob retrieves a single ai_job by ID.
	// Returns (zero, false, nil) when not found.
	GetAIJob(ctx context.Context, id string) (domain.AIJob, bool, error)
	// UpdateAIJob patches an existing ai_job record with the given fields.
	UpdateAIJob(ctx context.Context, id string, fields map[string]any) error
	// ListAIJobs returns all ai_job records sorted by started descending.
	ListAIJobs(ctx context.Context) ([]domain.AIJob, error)
}

// ChapterImageStore covers per-chapter illustration images stored in MinIO.
// The backend admin writes them; the backend serves them.
type ChapterImageStore interface {
	// PutChapterImage stores a raw image for chapter n of slug in MinIO.
	PutChapterImage(ctx context.Context, slug string, n int, data []byte, contentType string) error

	// GetChapterImage retrieves the image for chapter n of slug.
	// Returns (nil, "", false, nil) when no image exists.
	GetChapterImage(ctx context.Context, slug string, n int) ([]byte, string, bool, error)

	// ChapterImageExists returns true when an image is stored for slug/n.
	ChapterImageExists(ctx context.Context, slug string, n int) bool
}

// TranslationStore covers machine-translated chapter storage in MinIO.
// The runner writes translations; the backend reads them.
type TranslationStore interface {
	// TranslationObjectKey returns the MinIO object key for a cached translation.
	TranslationObjectKey(lang, slug string, n int) string

	// TranslationExists returns true when the translation object is present in MinIO.
	TranslationExists(ctx context.Context, key string) bool

	// PutTranslation stores raw translated markdown under the given MinIO object key.
	PutTranslation(ctx context.Context, key string, data []byte) error

	// GetTranslation retrieves translated markdown from MinIO.
	GetTranslation(ctx context.Context, key string) (string, error)
}

// Chapter represents a single chapter extracted from PDF/EPUB.
type Chapter struct {
	Number   int    // 1-based chapter number
	Title    string // chapter title (may be empty)
	Content  string // plain text content
}

// BookImporter handles PDF/EPUB file parsing and chapter extraction.
// Used by the runner to import books from uploaded files.
type BookImporter interface {
	// Import extracts chapters from a PDF or EPUB file stored in MinIO.
	// Returns the extracted chapters or an error.
	Import(ctx context.Context, objectKey, fileType string) ([]Chapter, error)
}
