using System.Collections.ObjectModel;
using System.Diagnostics;
using CommunityToolkit.Mvvm.ComponentModel;
using CommunityToolkit.Mvvm.Input;
using ConeCalorimeter.Models;
using ConeCalorimeter.Services;
using OxyPlot;
using OxyPlot.Axes;
using OxyPlot.Legends;
using OxyPlot.Series;

namespace ConeCalorimeter.ViewModels;

public sealed class TestPageViewModel : PageViewModel
{
    private const string ResetAction = "复位";
    private const string TestAction = "测试控制";
    private const string TestStartDisplayText = "测试开始";
    private const string TestStopDisplayText = "测试结束";
    private const string FanAction = "风机";
    private const string IgniterAction = "点火器";
    private const ushort ResetCoil = 88;
    private const ushort TestStartCoil = 65;
    private const ushort TestStopCoil = 67;
    private const ushort FanCoil = 54;
    private const ushort IgniterCoil = 53;
    private const double MinimumHeatReleaseAxisMaximum = 150;
    private const double MinimumTotalAxisMaximum = 150;
    private const double PlotAxisPaddingFactor = 1.1;
    private const int MaximumPlotPoints = 600;
    private const int ControlPulseReleaseRetryCount = 3;
    private static readonly TimeSpan ControlPulseDuration = TimeSpan.FromMilliseconds(300);
    private static readonly TimeSpan ControlPulseReleaseRetryDelay = TimeSpan.FromMilliseconds(100);

    private readonly IExperimentDataService _experimentDataService;
    private readonly ITcpDeviceConnectionService _tcpDeviceConnectionService;
    private readonly LineSeries _heatReleaseSeries;
    private readonly LineSeries _totalHeatSeries;
    private readonly LineSeries _totalSmokeSeries;
    private readonly Dictionary<string, DeviceActionViewModel> _deviceActionsByLabel = [];
    private readonly HashSet<string> _pulseActionsInProgress = [];
    private DeviceActionViewModel _testAction = null!;
    private DeviceActionViewModel _resetAction = null!;
    private bool _flameDetected;
    private bool _resetInProgress;
    private int? _lastPlottedSeconds;
    private string _lastAction = "待机";

    public TestPageViewModel(
        IExperimentDataService experimentDataService,
        ITcpDeviceConnectionService tcpDeviceConnectionService) : base("测试界面")
    {
        _experimentDataService = experimentDataService;
        _tcpDeviceConnectionService = tcpDeviceConnectionService;

        TopMetrics =
        [
            new MetricDisplayViewModel("孔板流量", "m³/s"),
            new MetricDisplayViewModel("孔板压差", "Pa"),
            new MetricDisplayViewModel("孔板温度", "K"),
            new MetricDisplayViewModel("辐射锥温度", "°C"),
            new MetricDisplayViewModel("辐射照度", "kW/㎡")
        ];

        GasMetrics =
        [
            new MetricDisplayViewModel("氧浓度", "%"),
            new MetricDisplayViewModel("二氧化碳", "%"),
            new MetricDisplayViewModel("一氧化碳", "%")
        ];

        HeatMetrics =
        [
            new MetricDisplayViewModel("热释放速率", "kW/m²"),
            new MetricDisplayViewModel("热释放速率180", "MJ/m²"),
            new MetricDisplayViewModel("热释放速率300", "MJ/m²"),
            new MetricDisplayViewModel("放热总量", "MJ/m²"),
            new MetricDisplayViewModel("产烟量", "m³"),
            new MetricDisplayViewModel("当前质量", "g"),
            new MetricDisplayViewModel("质量损失", "g")
        ];

        TimerMetrics =
        [
            new MetricDisplayViewModel("点火计时", "s"),
            new MetricDisplayViewModel("实验计时", "s")
        ];

        ExecuteDeviceActionCommand = new RelayCommand<string>(ExecuteDeviceAction);
        _testAction = new DeviceActionViewModel(TestAction, ExecuteDeviceActionCommand);
        _testAction.SetDisplayText(TestStartDisplayText);
        _resetAction = new DeviceActionViewModel(ResetAction, ExecuteDeviceActionCommand);
        DeviceActions =
        [
            new DeviceActionViewModel("辐射锥升", ExecuteDeviceActionCommand),
            new DeviceActionViewModel("辐射锥降", ExecuteDeviceActionCommand),
            new DeviceActionViewModel("称重台升", ExecuteDeviceActionCommand),
            new DeviceActionViewModel("称重台降", ExecuteDeviceActionCommand),
            _testAction,
            new DeviceActionViewModel(FanAction, ExecuteDeviceActionCommand),
            new DeviceActionViewModel(IgniterAction, ExecuteDeviceActionCommand),
            _resetAction
        ];
        foreach (var action in DeviceActions)
        {
            _deviceActionsByLabel[action.Label] = action;
        }

        RefreshToggleActionStates();
        HeatReleasePlot = CreatePlotModel(out _heatReleaseSeries, out _totalHeatSeries, out _totalSmokeSeries);
        UpdateSnapshot(_experimentDataService.CurrentSnapshot);
        _experimentDataService.SnapshotUpdated += (_, snapshot) => UpdateSnapshot(snapshot);
        _experimentDataService.HeatReleaseCorrectionStatusChanged += (_, _) => RebuildFinalHeatReleasePlot();
    }

    public ObservableCollection<MetricDisplayViewModel> TopMetrics { get; }

    public ObservableCollection<MetricDisplayViewModel> GasMetrics { get; }

    public ObservableCollection<MetricDisplayViewModel> HeatMetrics { get; }

    public ObservableCollection<MetricDisplayViewModel> TimerMetrics { get; }

    public ObservableCollection<DeviceActionViewModel> DeviceActions { get; }

    public IRelayCommand<string> ExecuteDeviceActionCommand { get; }

    public PlotModel HeatReleasePlot { get; }

    public bool FlameDetected
    {
        get => _flameDetected;
        private set => SetProperty(ref _flameDetected, value);
    }

    public string FlameStatus => FlameDetected ? "已检测" : "未检测";

    public string LastAction
    {
        get => _lastAction;
        private set => SetProperty(ref _lastAction, value);
    }

    private static PlotModel CreatePlotModel(out LineSeries heatReleaseSeries, out LineSeries totalHeatSeries, out LineSeries totalSmokeSeries)
    {
        var model = new PlotModel
        {
            Title = "热释放速率-时间曲线",
            PlotAreaBorderColor = OxyColors.DimGray,
            TextColor = OxyColors.Black,
            TitleColor = OxyColors.Black
        };

        model.Legends.Add(new Legend
        {
            LegendPlacement = LegendPlacement.Inside,
            LegendPosition = LegendPosition.TopRight,
            LegendOrientation = LegendOrientation.Vertical,
            LegendBorder = OxyColors.Gray,
            LegendBackground = OxyColor.FromAColor(220, OxyColors.White)
        });

        model.Axes.Add(new LinearAxis
        {
            Position = AxisPosition.Bottom,
            Title = "时间 (s)",
            Minimum = 0,
            Maximum = 300,
            MajorGridlineStyle = LineStyle.Solid,
            MinorGridlineStyle = LineStyle.Dot,
            MajorGridlineColor = OxyColor.FromRgb(210, 210, 210),
            MinorGridlineColor = OxyColor.FromRgb(235, 235, 235)
        });

        model.Axes.Add(new LinearAxis
        {
            Position = AxisPosition.Left,
            Title = "热释放速率 (kW/m²)",
            Minimum = 0,
            Maximum = 150,
            MajorGridlineStyle = LineStyle.Solid,
            MinorGridlineStyle = LineStyle.Dot,
            MajorGridlineColor = OxyColor.FromRgb(210, 210, 210),
            MinorGridlineColor = OxyColor.FromRgb(235, 235, 235)
        });

        model.Axes.Add(new LinearAxis
        {
            Position = AxisPosition.Right,
            Title = "总量 / 总烟",
            Minimum = 0,
            Maximum = 150,
            Key = "TotalAxis",
            MajorGridlineStyle = LineStyle.None
        });

        heatReleaseSeries = new LineSeries
        {
            Title = "热释放速率",
            Color = OxyColors.Red,
            StrokeThickness = 2
        };
        totalHeatSeries = new LineSeries
        {
            Title = "总放热",
            Color = OxyColors.LimeGreen,
            StrokeThickness = 2,
            YAxisKey = "TotalAxis"
        };
        totalSmokeSeries = new LineSeries
        {
            Title = "总产烟",
            Color = OxyColors.DodgerBlue,
            StrokeThickness = 2,
            YAxisKey = "TotalAxis"
        };

        model.Series.Add(heatReleaseSeries);
        model.Series.Add(totalHeatSeries);
        model.Series.Add(totalSmokeSeries);

        return model;
    }

    private void UpdateSnapshot(RealtimeSnapshot snapshot)
    {
        TopMetrics[0].SetValue(snapshot.OrificeFlow);
        TopMetrics[1].SetValue(snapshot.OrificePressure);
        TopMetrics[2].SetValue(snapshot.OrificeTemperature);
        TopMetrics[3].SetValue(snapshot.ConeTemperature, "0.0");
        TopMetrics[4].SetValue(snapshot.Irradiance);

        GasMetrics[0].SetValue(snapshot.Oxygen);
        GasMetrics[1].SetValue(snapshot.CarbonDioxide);
        GasMetrics[2].SetValue(snapshot.CarbonMonoxide);

        HeatMetrics[0].SetValue(snapshot.HeatReleaseRate);
        HeatMetrics[1].SetValue(snapshot.Qa180);
        HeatMetrics[2].SetValue(snapshot.Qa300);
        HeatMetrics[3].SetValue(snapshot.TotalHeatRelease);
        HeatMetrics[4].SetValue(snapshot.TotalSmoke);
        HeatMetrics[5].SetValue(snapshot.CurrentMass);
        HeatMetrics[6].SetValue(snapshot.MassLoss);

        TimerMetrics[0].SetValue(snapshot.IgnitionSeconds);
        TimerMetrics[1].SetValue(snapshot.TestSeconds);

        FlameDetected = snapshot.FlameDetected;
        OnPropertyChanged(nameof(FlameStatus));

        UpdatePlot(snapshot);
    }

    private void UpdatePlot(RealtimeSnapshot snapshot)
    {
        if (!ShouldPlotSnapshot(snapshot))
        {
            return;
        }

        var x = snapshot.TestSeconds;
        AppendPoint(_heatReleaseSeries, x, snapshot.HeatReleaseRate);
        AppendPoint(_totalHeatSeries, x, snapshot.TotalHeatRelease);
        AppendPoint(_totalSmokeSeries, x, snapshot.TotalSmoke);
        _lastPlottedSeconds = snapshot.TestSeconds;

        UpdateTimeAxis(x);
        UpdateValueAxes();
        HeatReleasePlot.InvalidatePlot(true);
    }

    private bool ShouldPlotSnapshot(RealtimeSnapshot snapshot)
    {
        return snapshot.TestSeconds >= 0
            && (!_lastPlottedSeconds.HasValue || snapshot.TestSeconds > _lastPlottedSeconds.Value)
            && double.IsFinite(snapshot.HeatReleaseRate)
            && double.IsFinite(snapshot.TotalHeatRelease)
            && double.IsFinite(snapshot.TotalSmoke);
    }

    private static void AppendPoint(LineSeries series, double x, double y)
    {
        if (!double.IsFinite(x) || x < 0 || !double.IsFinite(y))
        {
            return;
        }

        series.Points.Add(new DataPoint(x, y));

        if (series.Points.Count > MaximumPlotPoints)
        {
            series.Points.RemoveAt(0);
        }
    }

    private void UpdateTimeAxis(double latestSeconds)
    {
        var bottomAxis = HeatReleasePlot.Axes.First(axis => axis.Position == AxisPosition.Bottom);
        bottomAxis.Minimum = Math.Max(0, latestSeconds - MaximumPlotPoints);
        bottomAxis.Maximum = Math.Max(300, latestSeconds + 40);
    }

    private void UpdateValueAxes()
    {
        var heatReleaseAxis = HeatReleasePlot.Axes.First(axis => axis.Position == AxisPosition.Left);
        heatReleaseAxis.Maximum = CalculateAxisMaximum(MinimumHeatReleaseAxisMaximum, _heatReleaseSeries);

        var totalAxis = HeatReleasePlot.Axes.First(axis => axis.Key == "TotalAxis");
        totalAxis.Maximum = CalculateAxisMaximum(
            MinimumTotalAxisMaximum,
            _totalHeatSeries,
            _totalSmokeSeries);
    }

    private static double CalculateAxisMaximum(double minimumMaximum, params LineSeries[] seriesCollection)
    {
        var maximum = seriesCollection
            .SelectMany(series => series.Points)
            .Where(point => double.IsFinite(point.Y))
            .Select(point => point.Y)
            .DefaultIfEmpty(0)
            .Max();

        return Math.Max(minimumMaximum, Math.Ceiling(maximum * PlotAxisPaddingFactor));
    }

    private void ExecuteDeviceAction(string? action)
    {
        if (string.IsNullOrWhiteSpace(action))
        {
            return;
        }

        if (action == TestAction)
        {
            ExecuteTestControlAction();
            return;
        }

        if (action == ResetAction)
        {
            LastAction = action;
            ExecuteResetAction();
            return;
        }

        if (TryGetToggleDeviceActionCoil(action, out var toggleCoilAddress))
        {
            ToggleDeviceAction(action, toggleCoilAddress);
            return;
        }

        if (TryGetPulseDeviceActionCoil(action, out var coilAddress))
        {
            ExecutePulseDeviceAction(action, coilAddress);
            return;
        }

        LastAction = action;
    }

    private async void ExecuteResetAction()
    {
        if (_resetInProgress)
        {
            return;
        }

        if (!_tcpDeviceConnectionService.TryWriteCoil(ResetCoil, true))
        {
            LastAction = "复位失败";
            Debug.WriteLine("Device reset write failed.");
            return;
        }

        SetResetInProgress(true);
        LastAction = "复位中";

        await Task.Delay(ControlPulseDuration);
        if (await ReleaseControlPulseAsync(ResetCoil, ResetAction))
        {
            LastAction = "复位完成";
        }
        else
        {
            LastAction = "复位线圈复位失败";
        }

        SetResetInProgress(false);
    }

    private void SetResetInProgress(bool resetInProgress)
    {
        _resetInProgress = resetInProgress;
        _resetAction.SetDisplayText(resetInProgress ? "复位中" : ResetAction);
    }

    private void ClearPlotSeries()
    {
        _heatReleaseSeries.Points.Clear();
        _totalHeatSeries.Points.Clear();
        _totalSmokeSeries.Points.Clear();

        var bottomAxis = HeatReleasePlot.Axes.First(axis => axis.Position == AxisPosition.Bottom);
        bottomAxis.Minimum = 0;
        bottomAxis.Maximum = 300;
        UpdateValueAxes();
        _lastPlottedSeconds = null;
        HeatReleasePlot.InvalidatePlot(true);
    }

    private void RebuildFinalHeatReleasePlot()
    {
        if (!_experimentDataService.IsHeatReleaseCorrectionApplied)
        {
            LastAction = _experimentDataService.HeatReleaseCorrectionStatus;
            return;
        }

        ClearPlotSeries();
        foreach (var record in _experimentDataService.Records)
        {
            AppendPoint(_heatReleaseSeries, record.TestSeconds, record.HeatReleaseRate);
            AppendPoint(_totalHeatSeries, record.TestSeconds, record.TotalHeatRelease);
            AppendPoint(_totalSmokeSeries, record.TestSeconds, record.TotalSmoke);
            _lastPlottedSeconds = record.TestSeconds;
        }

        if (_lastPlottedSeconds.HasValue)
        {
            UpdateTimeAxis(_lastPlottedSeconds.Value);
        }

        UpdateValueAxes();
        HeatReleasePlot.InvalidatePlot(true);
        LastAction = _experimentDataService.HeatReleaseCorrectionStatus;
    }

    private async void ExecuteTestControlAction()
    {
        if (!TryBeginPulseAction(TestAction))
        {
            return;
        }

        var isStarting = !_testAction.IsActive;
        var actionText = isStarting ? TestStartDisplayText : TestStopDisplayText;
        var coilAddress = isStarting ? TestStartCoil : TestStopCoil;

        if (!_tcpDeviceConnectionService.TryWriteCoil(coilAddress, true))
        {
            LastAction = $"{actionText}失败";
            Debug.WriteLine($"Device action '{actionText}' write failed.");
            EndPulseAction(TestAction);
            return;
        }

        try
        {
            if (isStarting)
            {
                ClearPlotSeries();
                _experimentDataService.StartTest();
            }
            else
            {
                _experimentDataService.StopTest();
            }

            SetTestRunning(isStarting);
            LastAction = actionText;

            await Task.Delay(ControlPulseDuration);
            if (!await ReleaseControlPulseAsync(coilAddress, actionText))
            {
                LastAction = $"{actionText}线圈复位失败";
            }
        }
        finally
        {
            EndPulseAction(TestAction);
        }
    }

    private void SetTestRunning(bool isRunning)
    {
        _testAction.UpdateStatus(isRunning);
        _testAction.SetDisplayText(isRunning ? TestStopDisplayText : TestStartDisplayText);
    }

    private void RefreshToggleActionStates()
    {
        UpdateToggleActionStatus(FanAction, FanCoil);
        UpdateToggleActionStatus(IgniterAction, IgniterCoil);
    }

    private bool UpdateToggleActionStatus(string action, ushort coilAddress)
    {
        if (!_deviceActionsByLabel.TryGetValue(action, out var actionViewModel))
        {
            return false;
        }

        if (_tcpDeviceConnectionService.TryReadCoil(coilAddress, out var isActive))
        {
            actionViewModel.UpdateStatus(isActive);
            return true;
        }

        actionViewModel.UpdateStatus(actionViewModel.IsActive, false);
        return false;
    }

    private void ToggleDeviceAction(string action, ushort coilAddress)
    {
        if (!_tcpDeviceConnectionService.TryReadCoil(coilAddress, out var isActive))
        {
            LastAction = $"{action}状态读取失败";
            Debug.WriteLine($"Device action '{action}' state read failed.");
            UpdateToggleActionStatus(action, coilAddress);
            return;
        }

        var nextValue = !isActive;
        if (_tcpDeviceConnectionService.TryWriteCoil(coilAddress, nextValue))
        {
            LastAction = $"{action}控制完成";
            if (!UpdateToggleActionStatus(action, coilAddress)
                && _deviceActionsByLabel.TryGetValue(action, out var actionViewModel))
            {
                actionViewModel.UpdateStatus(nextValue);
            }

            return;
        }

        LastAction = $"{action}控制失败";
        Debug.WriteLine($"Device action '{action}' write failed.");
    }

    private async void ExecutePulseDeviceAction(string action, ushort coilAddress)
    {
        if (!TryBeginPulseAction(action))
        {
            return;
        }

        _deviceActionsByLabel.TryGetValue(action, out var actionViewModel);

        if (!_tcpDeviceConnectionService.TryWriteCoil(coilAddress, true))
        {
            LastAction = $"{action}失败";
            Debug.WriteLine($"Device action '{action}' write failed.");
            EndPulseAction(action);
            return;
        }

        try
        {
            actionViewModel?.UpdateStatus(true);
            LastAction = action;

            await Task.Delay(ControlPulseDuration);
            if (await ReleaseControlPulseAsync(coilAddress, action))
            {
                actionViewModel?.UpdateStatus(false);
                return;
            }

            actionViewModel?.UpdateStatus(true, false);
            LastAction = $"{action}线圈复位失败";
        }
        finally
        {
            EndPulseAction(action);
        }
    }

    private bool TryBeginPulseAction(string action)
    {
        return _pulseActionsInProgress.Add(action);
    }

    private void EndPulseAction(string action)
    {
        _pulseActionsInProgress.Remove(action);
    }

    private async Task<bool> ReleaseControlPulseAsync(ushort coilAddress, string actionText)
    {
        for (var attempt = 1; attempt <= ControlPulseReleaseRetryCount; attempt++)
        {
            if (_tcpDeviceConnectionService.TryWriteCoil(coilAddress, false))
            {
                return true;
            }

            if (attempt < ControlPulseReleaseRetryCount)
            {
                await Task.Delay(ControlPulseReleaseRetryDelay);
            }
        }

        Debug.WriteLine($"Device action '{actionText}' pulse release failed.");
        return false;
    }

    private static bool TryGetToggleDeviceActionCoil(string action, out ushort coilAddress)
    {
        switch (action)
        {
            case FanAction:
                coilAddress = FanCoil;
                return true;
            case IgniterAction:
                coilAddress = IgniterCoil;
                return true;
            default:
                coilAddress = 0;
                return false;
        }
    }

    private static bool TryGetPulseDeviceActionCoil(string action, out ushort coilAddress)
    {
        switch (action)
        {
            case "称重台升":
                coilAddress = 93;
                return true;
            case "称重台降":
                coilAddress = 94;
                return true;
            case "辐射锥升":
                coilAddress = 83;
                return true;
            case "辐射锥降":
                coilAddress = 84;
                return true;
            default:
                coilAddress = 0;
                return false;
        }
    }
}
