using System.ComponentModel;
using Evos.Api.CollaborationRepository.Exporting;
using Evos.Api.Common.Families;
using Evos.Api.Model;
using Evos.Api.Model.PredefinedFamilies;
using Evos.Api.Samples.Infrastructure;
using Evos.CommonValueObjects;
using Evos.ModelsRepository;
using Evos.ModelsRepository.DataProvider;
using Evos.ModelsRepository.Families;
using Evos.ModelsRepository.Products;
using JetBrains.Annotations;

namespace Evos.Api.Samples
{
    /// Высокоуровневое API: своё семейство свойств. Готовых семейств (EvosFamilies, CommonFamilies)
    /// хватает не всегда — свои описывают классом-наследником FamilyDefinitionBase и пользуются им
    /// точно так же, как готовыми: SetValue по типизированному описанию свойства.
    [Localizable(false)]
    public static class CustomFamilySample
    {
        public static void Run()
        {
            using var sampleRepository = SampleRepository.CreateWithBaseProduct();

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

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

            ReadPublishedPassport(sampleRepository.Repository);
        }

        /// Своё семейство ничем не отличается в применении от готового: то же SetValue,
        /// тот же контроль типов компилятором. Значение enum-свойства кладут как CustomEnumValue —
        /// строку из атрибута [CustomEnumValue] на члене перечисления.
        private static IReadOnlyExchangeModel CreateModel(Guid projectId)
        {
            var builder = ExchangeModelBuilderFactory.Create(projectId);

            var pump = builder.NewProduct();
            pump.KeyProperties.SetValue(CommonFamilies.КаталогПроизводитель.Производитель, "Grundfos");
            pump.KeyProperties.SetValue(CommonFamilies.КаталогПроизводитель.АртикулПроизводителя, "UPS-25");
            pump.Properties.SetValue(EvosFamilies.ProductName.Name, "Насос циркуляционный");

            pump.Properties.SetValue(SampleFamilies.ПаспортОборудования.НомерПаспорта, "П-2026-014");
            pump.Properties.SetValue(SampleFamilies.ПаспортОборудования.СрокСлужбыЛет, 15);
            pump.Properties.SetValue(SampleFamilies.ПаспортОборудования.Состояние, СостояниеОборудования.ВРаботе);

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

        /// Опубликованное своё семейство лежит в репозитории рядом с предопределёнными.
        /// Предопределённые читают типизированным фасадом (GetPredefinedFamilyOrThrow), а своё —
        /// по идентификаторам: семейство ищут по FamilyId, свойство внутри него — по Id свойства.
        private static void ReadPublishedPassport(IConcurrentRepository repository)
        {
            using var connection = repository.ReadConnection();

            var pump = connection.GetProducts()
                .Single(product => product.GetProductName() == "Насос циркуляционный");

            var familyFacadeId = new FamilyFacadeId(SampleFamilies.ПаспортОборудования.Id.ToGuid());

            SampleCheck.That(
                pump.Families.Any(family => family.FamilyDescription.FacadeId == familyFacadeId),
                "своё семейство должно опубликоваться в репозиторий");

            var passport = pump.Families.First(family => family.FamilyDescription.FacadeId == familyFacadeId);

            var number = ReadPropertyValue(passport, SampleFamilies.ПаспортОборудования.НомерПаспорта.Id);
            var state = ReadPropertyValue(passport, SampleFamilies.ПаспортОборудования.Состояние.Id);

            Console.WriteLine($"Паспорт насоса: № {number}, состояние — {state}");

            SampleCheck.That((string)number == "П-2026-014", "номер паспорта должен сохраниться");
            SampleCheck.That(
                state.Equals(new CustomEnumValue("В работе")),
                "enum-свойство едет в Evos строкой из атрибута [CustomEnumValue]");
        }

        private static object ReadPropertyValue(ReadOnlyProductFamily family, string propertyId)
        {
            return family.GetProperties()
                .First(property => property.FieldId.DataFieldId.Equals(new DataValueFieldId(propertyId)))
                .Evaluate()
                .Value;
        }
    }

    /// Реестр своих семейств — точный аналог EvosFamilies и CommonFamilies.
    /// Экземпляр один на приложение: описание семейства неизменяемо.
    [Localizable(false)]
    public static class SampleFamilies
    {
        public static readonly ПаспортОборудованияDefinition ПаспортОборудования = new();
    }

    /// Значения перечисления уезжают в Evos строками из атрибута [CustomEnumValue] —
    /// именно их увидит пользователь в свойствах объекта.
    [PublicAPI]
    public enum СостояниеОборудования
    {
        [CustomEnumValue("В работе")]
        ВРаботе,

        [CustomEnumValue("В ремонте")]
        ВРемонте,

        [CustomEnumValue("Выведено из эксплуатации")]
        Выведено
    }

    /// Своё семейство: класс-наследник FamilyDefinitionBase со стабильным GUID в [FamilyDefinitionId].
    /// GUID — это имя семейства для Evos: сгенерируете новый — получите новое семейство и потеряете
    /// связь с уже опубликованными данными. То же и с идентификаторами свойств.
    /// Конструктор обязан вызвать Initialize(): он собирает описание семейства по свойствам класса.
    [Localizable(false)]
    [FamilyDefinitionId("9C4E1B47-2E3A-4D18-9F27-3B8A6C0D5E11")]
    public sealed class ПаспортОборудованияDefinition : FamilyDefinitionBase
    {
        public ПаспортОборудованияDefinition()
        {
            Initialize();
        }

        protected override string FamilyName => "Паспорт оборудования";

        public IExchangePropertyDescription<string> НомерПаспорта { get; } = new ExchangePropertyDescription<string>(
            "1A7F0C63-9D24-4F5B-8E10-77C2A9B4E0D3",
            "Номер паспорта",
            "Паспорт оборудования",
            "Номер паспорта изделия у поставщика");

        public IExchangePropertyDescription<int> СрокСлужбыЛет { get; } = new ExchangePropertyDescription<int>(
            "5B2D8E41-6C93-42A7-B0F5-1E9D3A7C4F86",
            "Срок службы, лет",
            "Паспорт оборудования",
            "Назначенный срок службы");

        public IExchangePropertyEnumDescription<СостояниеОборудования> Состояние { get; } =
            new ExchangePropertyEnumDescription<СостояниеОборудования>(
                "D30F5A18-4B7E-4C62-9A81-2F6E0B5D9C74",
                "Состояние",
                "Паспорт оборудования",
                "Текущее состояние оборудования");
    }
}
