package logging

import (
	"log/slog"
	"time"
)

// Имена внешних сервисов для поля ext.service.
const (
	ServiceQBittorrent = "qbittorrent"
	ServiceJellyfin    = "jellyfin"
	ServiceLLM         = "llm"
	ServiceTMDB        = "tmdb"
	ServiceTVDB        = "tvdb"
	ServiceTVMaze      = "tvmaze"
)

// ExtCall описывает один вызов внешнего сервиса для логирования по конвенции
// (поля ext.*). Логгер передаётся аргументом — обычно scoped-логгер загрузки,
// чтобы запись о вызове несла download_id/capability стадии.
type ExtCall struct {
	Service   string    // ext.service: qbittorrent/jellyfin/llm/tmdb/tvdb/tvmaze
	Operation string    // ext.operation: логическая операция (torrents/add, chat.completions, search/movie)
	Start     time.Time // начало вызова → duration_ms
	Status    int       // ext.status_code: HTTP-код ответа; 0 — не писать (нет кода)
	Attempt   int       // номер попытки; >0 — пишем поле retry (поле и метод Retry конфликтовали бы)
}

// StartCall заводит запись о начинающемся вызове внешнего сервиса, засекая
// время. Единая точка отсчёта длительности: клиентам не нужен собственный
// time.Now, а конвенция «время генерирует store.Now()» остаётся без исключений
// (здесь это не метка времени, а измерение — см. docs/conventions/logging.md).
func StartCall(service, operation string) ExtCall {
	return ExtCall{Service: service, Operation: operation, Start: time.Now()} //nolint:forbidigo // единственная точка отсчёта длительности внешних вызовов
}

func (c ExtCall) attrs(extra ...any) []any {
	a := make([]any, 0, 10+len(extra))
	a = append(a,
		"ext.service", c.Service,
		"ext.operation", c.Operation,
		"duration_ms", time.Since(c.Start).Milliseconds(),
	)
	if c.Status > 0 {
		a = append(a, "ext.status_code", c.Status)
	}
	if c.Attempt > 0 {
		a = append(a, "retry", c.Attempt)
	}
	return append(a, extra...)
}

// Success логирует успешный вызов внешнего сервиса (INFO) — одна запись на вызов.
func (c ExtCall) Success(log *slog.Logger, extra ...any) {
	log.Info("external call", c.attrs(extra...)...)
}

// SuccessDebug — успешный вызов на DEBUG: для частых служебных вызовов
// (поллинг qBittorrent и т.п.), которые на INFO забивали бы аудит шумом, как и
// healthcheck. Сам факт вызова логируется, но в проде (INFO) не пишется.
func (c ExtCall) SuccessDebug(log *slog.Logger, extra ...any) {
	log.Debug("external call", c.attrs(extra...)...)
}

// Retry логирует неудачную попытку, после которой будет повтор (WARN). Ошибка
// санитизируется (SanitizeErr): транспортный сбой несёт URL с секретом
// (api_key TMDB в query, токен в пути) — в лог он попасть не должен.
func (c ExtCall) Retry(log *slog.Logger, err error, extra ...any) {
	log.Warn("external call retry", c.attrs(append([]any{"error", SanitizeErr(err)}, extra...)...)...)
}

// Failure логирует окончательную неудачу вызова / недоступность сервиса (ERROR).
// Ошибка санитизируется (SanitizeErr) — см. Retry.
func (c ExtCall) Failure(log *slog.Logger, err error, extra ...any) {
	log.Error("external call failed", c.attrs(append([]any{"error", SanitizeErr(err)}, extra...)...)...)
}
