using System.Collections.ObjectModel;
using System.Diagnostics;
using System.Windows.Threading;
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 ushort ResetCoil = 88;
    private const ushort ResetCompleteCoil = 89;
    private const double MinimumHeatReleaseAxisMaximum = 150;
    private const double MinimumTotalAxisMaximum = 150;
    private const double PlotAxisPaddingFactor = 1.1;
    private const int MaximumPlotPoints = 600;
    private static readonly TimeSpan ResetPulseDuration = TimeSpan.FromMilliseconds(300);
    private static readonly TimeSpan ResetCompletionTimeout = TimeSpan.FromSeconds(15);

    private readonly IExperimentDataService _experimentDataService;
    private readonly ITcpDeviceConnectionService _tcpDeviceConnectionService;
    private readonly LineSeries _heatReleaseSeries;
    private readonly LineSeries _totalHeatSeries;
    private readonly LineSeries _totalSmokeSeries;
    private readonly DispatcherTimer _resetStatusTimer;
    private DeviceActionViewModel _resetAction = null!;
    private bool _flameDetected;
    private bool _resetInProgress;
    private int? _lastPlottedSeconds;
    private DateTime _resetStartedAt;
    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);
        _resetAction = new DeviceActionViewModel(ResetAction, ExecuteDeviceActionCommand);
        DeviceActions =
        [
            new DeviceActionViewModel("辐射锥升", ExecuteDeviceActionCommand),
            new DeviceActionViewModel("辐射锥降", ExecuteDeviceActionCommand),
            new DeviceActionViewModel("称重台升", ExecuteDeviceActionCommand),
            new DeviceActionViewModel("称重台降", ExecuteDeviceActionCommand),
            new DeviceActionViewModel("测试开始", ExecuteDeviceActionCommand),
            new DeviceActionViewModel("测试结束", ExecuteDeviceActionCommand),
            new DeviceActionViewModel("风机开", ExecuteDeviceActionCommand),
            new DeviceActionViewModel("风机关", ExecuteDeviceActionCommand),
            new DeviceActionViewModel("点火开", ExecuteDeviceActionCommand),
            new DeviceActionViewModel("点火关", ExecuteDeviceActionCommand),
            _resetAction
        ];

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

        _resetStatusTimer = new DispatcherTimer
        {
            Interval = TimeSpan.FromSeconds(1)
        };
        _resetStatusTimer.Tick += (_, _) => RefreshResetStatus();
        _resetStatusTimer.Start();
    }

    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));
    }

    public bool StartMomentaryDeviceAction(string? action)
    {
        return TryWriteMomentaryDeviceAction(action, true);
    }

    public bool StopMomentaryDeviceAction(string? action)
    {
        return TryWriteMomentaryDeviceAction(action, false);
    }

    private bool TryWriteMomentaryDeviceAction(string? action, bool isRunning)
    {
        if (string.IsNullOrWhiteSpace(action)
            || !TryGetMomentaryDeviceActionCoil(action, out var coilAddress))
        {
            return false;
        }

        LastAction = isRunning ? action : $"停止：{action}";

        if (!_tcpDeviceConnectionService.TryWriteCoil(coilAddress, isRunning))
        {
            Debug.WriteLine($"Momentary device action '{action}' write failed.");
        }

        return true;
    }

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

        if (IsMomentaryDeviceAction(action))
        {
            return;
        }

        LastAction = action;

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

        if (action == "测试开始")
        {
            _experimentDataService.StartTest();
            ClearPlotSeries();
        }
        else if (action == "测试结束")
        {
            _experimentDataService.StopTest();
        }

        if (!TryGetDeviceActionCoil(action, out var coilAddress, out var value))
        {
            return;
        }

        if (!_tcpDeviceConnectionService.TryWriteCoil(coilAddress, value))
        {
            Debug.WriteLine($"Device action '{action}' write failed.");
        }
    }

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

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

        SetResetInProgress(true);
        LastAction = "复位中";
        _resetStartedAt = DateTime.Now;

        await Task.Delay(ResetPulseDuration);
        if (!_tcpDeviceConnectionService.TryWriteCoil(ResetCoil, false))
        {
            Debug.WriteLine("Device reset pulse release failed.");
        }
    }

    private void RefreshResetStatus()
    {
        if (!_resetInProgress)
        {
            return;
        }

        var statusRead = _tcpDeviceConnectionService.TryReadCoil(ResetCompleteCoil, out var resetComplete);
        if (statusRead && resetComplete)
        {
            SetResetInProgress(false);
            LastAction = "复位完成";
            return;
        }

        if (DateTime.Now - _resetStartedAt < ResetCompletionTimeout)
        {
            return;
        }

        SetResetInProgress(false);
        LastAction = statusRead ? "复位超时" : "复位状态读取失败";
    }

    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 static bool TryGetDeviceActionCoil(string action, out ushort coilAddress, out bool value)
    {
        value = true;

        switch (action)
        {
            case "测试开始":
                coilAddress = 65;
                return true;
            case "测试结束":
                coilAddress = 67;
                return true;
            case "点火开":
                coilAddress = 53;
                return true;
            case "点火关":
                coilAddress = 53;
                value = false;
                return true;
            case "风机开":
                coilAddress = 54;
                return true;
            case "风机关":
                coilAddress = 54;
                value = false;
                return true;
            default:
                coilAddress = 0;
                return false;
        }
    }

    private static bool IsMomentaryDeviceAction(string action)
    {
        return TryGetMomentaryDeviceActionCoil(action, out _);
    }

    private static bool TryGetMomentaryDeviceActionCoil(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;
        }
    }
}
