using System.ComponentModel;
using Evos.Api.Repository;
using Evos.Api.Repository.VCS;
using Evos.Api.Samples.Infrastructure;
using Evos.Collaboration.VCS;
using Evos.ModelsRepository;
using Evos.ModelsRepository.BasicData;
using Evos.ModelsRepository.DataProvider.TransactionDescriptions;
using Evos.ModelsRepository.SpaceModel;

namespace Evos.Api.Samples
{
    /// Низкоуровневое API: локальная история версий репозитория — коммиты, ветки, откат.
    /// Всё, что здесь показано, работает без Collaboration-сервера: история хранится
    /// в самом репозитории. Обмен с сервером (Pull/Push) — см. CollaborationSample.
    [Localizable(false)]
    public static class VcsSample
    {
        private const string MainBranchName = "main";

        public static void Run()
        {
            using var sampleRepository = SampleRepository.CreateNew();
            var repository = sampleRepository.Repository;

            // Контроллер VCS строится поверх уже открытого репозитория.
            var vcs = ApiRepositoryVcsControllerFactory.Create(repository);

            // Первый коммит обязан создать ветку: у нового репозитория её ещё нет.
            AddProduct(repository, "Насос");
            SampleCheck.That(vcs.CreateCommitInNewBranch("Добавил насос", MainBranchName), "первый коммит должен создаться");

            AddProduct(repository, "Клапан");
            SampleCheck.That(vcs.CreateCommit("Добавил клапан"), "второй коммит должен создаться");

            ShowCommitHistory(repository);

            RenameBranch(repository, vcs);
            RollBackLastCommit(repository, vcs);
        }

        /// Ветку переименовывают по её идентификатору — его берут из истории.
        /// Важно: read-соединение надо закрыть до записи. Операции VCS открывают свою
        /// write-транзакцию, а репозиторий не даёт брать write-блокировку поверх read-блокировки.
        private static void RenameBranch(IConcurrentRepository repository, IApiRepositoryVcsController vcs)
        {
            BranchId branchId;
            using (var vcsRead = repository.VCSReadConnection())
            {
                branchId = vcsRead.GetBranches().Single().Id;
            }

            SampleCheck.That(vcs.RenameBranch(branchId, "release"), "ветка должна переименоваться");

            using var afterRename = repository.VCSReadConnection();
            Console.WriteLine($"Ветка теперь называется: {afterRename.GetBranches().Single().BranchName}");
        }

        /// Strip удаляет коммит вместе с изменениями, которые он принёс.
        /// Так откатывают последний коммит: репозиторий возвращается к состоянию родителя.
        private static void RollBackLastCommit(IConcurrentRepository repository, IApiRepositoryVcsController vcs)
        {
            int commitsBefore;
            CommitId lastCommitId;

            using (var vcsRead = repository.VCSReadConnection())
            {
                var commits = vcsRead.GetCommits();
                commitsBefore = commits.Count;
                lastCommitId = vcsRead.GetCurrentCommitId();
            }

            SampleCheck.That(vcs.Strip(lastCommitId, out var output), $"откат коммита должен пройти: {output}");

            using var afterStrip = repository.VCSReadConnection();
            var commitsAfter = afterStrip.GetCommits().Count;

            Console.WriteLine($"Коммитов было {commitsBefore}, после отката осталось {commitsAfter}");
            SampleCheck.That(commitsAfter == commitsBefore - 1, "откат должен убрать ровно один коммит");
        }

        private static void ShowCommitHistory(IConcurrentRepository repository)
        {
            using var vcsRead = repository.VCSReadConnection();

            Console.WriteLine($"История ({vcsRead.GetCommits().Count} коммита):");
            foreach (var commit in vcsRead.GetCommits())
            {
                Console.WriteLine($"  {commit.Message}");
            }
        }

        private static void AddProduct(IConcurrentRepository repository, string productName)
        {
            repository.TryExecuteTransaction(
                writeAccess => writeAccess.NewNamedProduct(productName, ProductKind.Designed),
                new TransactionInfo(TransactionDescription.AddingProduct, TransactionMode.Default));
        }
    }
}
