package migrations

import (
	"errors"
	"fmt"

	"github.com/pocketbase/pocketbase/core"
)

// up202608140001 заводит владельца записи.
//
// Колонка — связь с коллекцией пользователей: хранилище само следит, чтобы
// владельцем стояла существующая учётная запись, а не строка, похожая на её
// идентификатор.
//
// Пустое значение допустимо, и это решение с названной ценой. Записи, принятые
// ботом, владельца не имеют вовсе: связи чата Telegram с учётной записью сервис
// не ведёт, её заводит отдельная задача. Обязательность для приёма по HTTP
// держит поэтому сам приём, а не схема.
//
// Каскадное удаление выключено, но одного этого мало: при выключенном каскаде
// хранилище **вынимает** идентификатор из поля связи и сохраняет запись без
// проверок, то есть архив удалённого пользователя стал бы ничьим и не достался
// бы никому. Поэтому удаление учётной записи, у которой остались задачи,
// отвергается слоем приложения — `GuardOwnerDeletion`.
func up202608140001(app core.App) error {
	users, err := app.FindCollectionByNameOrId(UsersCollection)
	if err != nil {
		return fmt.Errorf("failed to find users collection: %w", err)
	}

	jobs, err := app.FindCollectionByNameOrId(JobsCollection)
	if err != nil {
		return fmt.Errorf("failed to find jobs collection: %w", err)
	}

	jobs.Fields.Add(ownerField(users.Id))

	if err := app.Save(jobs); err != nil {
		return fmt.Errorf("failed to add owner to jobs: %w", err)
	}

	files, err := app.FindCollectionByNameOrId(FilesCollection)
	if err != nil {
		return fmt.Errorf("failed to find files collection: %w", err)
	}

	files.Fields.Add(ownerField(users.Id))

	// Правило просмотра сужается владельцем. Прежнее пускало всякого узнанного:
	// владельца у записи тогда не было, и сужать выборку было нечем. Без этой
	// строки разграничение закрыло бы метаданные задачи и оставило открытым
	// содержимое — то самое, что оно и заведено прятать: знание идентификатора
	// файловой записи равнялось бы праву скачать чужое аудио.
	files.ViewRule = ptr(`@request.auth.id != "" && owner = @request.auth.id`)

	if err := app.Save(files); err != nil {
		return fmt.Errorf("failed to narrow files by owner: %w", err)
	}

	return nil
}

// ownerField собирает описание колонки владельца. Обе коллекции получают
// одинаковую: разойдясь, они дали бы разное поведение у задачи и у её файла.
func ownerField(usersCollectionID string) *core.RelationField {
	return &core.RelationField{
		Name:         "owner",
		CollectionId: usersCollectionID,
		MaxSelect:    1,
		// Пустое значение допустимо — см. шапку шага. Умолчания у колонки нет:
		// связь его не имеет по устройству, и запись не достаётся никому по
		// недосмотру схемы.
		Required: false,
		// Удаление учётной записи не уносит её записи следом: сервис объявлен
		// архивом. Что происходит вместо этого, держит `GuardOwnerDeletion`.
		CascadeDelete: false,
	}
}

// down202608140001 снимает колонку с обеих коллекций и возвращает правило
// просмотра файлов к тому, что стояло до шага, — «всякий узнанный».
func down202608140001(app core.App) error {
	for _, name := range []string{JobsCollection, FilesCollection} {
		collection, err := app.FindCollectionByNameOrId(name)
		if err != nil {
			return fmt.Errorf("failed to find collection %s: %w", name, err)
		}

		field := collection.Fields.GetByName("owner")
		if field == nil {
			return errors.New("collection " + name + " has no owner field")
		}
		collection.Fields.RemoveById(field.GetId())

		if name == FilesCollection {
			collection.ViewRule = ptr(`@request.auth.id != ""`)
		}

		if err := app.Save(collection); err != nil {
			return fmt.Errorf("failed to drop owner from %s: %w", name, err)
		}
	}

	return nil
}
