using System.Globalization;
using NodePipeline.Abstractions;
using NodePipeline.Abstractions.Exceptions.Validation;
using NodePipeline.Abstractions.Interfaces;
using NodePipeline.Abstractions.Interfaces.Nodes;
using NodePipeline.Abstractions.Interfaces.Validation;
using NodePipeline.Abstractions.Models.Validation;
using NodePipeline.Abstractions.Validators;
using NodePipeline.Configuration.Abstractions.Models.Execute;
using NodePipeline.Engine.Abstractions;
using NodePipeline.Engine.NodeFields;
using NodePipeline.Engine.Utils;
using NodePipeline.Engine.Abstractions.Validation;

// ReSharper disable CheckNamespace
// ReSharper disable RedundantNameQualifier
// ReSharper disable UnusedParameter.Local
#nullable enable

namespace NodePipeline.Engine.Generated;

public sealed class GeneratedNodeValidator : IPipelineNodeValidator
{
    private Dictionary<NodePortKey, NodePortInfo>? _allPorts;
    public Dictionary<Type, INodeValidator> NodeValidators { get; set; } = [];
    public Dictionary<Type, INodeFieldValidator> NodeFieldValidators { get; set; } = [];
    public IPipelineLocalizationProvider PipelineLocalizationProvider { get; set; } = new PipelineLocalizationProvider();

    public NodeValidationResult ValidateNode(string pipelineId, INodeFactory nodeFactory, NodeConfig nodeConfig, List<NodeConfig> allNodes, Dictionary<string, object?> parameters, CultureInfo cultureInfo)
    {
        var node = nodeFactory.CreateNode(pipelineId, nodeConfig);
        nodeFactory.SetNodeParametersValues(pipelineId, node, nodeConfig.Id, parameters);
        var nodeConnections = allNodes.SelectMany(q => q.Inputs
                .Select(x => new KeyValuePair<string, InputSource>(GetNodeInputKey(q.Id, x.Key), x.Value))
            ).ToDictionary(q => q.Key, q => q.Value);
        var ports = GetAllPorts(pipelineId, nodeFactory, allNodes);
        switch (node)
        {
            default:
                throw new UnknownNodeTypeException(nodeConfig.Type, nodeConfig.Id);
        }
    }


    private IReadOnlyDictionary<NodePortKey, NodePortInfo> GetAllPorts(string pipelineId, INodeFactory nodeFactory, List<NodeConfig> allNodes)
    {
        var nodeTypes = allNodes.Select(q => q.Type)
            .Distinct()
            .ToHashSet();

        var nodeList = allNodes
            .GroupBy(q => q.Type,
                (type, g) => new { Type = type, NodeIds = g.Select(q => q.Id) })
            .ToDictionary(q => q.Type, q => q.NodeIds);
        _allPorts ??= nodeFactory.GetAllPorts(pipelineId, nodeTypes, nodeList);
        return _allPorts.AsReadOnly();
    }

    private INodeFieldValidator<T> GetNodeFieldValidator<T>(Type validatorType)
    {
        if (!NodeFieldValidators.TryGetValue(validatorType, out var validator)
            || validator is not INodeFieldValidator<T> genericValidator)
            throw new UserDefinedNodeFieldValidatorNotFoundException(validatorType.FullName ?? validatorType.Name);
        return genericValidator;
    }

    private ValidationResult ValidateNodePorts(string pipelineId, INodeFactory nodeFactory, string nodeType, string nodeId,
        IReadOnlyDictionary<string, InputSource> nodePortConnections, IReadOnlyDictionary<NodePortKey, NodePortInfo> ports,
        out Dictionary<string, List<NodeFieldValidationResult>> inputPortValidationResults, CultureInfo cultureInfo)
    {
        var result = ValidationResult.Valid;
        inputPortValidationResults = new Dictionary<string, List<NodeFieldValidationResult>>();

        var inputPorts = nodeFactory.GetNodeInputPorts(pipelineId, nodeType, nodeId);
        foreach (var inputPort in inputPorts)
        {
            var code = NodeFieldCodeGenerator.GenerateFieldCode(nodeId, inputPort.Name,
                inputPort.Input ? FieldDirection.Input : FieldDirection.Output);
            inputPortValidationResults[code] = [];
            var hasConnection = nodePortConnections.TryGetValue(GetNodeInputKey(nodeId, inputPort.Name), out var connection);
            if (inputPort.Required && !hasConnection)
            {
                inputPortValidationResults[code].Add(new NodeFieldValidationResult(ValidationResult.HasErrors,
                    PipelineLocalizationProvider.GetLocalizedString(Constants.ValidationResults.ErrorMessages.RequiredInputPortHasNoConnectionError, cultureInfo, inputPort.Name, nodeId)));
                UpdateValidationResult(ValidationResult.HasErrors, ref result);
                continue;
            }
            if (!hasConnection || connection == null) continue;

            var portValidationResult = ValidateInputPort(nodeId, inputPort, connection, ports, cultureInfo);
            inputPortValidationResults[code].Add(portValidationResult);
            UpdateValidationResult(portValidationResult.Result, ref result);
        }

        return result;
    }

    private NodeFieldValidationResult ValidateInputPort(string nodeId, NodePortInfo inputPort, InputSource connection,
        IReadOnlyDictionary<NodePortKey, NodePortInfo> ports, CultureInfo cultureInfo)
    {
        if (!ports.TryGetValue(new NodePortKey(connection.NodeId, connection.OutputName, false), out var outputPort))
        {
            return new NodeFieldValidationResult(ValidationResult.HasErrors, PipelineLocalizationProvider
                .GetLocalizedString(Constants.ValidationResults.ErrorMessages.OutputPortNotFoundError, cultureInfo, connection.OutputName,
                    connection.NodeId, inputPort.Name, nodeId));
        }

        if (inputPort.Type != outputPort.Type)
        {
            return new NodeFieldValidationResult(ValidationResult.HasErrors, PipelineLocalizationProvider
                .GetLocalizedString(Constants.ValidationResults.ErrorMessages.PortsTypeMismatchError, cultureInfo,
                    inputPort.Name, nodeId, inputPort.Type, connection.OutputName, connection.NodeId, outputPort.Type));
        }

        if (inputPort.IsNullable == outputPort.IsNullable)
        {
            return SuccessNodeFieldValidationResult();
        }

        if (!inputPort.IsNullable && outputPort.IsNullable)
        {
            return new NodeFieldValidationResult(
                ValidationResult.HasWarnings, PipelineLocalizationProvider
                    .GetLocalizedString(Constants.ValidationResults.ErrorMessages.NullableAndNonNullablePortsConnectionWarning, cultureInfo,
                        inputPort.Name, nodeId, inputPort.Type, connection.OutputName, connection.NodeId, outputPort.Type));
        }

        return SuccessNodeFieldValidationResult();
    }

    private static string GetNodeInputKey(string nodeId, string inputPortName)
    {
        return string.Concat(nodeId, "_", inputPortName);
    }

    private INodeValidator GetNodeValidator(Type validatorType)
    {
        if (!NodeValidators.TryGetValue(validatorType, out var validator))
            throw new UserDefinedNodeValidatorNotFoundException(validatorType.FullName ?? validatorType.Name);
        return validator;
    }

    private NodeFieldValidationResult CheckRequiredNodeParameter<T>(string nodeId, NodeField<T> nodeField, string nodeParameterName, CultureInfo cultureInfo)
    {
        return nodeField.Value is not null
            ? new NodeFieldValidationResult(ValidationResult.Valid, null)
            : new NodeFieldValidationResult(ValidationResult.HasErrors, PipelineLocalizationProvider
                .GetLocalizedString(Constants.ValidationResults.ErrorMessages.RequiredNodeParameterValidationError, cultureInfo, nodeParameterName, nodeId));
    }

    private static NodeValidationResult SuccessNodeValidationResult(bool hasWarnings, Dictionary<string, List<NodeFieldValidationResult>> fieldResults, Dictionary<string, List<NodeFieldValidationResult>> portResults) =>
        new(hasWarnings ? ValidationResult.HasWarnings : ValidationResult.Valid, string.Empty,
            fieldResults.AsReadOnly(), portResults.AsReadOnly());

    private NodeValidationResult GetErrorValidationResult(ValidationResult validationResult, string nodeId, Dictionary<string, List<NodeFieldValidationResult>> fieldResults, Dictionary<string, List<NodeFieldValidationResult>> portResults, CultureInfo cultureInfo) =>
        new(validationResult, PipelineLocalizationProvider.GetLocalizedString(Constants.ValidationResults.ErrorMessages.NodeValidationError, cultureInfo, nodeId), fieldResults.AsReadOnly(), portResults.AsReadOnly());

    private static NodeFieldValidationResult SuccessNodeFieldValidationResult() =>
        new NodeFieldValidationResult(ValidationResult.Valid, null);

    private static void UpdateValidationResult(ValidationResult validationResult, ref ValidationResult parametersValidationResult)
    {
        if (validationResult == parametersValidationResult || validationResult == ValidationResult.Valid) return;

        parametersValidationResult = validationResult switch
        {
            ValidationResult.HasWarnings when parametersValidationResult == ValidationResult.Valid => ValidationResult.HasWarnings,
            ValidationResult.HasErrors when parametersValidationResult != ValidationResult.HasErrors => ValidationResult.HasErrors,
            _ => parametersValidationResult
        };
    }

}
#nullable restore
