//------------------------------------------------------------------------------
//  此代码版权声明为全文件覆盖，如有原作者特别声明，会在下方手动补充
//  此代码版权（除特别声明外的代码）归作者本人Diego所有
//  源代码使用协议遵循本仓库的开源协议及附加协议
//  Gitee源代码仓库：https://gitee.com/diego2098/ThingsGateway
//  Github源代码仓库：https://github.com/kimdiego2098/ThingsGateway
//  使用文档：https://thingsgateway.cn/
//  QQ群：605534569
//------------------------------------------------------------------------------

using Newtonsoft.Json.Linq;

using Opc.Ua;
using Opc.Ua.Client;

using System.Collections.Concurrent;

using ThingsGateway.Foundation.Extension.Generic;
using ThingsGateway.Foundation.OpcUa;
using ThingsGateway.Gateway.Application;
using ThingsGateway.NewLife.Json.Extension;
using ThingsGateway.NewLife.Threading;

using TouchSocket.Core;

namespace ThingsGateway.Plugin.OpcUa;

/// <summary>
/// <inheritdoc/>
/// </summary>
public class OpcUaMaster : CollectBase
{
    private readonly OpcUaMasterProperty _driverProperties = new();

    private ThingsGateway.Foundation.OpcUa.OpcUaMaster _plc;

    private volatile bool connectFirstFailLoged;
    private volatile bool success = true;

    /// <inheritdoc/>
    public override CollectPropertyBase CollectProperties => _driverProperties;

    /// <inheritdoc/>
    public override Type DriverDebugUIType => typeof(ThingsGateway.Debug.OpcUaMaster);

    public override Type DriverUIType => typeof(OpcUaMasterRuntimeRazor);

    protected override async Task InitChannelAsync(IChannel? channel, CancellationToken cancellationToken)
    {


        //载入配置
        OpcUaProperty config = new()
        {
            OpcUrl = _driverProperties.OpcUrl,
            UpdateRate = _driverProperties.UpdateRate,
            DeadBand = _driverProperties.DeadBand,
            GroupSize = _driverProperties.GroupSize,
            KeepAliveInterval = _driverProperties.KeepAliveInterval,
            UseSecurity = _driverProperties.UseSecurity,
            ActiveSubscribe = _driverProperties.ActiveSubscribe,
            UserName = _driverProperties.UserName,
            Password = _driverProperties.Password,
            CheckDomain = _driverProperties.CheckDomain,
            LoadType = _driverProperties.LoadType,
            AutoAcceptUntrustedCertificates = _driverProperties.AutoAcceptUntrustedCertificates,
        };

        var plc = _plc;
        _plc = new();
        if (plc != null)
        {
            plc.DataChangedHandler -= DataChangedHandler;
            plc.LogEvent -= _plc_LogEvent;
            plc.SafeDispose();
        }

        _plc.LogEvent += _plc_LogEvent;
        _plc.DataChangedHandler += DataChangedHandler;
        _plc.OpcUaProperty = config;
        await base.InitChannelAsync(channel, cancellationToken).ConfigureAwait(false);
    }

    /// <inheritdoc/>
    public override bool IsConnected() => _plc?.Connected == true;

    public override string ToString()
    {
        return $"{_driverProperties.OpcUrl}";
    }

    /// <inheritdoc/>
    protected override void Dispose(bool disposing)
    {
        if (_plc != null)
        {
            _plc.DataChangedHandler -= DataChangedHandler;
            _plc.LogEvent -= _plc_LogEvent;

            _plc.Disconnect();
            _plc.SafeDispose();
        }

        VariableAddresDicts?.Clear();
        base.Dispose(disposing);
    }

    public override string GetAddressDescription()
    {
        return _plc?.GetAddressDescription();
    }
    protected override bool VariableSourceReadsEnable => !_driverProperties.ActiveSubscribe;
    protected override List<IScheduledTask> ProtectedGetTasks(CancellationToken cancellationToken)
    {
        var list = base.ProtectedGetTasks(cancellationToken);

        var check = ScheduledTaskHelper.GetTask("3000", CheckAsync, null, LogMessage, cancellationToken);
        list.Add(check);
        var checkConnec = ScheduledTaskHelper.GetTask("10000", CheckConnectAsync, null, LogMessage, cancellationToken);
        list.Add(checkConnec);
        return list;
    }

    protected override async Task ProtectedStartAsync(CancellationToken cancellationToken)
    {
        await CheckConnectAsync(null, cancellationToken).ConfigureAwait(false);
        await base.ProtectedStartAsync(cancellationToken).ConfigureAwait(false);
    }

    private async Task CheckConnectAsync(object? state, CancellationToken cancellationToken)
    {
        if (_plc.Session == null)
        {
            try
            {
                if (_plc.Session == null)
                    await _plc.ConnectAsync(cancellationToken).ConfigureAwait(false);
            }
            catch (Exception ex)
            {
                if (!connectFirstFailLoged)
                    LogMessage?.LogWarning(ex, "Connect Fail");

                connectFirstFailLoged = true;
                CurrentDevice.SetDeviceStatus(TimerX.Now, null, ex.Message);
            }
        }
    }

    private volatile bool checkLog;
    private async Task CheckAsync(object? state, CancellationToken cancellationToken)
    {
        if (_plc.Session != null)
        {
            if (_driverProperties.ActiveSubscribe)
            {

                //获取设备连接状态
                if (IsConnected())
                {

                    //更新设备活动时间
                    {

                        //如果是订阅模式，连接时添加订阅组
                        if (_plc.OpcUaProperty?.ActiveSubscribe == true && CurrentDevice.VariableSourceReads.Count > 0 && _plc.Session.SubscriptionCount < CurrentDevice.VariableSourceReads.Count)
                        {


                            foreach (var variableSourceRead in CurrentDevice.VariableSourceReads)
                            {
                                try
                                {
                                    if (_plc.Session.Subscriptions.FirstOrDefault(a => a.DisplayName == variableSourceRead.RegisterAddress) == null)
                                    {
                                        await _plc.AddSubscriptionAsync(variableSourceRead.RegisterAddress, variableSourceRead.VariableRuntimes.Where(a => !a.RegisterAddress.IsNullOrEmpty()).Select(a => a.RegisterAddress!).ToHashSet().ToArray(), _plc.OpcUaProperty.LoadType, cancellationToken).ConfigureAwait(false);

                                        LogMessage?.LogInformation($"AddSubscription index  {CurrentDevice.VariableSourceReads.IndexOf(variableSourceRead)}  done");

                                    }

                                    await Task.Delay(100, cancellationToken).ConfigureAwait(false); // allow for subscription to be finished on server?

                                    checkLog = true;
                                }
                                catch (Exception ex)
                                {
                                    if (!checkLog)
                                        LogMessage?.LogWarning(ex, "AddSubscriptions error");
                                    checkLog = false;
                                }
                                finally
                                {
                                }

                                LogMessage?.LogInformation("AddSubscriptions done");

                            }

                        }
                    }
                }

            }

        }
    }

    /// <inheritdoc/>
    protected override Task<List<VariableSourceRead>> ProtectedLoadSourceReadAsync(List<VariableRuntime> deviceVariables)
    {
        if (deviceVariables.Count > 0)
        {
            List<VariableSourceRead> variableSourceReads = new List<VariableSourceRead>();
            foreach (var deviceVariableGroups in deviceVariables.GroupBy(a => a.CollectGroup))
            {
                var dataLists = deviceVariableGroups.ChunkBetter(_driverProperties.GroupSize);

                foreach (var variable in dataLists)
                {
                    var sourVars = new VariableSourceRead()
                    {
                        IntervalTime = _driverProperties.UpdateRate.ToString(),
                        RegisterAddress = Guid.NewGuid().ToString(),
                    };
                    foreach (var item in variable)
                    {
                        sourVars.AddVariable(item);
                    }
                    variableSourceReads.Add(sourVars);
                }

            }
            return Task.FromResult(variableSourceReads);
        }
        else
        {
            return Task.FromResult(new List<VariableSourceRead>());
        }

    }

    /// <inheritdoc/>
    protected override async ValueTask<OperResult<byte[]>> ReadSourceAsync(VariableSourceRead deviceVariableSourceRead, CancellationToken cancellationToken)
    {
        DateTime time = DateTime.Now;
        var addresss = deviceVariableSourceRead.VariableRuntimes.Where(a => !a.RegisterAddress.IsNullOrEmpty()).Select(a => a.RegisterAddress!).ToArray();
        try
        {

            var result = await _plc.ReadJTokenValueAsync(addresss, cancellationToken).ConfigureAwait(false);
            foreach (var data in result)
            {
                if (!cancellationToken.IsCancellationRequested)
                {
                    var data1 = deviceVariableSourceRead.VariableRuntimes.Where(a => a.RegisterAddress == data.Item1);

                    foreach (var item in data1)
                    {
                        object value = data.Item3.GetObjectFromJToken();

                        var isGood = StatusCode.IsGood(data.Item2.StatusCode);
                        if (_driverProperties.SourceTimestampEnable)
                        {
                            time = data.Item2.SourceTimestamp.ToLocalTime();
                        }
                        if (isGood)
                        {
                            item.SetValue(value, time);
                        }
                        else
                        {
                            if (item is VariableRuntime variable && (variable.IsOnline || variable.CollectTime == DateTime.UnixEpoch.ToLocalTime()))
                            {
                                LogMessage?.LogWarning($"OPC quality bad:{Environment.NewLine}{data.Item1}");
                            }
                            item.SetValue(null, time, false);
                            item.VariableSource.LastErrorMessage = data.Item2.StatusCode.ToString();
                        }
                    }
                    LogMessage?.Trace($"Change:{Environment.NewLine}{data.Item1} : {data.Item3}");
                }
            }

            return OperResult.CreateSuccessResult<byte[]>(null);
        }
        catch (Exception ex)
        {
            return new OperResult<byte[]>($"ReadSourceAsync {addresss.ToSystemTextJsonString()}：{Environment.NewLine}{ex}");
        }
        finally
        {
        }
    }

    /// <inheritdoc/>
    protected override async ValueTask<Dictionary<string, OperResult>> WriteValuesAsync(Dictionary<VariableRuntime, JToken> writeInfoLists, CancellationToken cancellationToken)
    {
        using var writeLock = ReadWriteLock.WriterLock();
        try
        {
            var result = await _plc.WriteNodeAsync(writeInfoLists.ToDictionary(a => a.Key.RegisterAddress!, a => a.Value), cancellationToken).ConfigureAwait(false);
            var results = new ConcurrentDictionary<string, OperResult>(result.ToDictionary<KeyValuePair<string, Tuple<bool, string>>, string, OperResult>(a =>
            {
                return writeInfoLists.Keys.FirstOrDefault(b => b.RegisterAddress == a.Key)?.Name!;
            }
            , a =>
            {
                if (!a.Value.Item1)
                    return new OperResult(a.Value.Item2);
                else
                    return OperResult.Success;
            }));

            await Check(writeInfoLists, results, cancellationToken).ConfigureAwait(false);

            return new(results);

        }
        finally
        {
        }
    }

    private void _plc_LogEvent(byte level, object sender, string message, Exception ex)
    {
        LogMessage?.Log((LogLevel)level, sender, message, ex);
    }

    public override async Task AfterVariablesChangedAsync(CancellationToken cancellationToken)
    {
        try
        {
            _plc?.Disconnect();
            await base.AfterVariablesChangedAsync(cancellationToken).ConfigureAwait(false);
        }
        finally
        {
            VariableAddresDicts = IdVariableRuntimes.Select(a => a.Value).Where(it => !it.RegisterAddress.IsNullOrEmpty()).GroupBy(a => a.RegisterAddress).ToDictionary(a => a.Key!, b => b.ToList());
            try
            {
                await _plc.ConnectAsync(cancellationToken).ConfigureAwait(false);
            }
            catch (Exception ex)
            {
                LogMessage?.LogWarning(ex);
            }
        }

        RefreshVariableTasks(cancellationToken);

    }

    private Dictionary<string, List<VariableRuntime>> VariableAddresDicts { get; set; } = new();

    private void DataChangedHandler((Opc.Ua.VariableNode variableNode, MonitoredItem monitoredItem, DataValue dataValue, JToken jToken) data)
    {
        DateTime time = DateTime.Now;
        try
        {
            if (CurrentDevice.Pause)
                return;
            if (DisposedValue)
                return;
            if (TaskSchedulerLoop?.Stoped == true) return;



            LogMessage?.Trace($"Change: {Environment.NewLine} {data.monitoredItem.StartNodeId} : {data.jToken?.ToString()}");

            //尝试固定点位的数据类型
            var type = TypeInfo.GetSystemType(TypeInfo.GetBuiltInType(data.variableNode.DataType, _plc.Session.SystemContext.TypeTable), data.variableNode.ValueRank);

            if (!VariableAddresDicts.TryGetValue(data.monitoredItem.StartNodeId.ToString(), out var itemReads)) return;

            object value = data.jToken.GetObjectFromJToken();

            var isGood = StatusCode.IsGood(data.dataValue.StatusCode);
            if (_driverProperties.SourceTimestampEnable)
            {
                time = data.dataValue.SourceTimestamp.ToLocalTime();
            }
            foreach (var item in itemReads)
            {
                if (CurrentDevice.Pause)
                    return;
                if (DisposedValue)
                    return;
                if (TaskSchedulerLoop?.Stoped == true) return;

                if (isGood)
                {
                    item.SetValue(value, time);
                }
                else
                {
                    if ((item.IsOnline || item.CollectTime == DateTime.UnixEpoch.ToLocalTime()))
                    {
                        LogMessage?.LogWarning($"OPC quality bad:{Environment.NewLine}{item.Name}");
                    }
                    item.SetValue(null, time, false);
                    item.VariableSource.LastErrorMessage = data.dataValue.StatusCode.ToString();
                }
            }
            success = true;
        }
        catch (Exception ex)
        {
            if (success)
                LogMessage?.LogWarning(ex);
            success = false;
        }
    }

}
