using System.ComponentModel;
using Evos.Api.CollaborationRepository.Exporting;
using Evos.Api.Model;
using Evos.Api.Model.Serialization;
using Evos.Api.Repository;
using Evos.Api.Samples.Infrastructure;
using Evos.Core.Container.ProjectScope;
using Evos.ModelsRepository;
using Evos.ModelsRepository.BasicData;
using Evos.ModelsRepository.Connection;
using Evos.ModelsRepository.DataProvider.TransactionDescriptions;
using Evos.ModelsRepository.Products;
using Evos.ModelsRepository.SpaceModel;

namespace Evos.Api.Samples
{
    /// Публикация обменной модели в локальный репозиторий Evos — то место, где сходятся оба API.
    ///
    /// Репозиторий открывает низкоуровневое API (ApiConcurrentRepositoryFactory), и этот же
    /// IConcurrentRepository высокоуровневое принимает как есть: экспортёру нужен именно он.
    /// Никакого «репозитория обменной модели» не существует — модель публикуется в обычный
    /// репозиторий Evos, открытый обычной фабрикой.
    [Localizable(false)]
    public static class PublishToRepositorySample
    {
        public static void Run()
        {
            var directory = Path.Combine(Path.GetTempPath(), Path.GetRandomFileName());

            try
            {
                var location = new ProjectLocation(directory, Path.GetRandomFileName());

                // Шаг 1. Открываем репозиторий низкоуровневым API — как в RepositoryLifecycleSample.
                using var repository = new ApiConcurrentRepositoryFactory().OpenNewRepository(location);

                // Шаг 2. Наполняем его низкоуровневым API: базовый продукт «Компонент» классика
                // заводит в каждом проекте вентиляции, поэтому он есть и здесь — и попадает в подсчёты.
                repository.TryExecuteTransaction(
                    writeAccess => writeAccess.NewNamedProduct("Компонент", ProductKind.Designed),
                    new TransactionInfo(TransactionDescription.AddingProduct, TransactionMode.Default));

                // Шаг 3. В тот же самый репозиторий публикуем обменную модель высокоуровневым API.
                PublishExchangeModel(repository);
            }
            finally
            {
                Directory.Delete(directory, true);
            }
        }

        private static void PublishExchangeModel(IConcurrentRepository repository)
        {
            var model = BuildExchangeModelSample.CreateModelWithInstances(Guid.NewGuid());
            Export(repository, model);

            ShowProducts("после первого экспорта", ReadProductNames(repository));

            // Экспорт идемпотентен по ключевым свойствам: та же модель не создаёт дублей,
            // а обновляет уже существующие продукты.
            Export(repository, model);

            var productsAfterSecondExport = ReadProductNames(repository);
            ShowProducts("после повторного экспорта", productsAfterSecondExport);

            SampleCheck.That(productsAfterSecondExport.Count == 3, "повторный экспорт не должен плодить дубли продуктов");
            CheckAssemblyHasThreeParts(repository, "у сборки должно быть три экземпляра детали");

            ExportSerializedModel(repository, model);
        }

        /// Модель, пришедшая файлом или потоком, публикуется без промежуточной десериализации:
        /// ExportFromFile и ExportFromStream делают это сами. Отдаём ту же модель, что уже уехала
        /// в репозиторий выше: ключевые свойства у неё те же, поэтому дублей она не создаёт —
        /// ни продуктов, ни экземпляров.
        private static void ExportSerializedModel(IConcurrentRepository repository, IReadOnlyExchangeModel model)
        {
            var exporter = ExchangeModelToEvosExporterFactory.Create();

            using (var stream = new MemoryStream())
            {
                ExchangeModelSerializerFactory.CreateSerializer().Serialize(model, stream);

                // Сериализатор оставляет поток в конце — перед чтением его перематывают на начало.
                stream.Position = 0;

                _ = exporter.ExportFromStream(repository, stream).ValueOrThrow();
            }

            var filePath = Path.Combine(Path.GetTempPath(), $"{Guid.NewGuid():N}.evosexchange");
            try
            {
                ExchangeModelSerializerFactory.CreateSerializer().Serialize(model, filePath);

                _ = exporter.ExportFromFile(repository, filePath).ValueOrThrow();
            }
            finally
            {
                File.Delete(filePath);
            }

            var products = ReadProductNames(repository);
            ShowProducts("после экспорта из потока и файла", products);

            SampleCheck.That(products.Count == 3, "сериализованная модель — та же модель: дублей продуктов быть не должно");
            CheckAssemblyHasThreeParts(repository, "и дублей экземпляров тоже");
        }

        private static List<string> ReadProductNames(IConcurrentRepository repository)
        {
            using var connection = repository.ReadConnection();

            return connection.GetProducts()
                .Select(ReadProductName)
                .ToList();
        }

        private static void CheckAssemblyHasThreeParts(IConcurrentRepository repository, string message)
        {
            using var connection = repository.ReadConnection();

            var assembly = FindByName(connection, "Сборка");
            SampleCheck.That(assembly.NestedInstances.Count() == 3, message);
        }

        private static void ShowProducts(string stage, IReadOnlyCollection<string> productNames)
        {
            Console.WriteLine($"В репозитории продуктов {stage}: {productNames.Count}");

            foreach (var productName in productNames)
            {
                Console.WriteLine($"  {productName}");
            }
        }

        /// Экспорт идемпотентен по ключевым свойствам: повторный экспорт той же модели
        /// не создаёт дублей, а обновляет уже существующие продукты.
        /// Модель из памяти уходит напрямую — сериализация нужна только для обмена файлом или потоком.
        private static void Export(IConcurrentRepository repository, IReadOnlyExchangeModel model)
        {
            var exporter = ExchangeModelToEvosExporterFactory.Create();
            _ = exporter.Export(repository, model).ValueOrThrow();
        }

        private static IReadOnlyProduct FindByName(IRepositoryReadAccess connection, string name)
        {
            var product = connection.GetProducts()
                .FirstOrDefault(candidate => ReadProductName(candidate) == name);

            SampleCheck.That(product is not null, $"продукт '{name}' должен существовать в репозитории");
            return product!;
        }

        private static string ReadProductName(IReadOnlyProduct product)
        {
            return product.GetPredefinedFamilyOrThrow<IProductName>().Facade().Name;
        }
    }
}
