using System.ComponentModel;
using Evos.Api.CollaborationRepository.Exporting;
using Evos.Api.Common.Families;
using Evos.Api.Model;
using Evos.Api.Model.Graphic;
using Evos.Api.Model.PredefinedFamilies;
using Evos.Api.Samples.Infrastructure;
using Evos.CommonValueObjects;
using Evos.ModelsRepository;
using Evos.ModelsRepository.BasicData;
using Evos.ModelsRepository.DataProvider;
using Evos.ModelsRepository.DataProvider.TransactionDescriptions;
using Evos.ModelsRepository.Products;

namespace Evos.Api.Samples
{
    /// Высокоуровневое API: как оборудование попадает в систему, откуда в проекте берутся уровни
    /// и куда продукт ложится в дереве менеджера проекта.
    ///
    /// Привязка экземпляра к уровню (instance.Level) здесь не показана намеренно: ей нужен
    /// EvosInstanceRef уже существующего в проекте уровня, а такие идентификаторы выдаёт только
    /// чтение проекта с Collaboration-сервера (GetLevels). См. CollaborationSample.
    [Localizable(false)]
    public static class SystemsAndLevelsSample
    {
        private const string SectionName = "Секция А";
        private const string DuctName = "Воздуховод П1";

        public static void Run()
        {
            using var sampleRepository = SampleRepository.CreateWithBaseProduct();

            var model = CreateModel(Guid.NewGuid());

            _ = ExchangeModelToEvosExporterFactory.Create()
                .Export(sampleRepository.Repository, model)
                .ValueOrThrow();

            CheckDuctBelongsToSystem(sampleRepository.Repository);
        }

        private static IReadOnlyExchangeModel CreateModel(Guid projectId)
        {
            var builder = ExchangeModelBuilderFactory.Create(projectId);

            var system = builder.NewSystem("П1 Приточная", Color.FromSRgb(0, 200, 0, 255, false));

            var level = CreateLevelProduct(builder);
            var duct = CreateDuct(builder);

            // Секция — контейнер: уровень и оборудование попадают в проект экземплярами внутри неё.
            var section = builder.NewProduct();
            section.KeyProperties.SetValue(CommonFamilies.КаталогПроизводитель.Производитель, "Evos");
            section.KeyProperties.SetValue(CommonFamilies.КаталогПроизводитель.АртикулПроизводителя, "SECTION-A");
            section.Properties.SetValue(EvosFamilies.ProductName.Name, SectionName);

            var levelInstance = section.NewProductInstance(level);
            levelInstance.KeyProperties.SetValue(GraphicFamilies.ModelObjectId.ObjectId, Guid.NewGuid());
            levelInstance.Properties.SetValue(EvosFamilies.ProductName.Name, "Этаж 1");

            var ductInstance = section.NewProductInstance(duct);
            ductInstance.KeyProperties.SetValue(GraphicFamilies.ModelObjectId.ObjectId, Guid.NewGuid());
            ductInstance.Properties.SetValue(EvosFamilies.ProductName.Name, DuctName);

            // Система — свойство вхождения, а не типа: в системе состоит конкретный воздуховод
            // в конкретном месте проекта, а не «воздуховод вообще».
            ductInstance.System = system;

            return builder.Build().ValueOrThrow();
        }

        /// Уровень — не число и не строка, а продукт, которому задали свойства семейства Level.
        /// Уровнем он становится в тот момент, когда его экземпляр попадает внутрь другого продукта:
        /// именно экземпляр и есть уровень проекта, на который потом ссылаются через EvosInstanceRef.
        private static IExchangeProduct CreateLevelProduct(IExchangeModelBuilder builder)
        {
            var level = builder.NewProduct();

            level.KeyProperties.SetValue(CommonFamilies.КаталогПроизводитель.Производитель, "Evos");
            level.KeyProperties.SetValue(CommonFamilies.КаталогПроизводитель.АртикулПроизводителя, "LEVEL-1");
            level.Properties.SetValue(EvosFamilies.ProductName.Name, "Уровень");

            level.Properties.SetValue(EvosFamilies.Level.Label, "1");
            level.Properties.SetValue(EvosFamilies.Level.StoreyName, "Первый этаж");

            return level;
        }

        private static IExchangeProduct CreateDuct(IExchangeModelBuilder builder)
        {
            var duct = builder.NewProduct();

            duct.KeyProperties.SetValue(CommonFamilies.КаталогПроизводитель.Производитель, "Evos");
            duct.KeyProperties.SetValue(CommonFamilies.КаталогПроизводитель.АртикулПроизводителя, "DUCT-100");
            duct.Properties.SetValue(EvosFamilies.ProductName.Name, "Воздуховод 100×100");

            // Categorization решает, куда продукт ляжет в дереве менеджера проекта.
            // Разделитель вложенности — обратный слэш; вид компонента задаётся отдельно.
            duct.Properties.SetValue(EvosFamilies.Categorization.Catalog, @"Каталожные\Вентиляция\Воздуховоды");
            duct.Properties.SetValue(EvosFamilies.Categorization.ProductKind, ProductKind.Cataloged);

            return duct;
        }

        /// Опубликованная система лежит на экземпляре присоединяемым семейством ICurrentSystemSelection —
        /// в него экспортёр кладёт идентификатор продукта-системы.
        ///
        /// Читаем внутри транзакции, хотя ничего не меняем: присоединяемые семейства доступны только
        /// с write-грани (GetOrAttachTransientFamily живёт на IProductInstance), а read-соединение
        /// отдаёт IReadOnlyProductInstance. Так же поступает и штатное чтение структуры проекта.
        private static void CheckDuctBelongsToSystem(IConcurrentRepository repository)
        {
            var executed = repository.TryExecuteTransaction(
                writeAccess =>
                {
                    var section = writeAccess.GetProducts().Single(product => product.GetProductName() == SectionName);
                    var system = writeAccess.GetProducts().Single(product => product.GetProductName() == "П1 Приточная");

                    Console.WriteLine($"В «{SectionName}» вложено экземпляров: {section.NestedInstances.Count()}");

                    var ductInstance = section.NestedInstances
                        .Single(instance => instance.GetPredefinedFamilyOrThrow<IProductName>().Facade().Name == DuctName);

                    var systemId = ductInstance.GetOrAttachTransientFamily<ICurrentSystemSelection>().Facade().SystemId;

                    Console.WriteLine($"Воздуховод привязан к системе: {!systemId.Equals(RepositoryObjectId.Empty)}");

                    SampleCheck.That(
                        systemId.Equals(system.Id.RepositoryObjectId),
                        "экземпляр воздуховода должен ссылаться на продукт-систему");
                },
                new TransactionInfo(TransactionDescription.Default, TransactionMode.Default));

            SampleCheck.That(executed, "транзакция чтения должна выполниться");
        }
    }
}
