using System.Collections.ObjectModel;
using System.Diagnostics;
using System.Globalization;
using System.Windows.Threading;
using CommunityToolkit.Mvvm.ComponentModel;
using CommunityToolkit.Mvvm.Input;
using ConeCalorimeter.Services;

namespace ConeCalorimeter.ViewModels;

public sealed class SmokeDensitySettingsViewModel : PageViewModel
{
    private const ushort AbsorbanceRegister = 120;
    private const double AbsorbanceMinimum = 0;
    private const double AbsorbanceMaximum = 100;
    private const double AbsorbanceStableTolerance = 0.25;
    private const int AbsorbanceStableReadCount = 2;

    private readonly Action _closeAction;
    private readonly ITcpDeviceConnectionService _tcpDeviceConnectionService;
    private readonly DispatcherTimer _refreshTimer;
    private readonly HashSet<ushort> _loggedFloatDiagnostics = [];
    private readonly HashSet<ushort> _loggedInvalidFloatDiagnostics = [];
    private double? _stableAbsorbance;
    private double? _pendingAbsorbance;
    private int _pendingAbsorbanceReadCount;
    private string _absorbanceText = "";
    private string _lastCalibration = "待校准";

    public SmokeDensitySettingsViewModel(
        Action closeAction,
        ITcpDeviceConnectionService tcpDeviceConnectionService) : base("烟密度设置")
    {
        _closeAction = closeAction;
        _tcpDeviceConnectionService = tcpDeviceConnectionService;
        CloseCommand = new RelayCommand(_closeAction);
        CalibrationCommand = new RelayCommand<string>(Calibrate);

        CalibrationActions =
        [
            new SmokeDensityCalibrationViewModel("0%校准", CalibrationCommand),
            new SmokeDensityCalibrationViewModel("25%校准", CalibrationCommand),
            new SmokeDensityCalibrationViewModel("50%校准", CalibrationCommand),
            new SmokeDensityCalibrationViewModel("75%校准", CalibrationCommand),
            new SmokeDensityCalibrationViewModel("100%校准", CalibrationCommand)
        ];

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

    public ObservableCollection<SmokeDensityCalibrationViewModel> CalibrationActions { get; }

    public IRelayCommand CloseCommand { get; }

    public IRelayCommand<string> CalibrationCommand { get; }

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

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

    private void Calibrate(string? label)
    {
        if (string.IsNullOrWhiteSpace(label))
        {
            return;
        }

        LastCalibration = label;

        if (!TryGetCalibrationCoil(label, out var coilAddress))
        {
            return;
        }

        if (!_tcpDeviceConnectionService.TryWriteCoil(coilAddress, true))
        {
            LastCalibration = $"{label}失败";
            Debug.WriteLine($"Smoke density calibration '{label}' write failed.");
        }
    }

    private void RefreshDeviceValues()
    {
        if (!TryReadRangedFloatValue(
            "Absorbance",
            AbsorbanceRegister,
            AbsorbanceMinimum,
            AbsorbanceMaximum,
            out var value))
        {
            if (!_stableAbsorbance.HasValue)
            {
                AbsorbanceText = string.Empty;
            }

            return;
        }

        if (TryUpdateStableAbsorbance(value, out var stableValue))
        {
            AbsorbanceText = stableValue.ToString("0.00", CultureInfo.InvariantCulture);
        }
    }

    private bool TryReadRangedFloatValue(
        string label,
        ushort registerAddress,
        double minimum,
        double maximum,
        out double value)
    {
        value = double.NaN;

        if (!_tcpDeviceConnectionService.TryReadFloatValues(registerAddress, out var result))
        {
            return false;
        }

        if (!ModbusFloatSelector.TrySelectRangedFloat(result, minimum, maximum, out var byteOrder, out var selectedValue, out var matchCount))
        {
            LogFloatDiagnostic(label, registerAddress, result, null, matchCount);
            return false;
        }

        LogFloatDiagnostic(label, registerAddress, result, byteOrder, matchCount);
        value = NormalizeDisplayValue(selectedValue);
        return true;
    }

    private bool TryUpdateStableAbsorbance(double value, out double stableValue)
    {
        stableValue = _stableAbsorbance ?? value;

        if (_stableAbsorbance.HasValue
            && Math.Abs(value - _stableAbsorbance.Value) <= AbsorbanceStableTolerance)
        {
            _pendingAbsorbance = null;
            _pendingAbsorbanceReadCount = 0;
            stableValue = _stableAbsorbance.Value;
            return true;
        }

        if (!_pendingAbsorbance.HasValue
            || Math.Abs(value - _pendingAbsorbance.Value) > AbsorbanceStableTolerance)
        {
            _pendingAbsorbance = value;
            _pendingAbsorbanceReadCount = 1;
            return false;
        }

        _pendingAbsorbanceReadCount++;

        if (_pendingAbsorbanceReadCount < AbsorbanceStableReadCount)
        {
            return false;
        }

        _stableAbsorbance = value;
        _pendingAbsorbance = null;
        _pendingAbsorbanceReadCount = 0;
        stableValue = value;
        return true;
    }

    private void LogFloatDiagnostic(
        string label,
        ushort registerAddress,
        ModbusFloatReadResult result,
        ModbusFloatByteOrder? selectedByteOrder,
        int matchCount)
    {
        if (selectedByteOrder is null)
        {
            if (!_loggedInvalidFloatDiagnostics.Add(registerAddress))
            {
                return;
            }
        }
        else if (!_loggedFloatDiagnostics.Add(registerAddress))
        {
            return;
        }

        var selectedText = selectedByteOrder?.ToString() ?? "none";

        Debug.WriteLine(
            $"Smoke density float {label} register {registerAddress} raw [{result.RawHex}], "
            + $"ABCD={result.Abcd:G9}, CDAB={result.Cdab:G9}, "
            + $"BADC={result.Badc:G9}, DCBA={result.Dcba:G9}, "
            + $"selected={selectedText}, matches={matchCount}.");
    }

    private static double NormalizeDisplayValue(double value)
    {
        return Math.Abs(value) < 0.005 ? 0 : value;
    }

    private static bool TryGetCalibrationCoil(string label, out ushort coilAddress)
    {
        switch (label)
        {
            case "0%校准":
                coilAddress = 28;
                return true;
            case "25%校准":
                coilAddress = 26;
                return true;
            case "50%校准":
                coilAddress = 24;
                return true;
            case "75%校准":
                coilAddress = 22;
                return true;
            case "100%校准":
                coilAddress = 20;
                return true;
            default:
                coilAddress = 0;
                return false;
        }
    }
}
