package yandex

import (
	"testing"

	"github.com/stretchr/testify/assert"
	"github.com/stretchr/testify/require"
	stt "github.com/yandex-cloud/go-genproto/yandex/cloud/ai/stt/v3"
	"google.golang.org/protobuf/proto"
)

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

// response собирает ответ потока с одной репликой.
func response(text string, start, end int64) *stt.StreamingResponse {
	return &stt.StreamingResponse{
		Event: &stt.StreamingResponse_FinalRefinement{
			FinalRefinement: &stt.FinalRefinement{
				Type: &stt.FinalRefinement_NormalizedText{
					NormalizedText: &stt.AlternativeUpdate{
						Alternatives: []*stt.Alternative{
							{Text: text, StartTimeMs: start, EndTimeMs: end},
						},
					},
				},
			},
		},
	}
}

// Сохранённое читается обратно тем же: реплики со временем и плоский текст.
func TestPayloadSurvivesRoundTrip(t *testing.T) {
	responses := []*stt.StreamingResponse{
		response("Первая реплика.", 0, 900),
		response("Вторая реплика.", 900, 1800),
	}

	raw, err := encodeResponses(responses)
	require.NoError(t, err)
	require.NotEmpty(t, raw)

	decoded, err := decodeResponses(raw)
	require.NoError(t, err)
	require.Len(t, decoded, 2)

	outcome := outcomeFromResponses(decoded)
	require.Len(t, outcome.Replicas, 2)
	assert.Equal(t, "Первая реплика.", outcome.Replicas[0].Text)
	assert.Equal(t, int64(0), outcome.Replicas[0].StartMs)
	assert.Equal(t, int64(900), outcome.Replicas[0].EndMs)
	assert.Equal(t, "Вторая реплика.", outcome.Replicas[1].Text)
	assert.Equal(t, "Первая реплика. Вторая реплика.", outcome.PlainText)
}

// Ради этого свойства сохранение и сделано двоичным. Провайдер добавляет поля
// без предупреждения, и текстовое представление, собранное по нашей
// скомпилированной схеме, выбросило бы их молча — а пересчитать архив было бы
// уже не из чего: операция не переспрашивается.
func TestUnknownProviderFieldSurvivesStorage(t *testing.T) {
	original := response("Реплика.", 0, 500)

	// Так выглядит поле, которого наша схема не знает: провайдер прислал его,
	// разбор положил в неизвестные.
	unknown := protoimplUnknown(t)
	original.ProtoReflect().SetUnknown(unknown)
	require.NotEmpty(t, original.ProtoReflect().GetUnknown(), "неизвестное поле поставлено")

	raw, err := encodeResponses([]*stt.StreamingResponse{original})
	require.NoError(t, err)

	decoded, err := decodeResponses(raw)
	require.NoError(t, err)
	require.Len(t, decoded, 1)

	assert.Equal(t, []byte(unknown), []byte(decoded[0].ProtoReflect().GetUnknown()),
		"неизвестное провайдерское поле пережило запись и чтение")

	// И известное при этом на месте.
	outcome := outcomeFromResponses(decoded)
	require.Len(t, outcome.Replicas, 1)
	assert.Equal(t, "Реплика.", outcome.Replicas[0].Text)
}

// Обрезанное вложение узнаётся отказом, а не половиной расшифровки: половина
// текста, выданная за целую, тише и хуже отказа.
func TestTruncatedPayloadIsRefused(t *testing.T) {
	raw, err := encodeResponses([]*stt.StreamingResponse{response("Реплика.", 0, 500)})
	require.NoError(t, err)
	require.Greater(t, len(raw), 2)

	_, err = decodeResponses(raw[:len(raw)-2])
	assert.Error(t, err, "обрезанное вложение не разбирается молча")
}

// Пустой поток даёт пустой результат, а не отказ: «на записи нет текста» —
// законный исход распознавания.
func TestEmptyStreamGivesEmptyOutcome(t *testing.T) {
	raw, err := encodeResponses(nil)
	require.NoError(t, err)

	decoded, err := decodeResponses(raw)
	require.NoError(t, err)
	assert.Empty(t, decoded)

	outcome := outcomeFromResponses(decoded)
	assert.Empty(t, outcome.Replicas)
	assert.Empty(t, outcome.PlainText)
}

// Ответ без разбора текста реплик не даёт и разбор не роняет: провайдер шлёт по
// потоку и служебные события.
func TestResponseWithoutTextIsSkipped(t *testing.T) {
	responses := []*stt.StreamingResponse{
		{Event: &stt.StreamingResponse_FinalRefinement{FinalRefinement: &stt.FinalRefinement{}}},
		response("Реплика.", 0, 500),
		response("", 500, 600),
	}

	outcome := outcomeFromResponses(responses)
	require.Len(t, outcome.Replicas, 1, "пустые и служебные события репликами не становятся")
	assert.Equal(t, "Реплика.", outcome.PlainText)
}

// protoimplUnknown собирает байты неизвестного поля: номер поля, которого в
// нашей схеме нет, с целочисленным значением.
func protoimplUnknown(t *testing.T) []byte {
	t.Helper()

	// Поле 4095, тип varint, значение 7 — заведомо за пределами схемы ответа.
	raw, err := proto.Marshal(&stt.StreamingResponse{})
	require.NoError(t, err)
	require.Empty(t, raw)

	return []byte{0xF8, 0xFF, 0x3F, 0x07}
}
