using MainShell.DeviceMaintance.Model;
using System;
using System.Collections.Generic;
using System.Linq;
using System.Threading;
using System.Threading.Tasks;

namespace MainShell.Hardware
{
    public interface IDeviceCylinderService
    {
        IReadOnlyList<CylinderDefinition> Definitions { get; }
        bool TryGetDefinition(string name, out CylinderDefinition definition);
        bool TryExtend(string name);
        bool TryExtend(string name, out string failureReason);
        bool TryRetract(string name);
        bool TryRetract(string name, out string failureReason);
        Task<bool> TryExtendAndWaitAsync(string name, TimeSpan timeout, CancellationToken cancellationToken = default(CancellationToken));
        Task<bool> TryRetractAndWaitAsync(string name, TimeSpan timeout, CancellationToken cancellationToken = default(CancellationToken));
        Task<CylinderExecutionResult> TryExtendAndWaitResultAsync(string name, TimeSpan timeout, CancellationToken cancellationToken = default(CancellationToken));
        Task<CylinderExecutionResult> TryRetractAndWaitResultAsync(string name, TimeSpan timeout, CancellationToken cancellationToken = default(CancellationToken));
    }

    public class DeviceCylinderService : IDeviceCylinderService
    {
        private readonly IDeviceIoMonitorService _ioMonitorService;
        private readonly Dictionary<string, CylinderDefinition> _definitionsByName;
        private static readonly StringComparer PointReferenceComparer = StringComparer.OrdinalIgnoreCase;
        private static readonly TimeSpan ActiveRefreshTimeout = TimeSpan.FromMilliseconds(120);
        private static readonly TimeSpan FeedbackRetryInterval = TimeSpan.FromMilliseconds(50);

        public IReadOnlyList<CylinderDefinition> Definitions { get; }

        public DeviceCylinderService(IDeviceIoMonitorService ioMonitorService)
        {
            _ioMonitorService = ioMonitorService;
            Definitions = CylinderDefinitionLoader.LoadDefinitions();
            _definitionsByName = Definitions.ToDictionary(x => x.Name, x => x, StringComparer.OrdinalIgnoreCase);
        }

        public bool TryGetDefinition(string name, out CylinderDefinition definition)
        {
            return _definitionsByName.TryGetValue(name, out definition);
        }

        public bool TryExtend(string name)
        {
            string failureReason;
            return TryExtend(name, out failureReason);
        }

        public bool TryExtend(string name, out string failureReason)
        {
            CylinderDefinition definition;
            if (!TryGetDefinition(name, out definition))
            {
                failureReason = "未找到对应气缸定义。";
                return false;
            }

            return Execute(definition, true, out failureReason);
        }

        public bool TryRetract(string name)
        {
            string failureReason;
            return TryRetract(name, out failureReason);
        }

        public bool TryRetract(string name, out string failureReason)
        {
            CylinderDefinition definition;
            if (!TryGetDefinition(name, out definition))
            {
                failureReason = "未找到对应气缸定义。";
                return false;
            }

            return Execute(definition, false, out failureReason);
        }

        public async Task<bool> TryExtendAndWaitAsync(string name, TimeSpan timeout, CancellationToken cancellationToken = default(CancellationToken))
        {
            var result = await TryExtendAndWaitResultAsync(name, timeout, cancellationToken);
            return result.Success;
        }

        public async Task<bool> TryRetractAndWaitAsync(string name, TimeSpan timeout, CancellationToken cancellationToken = default(CancellationToken))
        {
            var result = await TryRetractAndWaitResultAsync(name, timeout, cancellationToken);
            return result.Success;
        }

        public async Task<CylinderExecutionResult> TryExtendAndWaitResultAsync(string name, TimeSpan timeout, CancellationToken cancellationToken = default(CancellationToken))
        {
            CylinderDefinition definition;
            string failureReason;
            if (!TryGetDefinition(name, out definition))
            {
                return CreateFailureResult("未找到对应气缸定义。");
            }

            if (!Execute(definition, true, out failureReason))
            {
                return CreateFailureResult(failureReason);
            }

            return await WaitForFeedbackResultAsync(definition.ExtendedFeedbackPoints, definition.RetractedFeedbackPoints, timeout, cancellationToken);
        }

        public async Task<CylinderExecutionResult> TryRetractAndWaitResultAsync(string name, TimeSpan timeout, CancellationToken cancellationToken = default(CancellationToken))
        {
            CylinderDefinition definition;
            string failureReason;
            if (!TryGetDefinition(name, out definition))
            {
                return CreateFailureResult("未找到对应气缸定义。");
            }

            if (!Execute(definition, false, out failureReason))
            {
                return CreateFailureResult(failureReason);
            }

            return await WaitForFeedbackResultAsync(definition.RetractedFeedbackPoints, definition.ExtendedFeedbackPoints, timeout, cancellationToken);
        }

        private bool Execute(CylinderDefinition definition, bool extend, out string failureReason)
        {
            if (!TryValidateConditions(definition, extend, out failureReason))
            {
                return false;
            }

            List<DeviceIoOutputWriteRequest> requests;
            if (!TryBuildWriteRequests(definition, extend, out requests, out failureReason))
            {
                return false;
            }

            string failurePointReference;
            var result = _ioMonitorService.TrySetOutputStates(requests, out failurePointReference);
            failureReason = result
                ? string.Empty
                : string.IsNullOrWhiteSpace(failurePointReference)
                    ? "输出控制执行失败。"
                    : $"输出控制执行失败：{failurePointReference}";
            return result;
        }

        private bool TryValidateConditions(CylinderDefinition definition, bool extend, out string failureReason)
        {
            failureReason = string.Empty;
            var conditions = extend ? definition.ExtendConditions : definition.RetractConditions;
            if (conditions == null || conditions.Count == 0)
            {
                return true;
            }

            _ioMonitorService.TryRefreshNow(ActiveRefreshTimeout);

            foreach (var condition in conditions)
            {
                DeviceIoPointState point;
                if (!TryGetPoint(condition.PointReference, out point))
                {
                    failureReason = !string.IsNullOrWhiteSpace(condition.Message)
                        ? condition.Message
                        : $"条件 IO {condition.PointReference} 状态不可用。";
                    return false;
                }

                if (point.Value != condition.ExpectedState)
                {
                    failureReason = !string.IsNullOrWhiteSpace(condition.Message)
                        ? condition.Message
                        : $"条件 IO {point.Name} 未满足，要求 {(condition.ExpectedState ? "ON" : "OFF")}。";
                    return false;
                }
            }

            return true;
        }

        private async Task<CylinderExecutionResult> WaitForFeedbackResultAsync(IReadOnlyList<string> requiredOnPoints, IReadOnlyList<string> requiredOffPoints, TimeSpan timeout, CancellationToken cancellationToken)
        {
            var normalizedOnPoints = NormalizePointReferences(requiredOnPoints);
            var normalizedOffPoints = NormalizePointReferences(requiredOffPoints);
            if (normalizedOnPoints.Count == 0 && normalizedOffPoints.Count == 0)
            {
                return CreateSuccessResult();
            }

            var start = DateTime.UtcNow;

            while (DateTime.UtcNow - start < timeout)
            {
                cancellationToken.ThrowIfCancellationRequested();

                var remaining = timeout - (DateTime.UtcNow - start);
                var refreshTimeout = remaining < ActiveRefreshTimeout ? remaining : ActiveRefreshTimeout;
                if (refreshTimeout > TimeSpan.Zero)
                {
                    await _ioMonitorService.TryRefreshNowAsync(refreshTimeout, cancellationToken);
                }

                var onReady = normalizedOnPoints.All(IsPointOn);
                var offReady = normalizedOffPoints.All(pointReference => !IsPointOn(pointReference));
                if (onReady && offReady)
                {
                    return CreateSuccessResult();
                }

                await Task.Delay(FeedbackRetryInterval, cancellationToken);
            }

            return CreateFeedbackTimeoutResult(normalizedOnPoints, normalizedOffPoints);
        }

        private bool TryBuildWriteRequests(CylinderDefinition definition, bool extend, out List<DeviceIoOutputWriteRequest> requests, out string failureReason)
        {
            requests = new List<DeviceIoOutputWriteRequest>();
            failureReason = string.Empty;

            var desiredStates = new Dictionary<string, bool>(PointReferenceComparer);
            if (!AddOutputRequests(desiredStates, definition.ExtendOutputPoints, extend, out failureReason) ||
                !AddOutputRequests(desiredStates, definition.RetractOutputPoints, !extend, out failureReason))
            {
                return false;
            }

            requests.AddRange(desiredStates.Select(x => new DeviceIoOutputWriteRequest
            {
                PointReference = x.Key,
                OutputOn = x.Value
            }));

            if (requests.Count > 0)
            {
                return true;
            }

            failureReason = "未配置输出控制点。";
            return false;
        }

        private bool AddOutputRequests(IDictionary<string, bool> desiredStates, IEnumerable<string> pointReferences, bool outputOn, out string failureReason)
        {
            failureReason = string.Empty;
            if (pointReferences == null)
            {
                return true;
            }

            foreach (var pointReference in pointReferences.Where(x => !string.IsNullOrWhiteSpace(x)))
            {
                bool existingState;
                if (desiredStates.TryGetValue(pointReference, out existingState))
                {
                    if (existingState != outputOn)
                    {
                        failureReason = $"气缸输出配置冲突：{pointReference} 同时要求为 {(existingState ? "ON" : "OFF")} 和 {(outputOn ? "ON" : "OFF")}。";
                        return false;
                    }

                    continue;
                }

                desiredStates[pointReference] = outputOn;
            }

            return true;
        }

        private static List<string> NormalizePointReferences(IEnumerable<string> pointReferences)
        {
            return (pointReferences ?? Enumerable.Empty<string>())
                .Where(x => !string.IsNullOrWhiteSpace(x))
                .Distinct(PointReferenceComparer)
                .ToList();
        }

        private bool TryGetPoint(string pointReference, out DeviceIoPointState point)
        {
            return _ioMonitorService.TryGetPoint(pointReference, out point)
                || _ioMonitorService.TryGetPointByPointKey(pointReference, out point);
        }

        private bool IsPointOn(string pointReference)
        {
            return _ioMonitorService.IsPointOn(pointReference)
                || _ioMonitorService.IsPointOnByPointKey(pointReference);
        }

        private CylinderExecutionResult CreateFeedbackTimeoutResult(IReadOnlyList<string> requiredOnPoints, IReadOnlyList<string> requiredOffPoints)
        {
            var missingOnPoints = requiredOnPoints.Where(pointReference => !IsPointOn(pointReference)).ToList();
            var missingOffPoints = requiredOffPoints.Where(IsPointOn).ToList();
            var messages = new List<string>();
            if (missingOnPoints.Count > 0)
            {
                messages.Add("以下反馈未到位(期望ON)：" + string.Join("，", missingOnPoints.Select(GetPointDisplayName)));
            }

            if (missingOffPoints.Count > 0)
            {
                messages.Add("以下反馈未复位(期望OFF)：" + string.Join("，", missingOffPoints.Select(GetPointDisplayName)));
            }

            var failurePoints = missingOnPoints.Concat(missingOffPoints).Distinct(PointReferenceComparer).ToList();
            return new CylinderExecutionResult
            {
                Success = false,
                FailureReason = messages.Count > 0 ? string.Join("；", messages) : "等待气缸反馈超时。",
                FailurePointReferences = failurePoints
            };
        }

        private string GetPointDisplayName(string pointReference)
        {
            DeviceIoPointState point;
            return TryGetPoint(pointReference, out point) ? point.Name : pointReference;
        }

        private static CylinderExecutionResult CreateSuccessResult()
        {
            return new CylinderExecutionResult
            {
                Success = true,
                FailureReason = string.Empty
            };
        }

        private static CylinderExecutionResult CreateFailureResult(string failureReason)
        {
            return new CylinderExecutionResult
            {
                Success = false,
                FailureReason = failureReason ?? string.Empty
            };
        }
    }
}
