package service

import (
	"strings"
	"testing"

	"github.com/stretchr/testify/assert"
	"github.com/stretchr/testify/require"

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

// Выборка воркера владельцем не сужается: владелец решает, кому запись
// показывать, а не кому её считать. Сужение поставило бы записи одних людей в
// зависимость от того, кто первым завёл учётную запись.
func TestWorkerTakesRecordsOfEveryOwner(t *testing.T) {
	env := newPipelineEnv(t, &okMetaViewer{}, &failingConverter{})

	first, err := env.service.CreateJobFromApi(t.Context(),
		strings.NewReader("первая"), "one.mp3", newOwner(t, env))
	require.NoError(t, err)

	second, err := env.service.CreateJobFromApi(t.Context(),
		strings.NewReader("вторая"), "two.mp3", newOwner(t, env))
	require.NoError(t, err)

	// Третья — ещё одного владельца: воркер не сужается ни одним из них.
	third := newRecord(t, env)

	// Захваченная запись остаётся за держателем, и следующий вызов берёт
	// следующую, а не ту же самую.
	taken := map[string]bool{}
	for range 3 {
		acquired, err := env.recordRepo.FindAndAcquire(entity.WorkingStages())
		require.NoError(t, err, "воркер берёт записи подряд, владельцем не сужаясь")
		taken[acquired.ID] = true
	}

	assert.True(t, taken[first.Id], "запись первого владельца досталась воркеру")
	assert.True(t, taken[second.Id], "и второго")
	assert.True(t, taken[third.Id], "и третьего")

	_, err = env.recordRepo.FindAndAcquire(entity.WorkingStages())
	var missing *contract.JobNotFoundError
	assert.ErrorAs(t, err, &missing, "больше пригодных к работе записей нет")
}

// Захват отдаёт идентификатор и признак своего захвата — не перечень колонок.
// Колонки шаг читает сам: иначе всякая новая колонка записи попадала бы под
// инвариант проекта о колонках очереди.
func TestAcquireReturnsIdentifierAndHolder(t *testing.T) {
	env := newPipelineEnv(t, &okMetaViewer{}, &failingConverter{})

	owner := newOwner(t, env)
	record, err := env.service.CreateJobFromApi(t.Context(), strings.NewReader("запись"), "one.mp3", owner)
	require.NoError(t, err)

	acquired, err := env.recordRepo.FindAndAcquire(entity.WorkingStages())
	require.NoError(t, err)

	assert.Equal(t, record.Id, acquired.ID, "захват назвал запись")
	assert.NotEmpty(t, acquired.Holder, "и признак своего захвата")

	// Колонки читаются отдельным чтением, и владелец среди них.
	read := readRecord(t, env, record.Id)
	assert.Equal(t, owner, read.OwnerID)
	assert.Equal(t, acquired.Holder, *read.AcquisitionID, "признак захвата записан в саму запись")
	require.NotNil(t, read.AcquireExpiresAt, "срок протухания приехал с рубежом")
}

// Шаг конвейера владельца не затирает: сохранение кладёт только то, чем
// распоряжается конвейер, и владельца среди этого нет.
func TestPipelineStepKeepsOwner(t *testing.T) {
	env := newPipelineEnv(t, &okMetaViewer{}, &failingConverter{})

	owner := newOwner(t, env)
	record, err := env.service.CreateJobFromApi(t.Context(), strings.NewReader("запись"), "one.mp3", owner)
	require.NoError(t, err)

	// Отказ приведения — приговор записи: шаг останавливает её и сохраняет. Это
	// сохранение владельца тронуть не должно.
	require.NoError(t, env.service.RunStep(t.Context()))

	after := readRecord(t, env, record.Id)
	require.True(t, after.IsHalted(), "шаг записал свой приговор")
	assert.Equal(t, owner, after.OwnerID, "владелец пережил шаг")
}

// Приём без владельца записи не заводит. Отказ стоит здесь раньше схемы: он
// отвечает понятной ошибкой до того, как запись ляжет в хранилище, а схема
// отказала бы уже после укладки файла.
func TestCreateJobFromApiRequiresOwner(t *testing.T) {
	env := newPipelineEnv(t, &okMetaViewer{}, &failingConverter{})

	_, err := env.service.CreateJobFromApi(t.Context(), strings.NewReader("запись"), "one.mp3", "")
	require.ErrorIs(t, err, contract.ErrOwnerRequired)
}

// Чужая запись, ничья и несуществующая отвечают одним и тем же: по разнице
// ответов иначе перебирается список заведённых записей.
func TestGetByIDHidesForeignRecords(t *testing.T) {
	env := newPipelineEnv(t, &okMetaViewer{}, &failingConverter{})

	owner := newOwner(t, env)
	stranger := newOwner(t, env)

	record, err := env.service.CreateJobFromApi(t.Context(), strings.NewReader("запись"), "one.mp3", owner)
	require.NoError(t, err)

	var missing *contract.JobNotFoundError

	_, err = env.recordRepo.GetByID(record.Id, stranger)
	require.ErrorAs(t, err, &missing, "чужая запись неотличима от несуществующей")

	_, err = env.recordRepo.GetByID(record.Id, "")
	require.ErrorAs(t, err, &missing, "пустой владелец не совпадает ни с чем")

	own, err := env.recordRepo.GetByID(record.Id, owner)
	require.NoError(t, err, "своя запись отдаётся")
	assert.Equal(t, record.Id, own.Id)
}
