package service

import (
	"errors"
	"fmt"
	"log/slog"
	"testing"

	"git.vakhrushev.me/av/transcriber/internal/contract"
	"git.vakhrushev.me/av/transcriber/internal/entity"
)

// Путь признака «работы нет» состоит из двух звеньев: репозиторий рождает
// «пригодной записи не нашлось», сервис переводит это в «работы нет», и уже его
// читает воркер. Проверки воркера подменяют работу целиком и второе звено не
// видят — без этого файла правку в сервисе принимал бы только линтер, а он судит
// форму записи, а не то, узнаётся ли признак на самом деле.

// stubRecordRepo отдаёт заданную ошибку на запрос записи. Прочих методов
// проверки этого файла не зовут.
type stubRecordRepo struct {
	err error
}

func (r *stubRecordRepo) Create(*entity.AudioRecord) error       { return nil }
func (r *stubRecordRepo) Save(*entity.AudioRecord, string) error { return nil }

func (r *stubRecordRepo) GetByID(string, string) (*entity.AudioRecord, error) {
	return nil, errors.New("не зовётся этими проверками")
}

func (r *stubRecordRepo) Get(string) (*entity.AudioRecord, error) {
	return nil, errors.New("не зовётся этими проверками")
}

func (r *stubRecordRepo) List(contract.RecordQuery) (*contract.RecordPage, error) {
	return &contract.RecordPage{}, nil
}

func (r *stubRecordRepo) ResolveTopicNames(string, []string) (map[string]string, error) {
	return map[string]string{}, nil
}

func (r *stubRecordRepo) FindAndAcquire([]entity.Stage) (*contract.AcquiredRecord, error) {
	return nil, r.err
}

func serviceWithRepo(repo contract.AudioRecordRepository) *TranscribeService {
	return NewTranscribeService(
		Repositories{Records: repo},
		nil, nil, nil,
		entity.StuckLimits{},
		slog.New(slog.DiscardHandler),
	)
}

// Репозиторий вправе добавить своему отказу пояснение — соседние ветки того же
// метода уже оборачивают ошибки `%w` подряд. Пока признак узнавался приведением
// типа, первая такая обёртка превратила бы пустой прогон в отказ: воркер начал
// бы писать в журнал раз в секунду на каждом воркере пула.
func TestAcquireTranslatesWrappedNotFoundToNoop(t *testing.T) {
	svc := serviceWithRepo(&stubRecordRepo{
		err: fmt.Errorf("find and acquire record: %w",
			&contract.JobNotFoundError{Message: "no record is ready for work"}),
	})

	_, _, err := svc.acquire()

	var noop *contract.NoopJobError
	if !errors.As(err, &noop) {
		t.Fatalf("обёрнутое «работы нет» не переведено в пустой прогон: получено %v", err)
	}
}

// Оборотная сторона: настоящий отказ хранилища пустым прогоном считаться не
// должен, иначе запись молча крутилась бы в цикле без единой записи в журнале.
func TestAcquireKeepsRealFailure(t *testing.T) {
	svc := serviceWithRepo(&stubRecordRepo{err: errors.New("database is gone")})

	_, _, err := svc.acquire()

	var noop *contract.NoopJobError
	if errors.As(err, &noop) {
		t.Fatalf("отказ хранилища зачтён пустым прогоном: %v", err)
	}
	if err == nil {
		t.Fatal("отказ хранилища потерян")
	}
}
