// Package qbit is the torrent-client integration layer. One TorrentClient
// adapter implementation talks to qBittorrent's WebUI API directly; a thin
// wrapper handles the qui reverse-proxy mode. See PROJECT.md §7.1 for the
// operator-side trade-off; see CLAUDE-phase3.md for the Phase 3 boundary.
package qbit

import (
	"context"
	"fmt"
	"sync"
)

// TorrentClient is the contract every adapter implements. Methods that take
// hashes accept the qBittorrent canonical lower-case hex form.
type TorrentClient interface {
	// ID returns the operator-facing identifier from config.
	ID() string

	// Login establishes a session if the adapter needs one. Subsequent
	// calls reuse the cached session. Returns an error if credentials are
	// missing or rejected.
	Login(ctx context.Context) error

	// Sync returns either a full state snapshot (when rid is 0 or the
	// server's rid is uninitialized) or a delta since rid. The new rid is
	// in the returned MainData.
	Sync(ctx context.Context, rid int) (MainData, error)

	// List returns a slice of torrents matching filter constraints. Returns
	// the full list if filter is empty.
	List(ctx context.Context, filter ListFilter) ([]Torrent, error)

	// Get fetches one torrent by hash.
	Get(ctx context.Context, hash string) (Torrent, error)

	// Trackers returns the per-torrent tracker rows.
	Trackers(ctx context.Context, hash string) ([]Tracker, error)

	// Add accepts a magnet link or .torrent bytes plus add-time options.
	Add(ctx context.Context, req AddRequest) error

	// Pause / Resume / Recheck are bulk mutations.
	Pause(ctx context.Context, hashes []string) error
	Resume(ctx context.Context, hashes []string) error
	Recheck(ctx context.Context, hashes []string) error

	// Delete removes the torrents; deleteFiles=true also removes downloaded
	// content from disk.
	Delete(ctx context.Context, hashes []string, deleteFiles bool) error

	// SetCategory and SetTags mutate metadata.
	SetCategory(ctx context.Context, hashes []string, category string) error
	SetTags(ctx context.Context, hashes []string, tags []string) error

	// Health is a cheap reachability check.
	Health(ctx context.Context) error
}

// ListFilter is the query-string-style filter accepted by /api/v2/torrents/info.
type ListFilter struct {
	State    string // "downloading" | "seeding" | "completed" | ...
	Category string
	Tag      string
	Sort     string
	Reverse  bool
	Limit    int
	Offset   int
}

// Config is the shape passed to adapter constructors. Adapter-specific
// settings live in TypeConfigJSON.
type Config struct {
	ID                  string
	Name                string
	Type                string
	BaseURL             string
	ProxyViaQUI         bool
	PollIntervalSeconds int
	TypeConfigJSON      []byte
}

// Factory constructs a TorrentClient. Each adapter type registers one in init().
type Factory func(cfg Config, deps Deps) (TorrentClient, error)

// Deps bundles the cross-cutting dependencies adapters need. Phase 3 ships
// only the secrets store (for credentials lookup). Later phases may add the
// eventbus or a shared HTTP client.
type Deps struct {
	Secrets SecretsAccessor
}

// SecretsAccessor is the narrow interface adapters need from
// internal/secrets — abstracted so the qbit package doesn't depend on the
// secrets package directly (test ergonomics).
type SecretsAccessor interface {
	Get(ctx context.Context, key string) ([]byte, error)
}

var (
	factoryMu sync.RWMutex
	factories = map[string]Factory{}
)

// Register binds a type string ("qbit", "qui") to its factory. Called from
// init() in the adapter file.
func Register(typ string, f Factory) {
	factoryMu.Lock()
	defer factoryMu.Unlock()
	factories[typ] = f
}

// New constructs an adapter for cfg.Type. Returns an error if the type is
// unknown.
func New(cfg Config, deps Deps) (TorrentClient, error) {
	factoryMu.RLock()
	f, ok := factories[cfg.Type]
	factoryMu.RUnlock()
	if !ok {
		return nil, fmt.Errorf("qbit: unknown client type %q (registered: %v)", cfg.Type, RegisteredTypes())
	}
	return f(cfg, deps)
}

// RegisteredTypes returns the list of registered client types.
func RegisteredTypes() []string {
	factoryMu.RLock()
	defer factoryMu.RUnlock()
	out := make([]string, 0, len(factories))
	for k := range factories {
		out = append(out, k)
	}
	return out
}

// CredentialSecretKey returns the canonical secrets-store key for a
// client's password (or session token if pre-shared).
func CredentialSecretKey(clientID string) string {
	return "torrent_client:" + clientID + ":password"
}
