using System.ComponentModel;
using Evos.Api.Common.Families;
using Evos.Api.Model;
using Evos.Api.Model.Identifiers;
using Evos.Api.Model.Output;
using Evos.Api.Model.PredefinedFamilies;
using Evos.Api.Model.Validation;
using Evos.Api.Samples.Infrastructure;

namespace Evos.Api.Samples
{
    /// Высокоуровневое API: что делать, когда модель не собралась.
    /// Build() не бросает исключение на ошибке в данных — он возвращает OperationResult с отказом
    /// и диагностикой. ValueOrThrow() из остальных сэмплов годится, когда отказ не предусмотрен;
    /// здесь показано, как отказ разобрать.
    [Localizable(false)]
    public static class ValidationErrorsSample
    {
        public static void Run()
        {
            ForgottenInstanceId();
            ModelIdTogetherWithKeyProperties();
            ProductsWithoutKeyProperties();
        }

        /// Самая частая ошибка. Экземпляры одного продукта различаются именем и матрицей,
        /// а это обычные свойства — в тождество они не входят. Без собственного ключа
        /// (GraphicFamilies.ModelObjectId.ObjectId) экземпляры оказываются дублями друг друга.
        private static void ForgottenInstanceId()
        {
            var builder = ExchangeModelBuilderFactory.Create(Guid.NewGuid());

            var assembly = NewCatalogProduct(builder, "ASSEMBLY-1", "Сборка");
            var part = NewCatalogProduct(builder, "PART-1", "Деталь");

            for (var i = 0; i < 3; i++)
            {
                var instance = assembly.NewProductInstance(part);

                // Здесь должен был быть instance.KeyProperties.SetValue(GraphicFamilies.ModelObjectId.ObjectId, Guid.NewGuid())
                instance.Properties.SetValue(EvosFamilies.ProductName.Name, $"Деталь {i + 1}");
            }

            var result = builder.Build();

            // TryGetValue отдаёт успех и значение одним вызовом: удобно, когда отказ — штатная ветка.
            if (result.TryGetValue(out var model))
            {
                SampleCheck.That(false, $"модель не должна была собраться, а собралась: продуктов {model.Products.Count}");
                return;
            }

            ShowValidationErrors("забытый идентификатор экземпляра", result);
            SampleCheck.That(
                HasError(result, ExchangeModelValidationErrorKind.DuplicateInstance),
                "три безымянных для Evos экземпляра — это DuplicateInstance");
        }

        /// ModelId адресует конкретный продукт, уже лежащий в проекте, — тождество по ключевым
        /// свойствам для него не считается. Задать и то и другое — противоречие.
        private static void ModelIdTogetherWithKeyProperties()
        {
            var builder = ExchangeModelBuilderFactory.Create(Guid.NewGuid());

            var product = NewCatalogProduct(builder, "PUMP-1", "Насос");
            product.ModelId = new EvosModelId("идентификатор, полученный чтением проекта");

            var result = builder.Build();

            ShowValidationErrors("ModelId вместе с ключевыми свойствами", result);
            SampleCheck.That(
                HasError(result, ExchangeModelValidationErrorKind.ProductWithModelIdHasKeyProperties),
                "продукт с ModelId не должен иметь ключевых свойств");
        }

        /// Ловушка. Билдер сам кладёт каждому продукту ключевое свойство «ID проекта», поэтому
        /// два продукта, которым вы не задали ни одного ключевого свойства, различить нечем —
        /// и они оказываются дублями. Диагностика при этом показывает ключ, которого вы не ставили.
        private static void ProductsWithoutKeyProperties()
        {
            var builder = ExchangeModelBuilderFactory.Create(Guid.NewGuid());

            var first = builder.NewProduct();
            first.Properties.SetValue(EvosFamilies.ProductName.Name, "Первый");

            var second = builder.NewProduct();
            second.Properties.SetValue(EvosFamilies.ProductName.Name, "Второй");

            var result = builder.Build();

            ShowValidationErrors("продукты без ключевых свойств", result);
            SampleCheck.That(
                HasError(result, ExchangeModelValidationErrorKind.DuplicateProduct),
                "имя лежит в обычных свойствах и продукты не различает");
        }

        /// Диагностика приходит записями разных уровней; Errors отбирает только уровень Error.
        /// Разбирать её лучше не по тексту сообщения (в нём — дамп контейнеров, он для человека),
        /// а по полезной нагрузке: ValidationErrorOutputPayload несёт вид ошибки и те контейнеры
        /// модели, на которых она возникла, — по ним и находят виноватые продукты.
        private static void ShowValidationErrors(string scenario, OperationResult<IReadOnlyExchangeModel> result)
        {
            Console.WriteLine($"Сценарий «{scenario}»: успех — {result.Succeeded}, состояние — {result.ResultState}");

            foreach (var error in result.Errors)
            {
                if (error.Payload is not ValidationErrorOutputPayload payload)
                {
                    Console.WriteLine($"  не ошибка валидации: {error.Message}");
                    continue;
                }

                Console.WriteLine(
                    $"  {payload.Error.Kind}: задето контейнеров — {payload.Error.RelatedContainers.Count}");
            }
        }

        private static bool HasError(OperationResult<IReadOnlyExchangeModel> result, ExchangeModelValidationErrorKind kind)
        {
            return result.Errors.Any(
                entry => entry.Payload is ValidationErrorOutputPayload payload && payload.Error.Kind == kind);
        }

        private static IExchangeProduct NewCatalogProduct(IExchangeModelBuilder builder, string article, string name)
        {
            var product = builder.NewProduct();

            product.KeyProperties.SetValue(CommonFamilies.КаталогПроизводитель.Производитель, "Evos");
            product.KeyProperties.SetValue(CommonFamilies.КаталогПроизводитель.АртикулПроизводителя, article);
            product.Properties.SetValue(EvosFamilies.ProductName.Name, name);

            return product;
        }
    }
}
