using System.Collections.ObjectModel;
using System.Diagnostics;
using System.Windows.Threading;
using ConeCalorimeter.Models;
using Serilog;

namespace ConeCalorimeter.Services;

public sealed class ExperimentDataService : IExperimentDataService
{
    private const double TotalHeatReleaseDivisor = 1000;
    private static readonly TimeSpan TimerInterval = TimeSpan.FromMilliseconds(250);
    private static readonly TimeSpan DevicePollInterval = TimeSpan.FromSeconds(1);

    private readonly IRealtimeDataService _realtimeDataService;
    private readonly IHeatReleaseCorrectionService _heatReleaseCorrectionService;
    private readonly BulkObservableCollection<RealtimeDataRecord> _records;
    private readonly DispatcherTimer _timer;
    private readonly Stopwatch _testClock = new();
    private DateTime _lastDevicePollStartedAtUtc = DateTime.MinValue;
    private double? _initialMass;
    private double? _previousMass;
    private int? _previousMassSeconds;
    private double _accumulatedTotalHeatRelease;
    private double _accumulatedTotalSmoke;
    private int? _lastAccumulationSeconds;
    private int? _lastRecordedSeconds;
    private int? _deviceTestTimerBaselineSeconds;
    private bool _hasObservedDeviceTestTimerReset;
    private bool _isTestRunning;
    private bool _isRecordingActive;
    private bool _isPollingSnapshot;
    private RealtimeSnapshot? _completedSnapshot;

    public ExperimentDataService(
        IRealtimeDataService realtimeDataService,
        IHeatReleaseCorrectionService heatReleaseCorrectionService)
    {
        _realtimeDataService = realtimeDataService;
        _heatReleaseCorrectionService = heatReleaseCorrectionService;
        _records = [];
        Records = _records;
        CurrentSnapshot = BuildIdleSnapshot(_realtimeDataService.GetCurrentSnapshot(TimeSpan.Zero));
        HeatReleaseCorrectionStatus = "尚未完成测试，未生成正式 HRR 校正结果。";

        _timer = new DispatcherTimer
        {
            Interval = TimerInterval
        };
        _timer.Tick += async (_, _) => await TickAsync();
        _timer.Start();
    }

    public ObservableCollection<RealtimeDataRecord> Records { get; }

    public RealtimeSnapshot CurrentSnapshot { get; private set; }

    public bool IsHeatReleaseCorrectionApplied { get; private set; }

    public string HeatReleaseCorrectionStatus { get; private set; }

    public event EventHandler<RealtimeSnapshot>? SnapshotUpdated;

    public event EventHandler? HeatReleaseCorrectionStatusChanged;

    public void StartTest()
    {
        Records.Clear();
        ResetComputedState();
        _completedSnapshot = null;
        SetHeatReleaseCorrectionStatus(false, "测试进行中，当前显示和记录为原始 HRR/THR。");
        _isTestRunning = true;
        _isRecordingActive = false;
        _testClock.Reset();
        PrepareDeviceTestTimerGate();
        PublishSnapshot(BuildWaitingSnapshot(CurrentSnapshot));

        QueueImmediateDevicePoll();

        Log.Information(
            "Experiment test started. Waiting for D1015 timing marker. DeviceTimerBaseline={DeviceTimerBaseline}.",
            _deviceTestTimerBaselineSeconds);
    }

    public void StopTest()
    {
        _isTestRunning = false;
        _isRecordingActive = false;
        _testClock.Stop();
        ApplyFinalHeatReleaseCorrection();
        _completedSnapshot = BuildCompletedSnapshot(CurrentSnapshot);
        PublishSnapshot(BuildIdleSnapshot(CurrentSnapshot));

        Log.Information(
            "Experiment test stopped. Records={RecordCount}, LastRecordedSeconds={LastRecordedSeconds}.",
            Records.Count,
            _lastRecordedSeconds);
    }

    public void ClearRecords()
    {
        Records.Clear();

        if (!_isTestRunning)
        {
            _testClock.Reset();
            _isRecordingActive = false;
            _completedSnapshot = null;
            SetHeatReleaseCorrectionStatus(false, "记录已清空，未生成正式 HRR 校正结果。");
            ResetComputedState();
            PublishSnapshot(BuildIdleSnapshot(CurrentSnapshot));
        }
    }

    private async Task TickAsync()
    {
        await RefreshDeviceSnapshotAsync();
    }

    private void QueueImmediateDevicePoll()
    {
        _lastDevicePollStartedAtUtc = DateTime.MinValue;
        _ = RefreshDeviceSnapshotAsync();
    }

    private async Task RefreshDeviceSnapshotAsync()
    {
        if (!TryBeginDevicePoll())
        {
            return;
        }

        try
        {
            var elapsed = _isTestRunning && _isRecordingActive ? _testClock.Elapsed : TimeSpan.Zero;
            var snapshot = await Task.Run(() => _realtimeDataService.GetCurrentSnapshot(elapsed));
            if (_isTestRunning)
            {
                RecordDeviceSnapshot(snapshot);
            }
            else
            {
                PublishSnapshot(BuildIdleSnapshot(snapshot));
            }
        }
        catch (Exception ex)
        {
            Debug.WriteLine($"Realtime snapshot polling failed: {ex.Message}");
            Log.Warning(ex, "Realtime snapshot polling failed.");
        }
        finally
        {
            _isPollingSnapshot = false;
        }
    }

    private bool TryBeginDevicePoll()
    {
        if (_isPollingSnapshot)
        {
            return false;
        }

        var now = DateTime.UtcNow;
        if (now - _lastDevicePollStartedAtUtc < DevicePollInterval)
        {
            return false;
        }

        _lastDevicePollStartedAtUtc = now;
        _isPollingSnapshot = true;
        return true;
    }

    private void RecordDeviceSnapshot(RealtimeSnapshot snapshot)
    {
        if (!_isTestRunning)
        {
            return;
        }

        if (!_isRecordingActive)
        {
            if (!ShouldStartTimedRecording(snapshot))
            {
                PublishSnapshot(BuildWaitingSnapshot(snapshot));
                return;
            }

            BeginTimedRecording(snapshot);
            snapshot = snapshot with
            {
                TestSeconds = 0
            };
        }
        else
        {
            snapshot = snapshot with
            {
                TestSeconds = GetElapsedTestSeconds()
            };
        }

        var currentSecond = Math.Max(0, snapshot.TestSeconds);
        if (_lastRecordedSeconds.HasValue && currentSecond <= _lastRecordedSeconds.Value)
        {
            PublishSnapshot(BuildDisplaySnapshot(snapshot));
            return;
        }

        var timedSnapshot = snapshot with
        {
            TestSeconds = currentSecond
        };
        var massSnapshot = ApplyMassLoss(timedSnapshot);
        var accumulatedSnapshot = ApplyAccumulatedTotals(massSnapshot);

        PublishSnapshot(accumulatedSnapshot);
        Records.Add(RealtimeDataRecord.FromSnapshot(accumulatedSnapshot));
        _lastRecordedSeconds = currentSecond;
    }

    private RealtimeSnapshot BuildDisplaySnapshot(RealtimeSnapshot snapshot)
    {
        if (!_isTestRunning)
        {
            return BuildIdleSnapshot(snapshot);
        }

        if (!_isRecordingActive)
        {
            return BuildWaitingSnapshot(snapshot);
        }

        var timedSnapshot = snapshot with
        {
            TestSeconds = GetElapsedTestSeconds()
        };

        if (!_initialMass.HasValue && double.IsFinite(timedSnapshot.CurrentMass))
        {
            _initialMass = timedSnapshot.CurrentMass;
        }

        var massLoss = _initialMass.HasValue && double.IsFinite(timedSnapshot.CurrentMass)
            ? _initialMass.Value - timedSnapshot.CurrentMass
            : double.NaN;

        return timedSnapshot with
        {
            InitialMass = _initialMass ?? double.NaN,
            MassLoss = massLoss,
            MassLossRate = CurrentSnapshot.MassLossRate,
            TotalHeatRelease = _accumulatedTotalHeatRelease,
            TotalSmoke = _accumulatedTotalSmoke
        };
    }

    private RealtimeSnapshot BuildIdleSnapshot(RealtimeSnapshot snapshot)
    {
        var completedSnapshot = _completedSnapshot;

        return snapshot with
        {
            HeatReleaseRate = completedSnapshot?.HeatReleaseRate ?? snapshot.HeatReleaseRate,
            PeakHeatReleaseRate = completedSnapshot?.PeakHeatReleaseRate ?? snapshot.PeakHeatReleaseRate,
            TestSeconds = completedSnapshot?.TestSeconds ?? -1,
            InitialMass = completedSnapshot?.InitialMass ?? double.NaN,
            MassLoss = completedSnapshot?.MassLoss ?? double.NaN,
            MassLossRate = completedSnapshot?.MassLossRate ?? double.NaN,
            TotalHeatRelease = completedSnapshot?.TotalHeatRelease ?? double.NaN,
            TotalSmoke = completedSnapshot?.TotalSmoke ?? double.NaN
        };
    }

    private RealtimeSnapshot BuildWaitingSnapshot(RealtimeSnapshot snapshot)
    {
        var displayInitialMass = double.IsFinite(snapshot.CurrentMass)
            ? snapshot.CurrentMass
            : double.NaN;

        return snapshot with
        {
            TestSeconds = -1,
            InitialMass = displayInitialMass,
            MassLoss = double.IsFinite(displayInitialMass) ? 0 : double.NaN,
            MassLossRate = double.NaN,
            TotalHeatRelease = 0,
            TotalSmoke = 0
        };
    }

    private RealtimeSnapshot? BuildCompletedSnapshot(RealtimeSnapshot snapshot)
    {
        if (Records.Count == 0)
        {
            return snapshot.TestSeconds >= 0 ? snapshot : null;
        }

        var lastRecord = Records[Records.Count - 1];
        return snapshot with
        {
            HeatReleaseRate = lastRecord.HeatReleaseRate,
            PeakHeatReleaseRate = lastRecord.PeakHeatReleaseRate,
            TotalHeatRelease = lastRecord.TotalHeatRelease,
            InitialMass = lastRecord.InitialMass,
            MassLoss = lastRecord.MassLoss,
            MassLossRate = lastRecord.MassLossRate,
            IgnitionSeconds = lastRecord.IgnitionSeconds,
            TestSeconds = lastRecord.TestSeconds,
            TotalSmoke = lastRecord.TotalSmoke
        };
    }

    private void ApplyFinalHeatReleaseCorrection()
    {
        if (Records.Count == 0)
        {
            SetHeatReleaseCorrectionStatus(false, "没有有效记录，无法生成正式 HRR 校正结果。");
            return;
        }

        var result = _heatReleaseCorrectionService.Correct(Records.ToList());
        if (result.IsApplied)
        {
            _records.ReplaceAll(result.Records);
        }

        SetHeatReleaseCorrectionStatus(result.IsApplied, result.Status);
    }

    private void SetHeatReleaseCorrectionStatus(bool isApplied, string status)
    {
        IsHeatReleaseCorrectionApplied = isApplied;
        HeatReleaseCorrectionStatus = status;
        HeatReleaseCorrectionStatusChanged?.Invoke(this, EventArgs.Empty);
    }

    private void PrepareDeviceTestTimerGate()
    {
        var currentDeviceSeconds = CurrentSnapshot.DeviceTestSeconds;
        _deviceTestTimerBaselineSeconds = currentDeviceSeconds >= 0 ? currentDeviceSeconds : null;
        _hasObservedDeviceTestTimerReset = !_deviceTestTimerBaselineSeconds.HasValue
            || _deviceTestTimerBaselineSeconds.Value <= 0;
    }

    private bool ShouldStartTimedRecording(RealtimeSnapshot snapshot)
    {
        var deviceSeconds = snapshot.DeviceTestSeconds;
        if (deviceSeconds < 0)
        {
            return false;
        }

        if (!_deviceTestTimerBaselineSeconds.HasValue || _hasObservedDeviceTestTimerReset)
        {
            return deviceSeconds > 0;
        }

        var baselineSeconds = _deviceTestTimerBaselineSeconds.Value;
        if (deviceSeconds <= 0)
        {
            _hasObservedDeviceTestTimerReset = true;
            return false;
        }

        return deviceSeconds != baselineSeconds;
    }

    private void BeginTimedRecording(RealtimeSnapshot snapshot)
    {
        _isRecordingActive = true;
        _testClock.Restart();

        if (double.IsFinite(snapshot.CurrentMass))
        {
            _initialMass = snapshot.CurrentMass;
        }

        Log.Information(
            "Experiment timed recording started by D1015. DeviceTestSeconds={DeviceTestSeconds}, InitialMass={InitialMass}.",
            snapshot.DeviceTestSeconds,
            _initialMass);
    }

    private int GetElapsedTestSeconds()
    {
        return Math.Max(0, (int)Math.Floor(_testClock.Elapsed.TotalSeconds));
    }

    private void PublishSnapshot(RealtimeSnapshot snapshot)
    {
        CurrentSnapshot = snapshot;
        SnapshotUpdated?.Invoke(this, snapshot);
    }

    private RealtimeSnapshot ApplyAccumulatedTotals(RealtimeSnapshot snapshot)
    {
        if (!_isTestRunning)
        {
            return snapshot with
            {
                TotalHeatRelease = double.NaN,
                TotalSmoke = double.NaN
            };
        }

        if (snapshot.TestSeconds < 0)
        {
            return snapshot with
            {
                TotalHeatRelease = _accumulatedTotalHeatRelease,
                TotalSmoke = _accumulatedTotalSmoke
            };
        }

        if (!_lastAccumulationSeconds.HasValue)
        {
            _lastAccumulationSeconds = snapshot.TestSeconds;
            return snapshot with
            {
                TotalHeatRelease = _accumulatedTotalHeatRelease,
                TotalSmoke = _accumulatedTotalSmoke
            };
        }

        var deltaSeconds = snapshot.TestSeconds - _lastAccumulationSeconds.Value;
        if (deltaSeconds > 0)
        {
            if (double.IsFinite(snapshot.HeatReleaseRate))
            {
                _accumulatedTotalHeatRelease += snapshot.HeatReleaseRate * deltaSeconds / TotalHeatReleaseDivisor;
            }

            if (double.IsFinite(snapshot.SmokeProduction))
            {
                _accumulatedTotalSmoke += snapshot.SmokeProduction * deltaSeconds;
            }

            _lastAccumulationSeconds = snapshot.TestSeconds;
        }

        return snapshot with
        {
            TotalHeatRelease = _accumulatedTotalHeatRelease,
            TotalSmoke = _accumulatedTotalSmoke
        };
    }

    private RealtimeSnapshot ApplyMassLoss(RealtimeSnapshot snapshot)
    {
        if (!_isTestRunning)
        {
            return snapshot with
            {
                InitialMass = _initialMass ?? double.NaN,
                MassLoss = double.NaN,
                MassLossRate = double.NaN
            };
        }

        if (!_initialMass.HasValue && double.IsFinite(snapshot.CurrentMass))
        {
            _initialMass = snapshot.CurrentMass;
        }

        var massLoss = _initialMass.HasValue && double.IsFinite(snapshot.CurrentMass)
            ? _initialMass.Value - snapshot.CurrentMass
            : double.NaN;
        var massLossRate = CalculateMassLossRate(snapshot);

        return snapshot with
        {
            InitialMass = _initialMass ?? double.NaN,
            MassLoss = massLoss,
            MassLossRate = massLossRate
        };
    }

    private double CalculateMassLossRate(RealtimeSnapshot snapshot)
    {
        if (snapshot.TestSeconds < 0 || !double.IsFinite(snapshot.CurrentMass))
        {
            return double.NaN;
        }

        if (!_previousMass.HasValue || !_previousMassSeconds.HasValue)
        {
            _previousMass = snapshot.CurrentMass;
            _previousMassSeconds = snapshot.TestSeconds;
            return double.NaN;
        }

        var deltaSeconds = snapshot.TestSeconds - _previousMassSeconds.Value;
        var deltaMass = _previousMass.Value - snapshot.CurrentMass;

        _previousMass = snapshot.CurrentMass;
        _previousMassSeconds = snapshot.TestSeconds;

        return deltaSeconds > 0 && deltaMass >= 0
            ? deltaMass / deltaSeconds
            : double.NaN;
    }

    private void ResetComputedState()
    {
        _initialMass = null;
        _previousMass = null;
        _previousMassSeconds = null;
        _accumulatedTotalHeatRelease = 0;
        _accumulatedTotalSmoke = 0;
        _lastAccumulationSeconds = null;
        _lastRecordedSeconds = null;
    }
}
