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 CValueCalibrationViewModel : PageViewModel
{
    private const ushort BaselineOxygenRegister = 268;
    private const ushort TemperatureRegister = 282;
    private const ushort PressureDifferenceRegister = 284;
    private const ushort CalibrationOxygenRegister = 286;
    private const ushort CValueRegister = 308;
    private const double OxygenMinimum = 0;
    private const double OxygenMaximum = 30;
    private const double TemperatureMinimum = 200;
    private const double TemperatureMaximum = 1500;
    private const double PressureDifferenceMinimum = -5000;
    private const double PressureDifferenceMaximum = 5000;
    private const double CValueMinimum = 0;
    private const double CValueMaximum = 1000;
    private const double BaselineOxygenDisplayScale = 100;
    private const ushort CirculatingWaterCoil = 49;
    private const ushort SamplingPumpCoil = 50;
    private const ushort IgniterCoil = 53;
    private const ushort FanCoil = 54;
    private const ushort MethaneValveCoil = 55;
    private const ushort BaselineCollectionCoil = 60;
    private const ushort BaselineEndCoil = 62;
    private const ushort CalibrationStartCoil = 70;
    private const ushort CalibrationEndCoil = 72;
    private static readonly TimeSpan PairedActionPulseDuration = TimeSpan.FromMilliseconds(300);
    private const string CirculatingWaterAction = "循环水";
    private const string SamplingPumpAction = "取样泵";
    private const string IgniterAction = "点火器";
    private const string FanAction = "风机";
    private const string MethaneValveAction = "甲烷阀";
    private const string BaselineCollectionAction = "基线采集";
    private const string CalibrationStartAction = "标定开始";

    private readonly Action _closeAction;
    private readonly Action _helpAction;
    private readonly Action _baselineCollectionCompletedAction;
    private readonly Action _calibrationCompletedAction;
    private readonly ITcpDeviceConnectionService _tcpDeviceConnectionService;
    private readonly DispatcherTimer _refreshTimer;
    private readonly DispatcherTimer _pairedActionCompletionTimer;
    private readonly Dictionary<string, CValueCalibrationActionViewModel> _actionsByLabel = [];
    private readonly Dictionary<string, PairedActionWaitState> _pairedActionWaitStates = [];
    private readonly HashSet<ushort> _loggedFloatDiagnostics = [];
    private readonly HashSet<ushort> _loggedInvalidFloatDiagnostics = [];
    private string _baselineOxygenText = "";
    private string _temperatureText = "";
    private string _pressureDifferenceText = "";
    private string _calibrationOxygenText = "";
    private string _cValueText = "";
    private string _lastAction = "待机";

    public CValueCalibrationViewModel(
        Action closeAction,
        Action helpAction,
        Action baselineCollectionCompletedAction,
        Action calibrationCompletedAction,
        ITcpDeviceConnectionService tcpDeviceConnectionService) : base("C值标定")
    {
        _closeAction = closeAction;
        _helpAction = helpAction;
        _baselineCollectionCompletedAction = baselineCollectionCompletedAction;
        _calibrationCompletedAction = calibrationCompletedAction;
        _tcpDeviceConnectionService = tcpDeviceConnectionService;
        CloseCommand = new RelayCommand(_closeAction);
        HelpCommand = new RelayCommand(_helpAction);
        ActionCommand = new RelayCommand<string>(ExecuteAction);

        TopActions =
        [
            CreateAction(CirculatingWaterAction, $"{CirculatingWaterAction}：关闭", $"{CirculatingWaterAction}：开启"),
            CreateAction(MethaneValveAction, $"{MethaneValveAction}：关闭", $"{MethaneValveAction}：开启"),
            CreateAction(FanAction, $"{FanAction}：关闭", $"{FanAction}：开启"),
            CreateAction(IgniterAction, $"{IgniterAction}：关闭", $"{IgniterAction}：开启"),
            CreateAction(SamplingPumpAction, $"{SamplingPumpAction}：关闭", $"{SamplingPumpAction}：开启")
        ];

        BottomActions =
        [
            CreateAction(CalibrationStartAction, CalibrationStartAction, "标定结束"),
            CreateAction(BaselineCollectionAction, BaselineCollectionAction, "基线结束")
        ];
        _actionsByLabel[CalibrationStartAction].UpdateStatus(false);
        _actionsByLabel[BaselineCollectionAction].UpdateStatus(false);
        _pairedActionWaitStates[CalibrationStartAction] = new PairedActionWaitState(
            CalibrationEndCoil,
            "标定中",
            "标定完成",
            _calibrationCompletedAction);
        _pairedActionWaitStates[BaselineCollectionAction] = new PairedActionWaitState(
            BaselineEndCoil,
            "基线采集中",
            "基线采集完成",
            _baselineCollectionCompletedAction);

        _pairedActionCompletionTimer = new DispatcherTimer
        {
            Interval = TimeSpan.FromMilliseconds(250)
        };
        _pairedActionCompletionTimer.Tick += (_, _) => RefreshPairedActionCompletions();

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

    public ObservableCollection<CValueCalibrationActionViewModel> TopActions { get; }

    public ObservableCollection<CValueCalibrationActionViewModel> BottomActions { get; }

    public IRelayCommand CloseCommand { get; }

    public IRelayCommand HelpCommand { get; }

    public IRelayCommand<string> ActionCommand { get; }

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

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

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

    public string HeatInputText { get; } = "5.0";

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

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

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

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

        if (IsPairedAction(action))
        {
            StartPairedAction(action);
            return;
        }

        LastAction = action;
        ToggleHoldAction(action);
    }

    private void RefreshDeviceValues()
    {
        BaselineOxygenText = ReadRangedFloatText(
            "BaselineOxygen",
            BaselineOxygenRegister,
            OxygenMinimum,
            OxygenMaximum / BaselineOxygenDisplayScale,
            "0.00",
            BaselineOxygenDisplayScale);
        TemperatureText = ReadRangedFloatText(
            "Temperature",
            TemperatureRegister,
            TemperatureMinimum,
            TemperatureMaximum,
            "0.00");
        PressureDifferenceText = ReadRangedFloatText(
            "PressureDifference",
            PressureDifferenceRegister,
            PressureDifferenceMinimum,
            PressureDifferenceMaximum,
            "0.00");
        CalibrationOxygenText = ReadRangedFloatText(
            "CalibrationOxygen",
            CalibrationOxygenRegister,
            OxygenMinimum,
            OxygenMaximum,
            "0.00");
        CValueText = ReadRangedFloatText(
            "CValue",
            CValueRegister,
            CValueMinimum,
            CValueMaximum,
            "0.00");
        RefreshHoldActionStates();
    }

    private string ReadRangedFloatText(
        string label,
        ushort registerAddress,
        double minimum,
        double maximum,
        string format,
        double displayScale = 1)
    {
        if (!_tcpDeviceConnectionService.TryReadFloatValues(registerAddress, out var result))
        {
            return string.Empty;
        }

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

        LogFloatDiagnostic(label, registerAddress, result, byteOrder, matchCount);
        return NormalizeDisplayValue(value * displayScale).ToString(format, CultureInfo.InvariantCulture);
    }

    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(
            $"C value calibration 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 CValueCalibrationActionViewModel CreateAction(
        string label,
        string inactiveDisplayText,
        string activeDisplayText)
    {
        var action = new CValueCalibrationActionViewModel(label, ActionCommand, inactiveDisplayText, activeDisplayText);
        _actionsByLabel[label] = action;
        return action;
    }

    private void RefreshHoldActionStates()
    {
        UpdateActionStatus(CirculatingWaterAction, CirculatingWaterCoil);
        UpdateActionStatus(MethaneValveAction, MethaneValveCoil);
        UpdateActionStatus(FanAction, FanCoil);
        UpdateActionStatus(IgniterAction, IgniterCoil);
        UpdateActionStatus(SamplingPumpAction, SamplingPumpCoil);
    }

    private bool UpdateActionStatus(string action, ushort coilAddress)
    {
        if (!_actionsByLabel.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 ToggleHoldAction(string action)
    {
        if (!TryGetHoldActionCoil(action, out var coilAddress))
        {
            return;
        }

        if (!_tcpDeviceConnectionService.TryReadCoil(coilAddress, out var isActive))
        {
            LastAction = $"{action}状态读取失败";
            Debug.WriteLine($"C value calibration action '{action}' state read failed.");
            return;
        }

        var nextValue = !isActive;
        if (_tcpDeviceConnectionService.TryWriteCoil(coilAddress, nextValue))
        {
            LastAction = $"{action}{(nextValue ? "开启" : "关闭")}";
            if (!UpdateActionStatus(action, coilAddress) && _actionsByLabel.TryGetValue(action, out var actionViewModel))
            {
                actionViewModel.UpdateStatus(nextValue);
            }

            return;
        }

        LastAction = $"{action}{(nextValue ? "开启" : "关闭")}失败";
        Debug.WriteLine($"C value calibration action '{action}' write failed.");
    }

    private void StartPairedAction(string action)
    {
        if (!TryGetPairedActionCoils(action, out var startCoilAddress, out var endCoilAddress, out var endActionText))
        {
            return;
        }

        if (!_actionsByLabel.TryGetValue(action, out var actionViewModel))
        {
            return;
        }

        if (actionViewModel.IsBusy)
        {
            return;
        }

        if (!_pairedActionWaitStates.TryGetValue(action, out var waitState))
        {
            return;
        }

        if (!TryStartCoilPulse(startCoilAddress, action))
        {
            LastAction = $"{action}失败";
            Debug.WriteLine($"C value calibration action '{action}' write failed.");
            return;
        }

        var completionIsAlreadyActive = _tcpDeviceConnectionService.TryReadCoil(endCoilAddress, out var isComplete) && isComplete;
        waitState.Begin(completionIsAlreadyActive);
        actionViewModel.BeginWaiting(waitState.WaitingText);
        _pairedActionCompletionTimer.Start();
        LastAction = waitState.WaitingText;

        if (completionIsAlreadyActive)
        {
            Debug.WriteLine(
                $"C value calibration completion coil {endCoilAddress} was already active when '{action}' started; "
                + "waiting for it to reset before accepting completion.");
        }
    }

    private void RefreshPairedActionCompletions()
    {
        var hasWaitingAction = false;

        foreach (var (action, waitState) in _pairedActionWaitStates)
        {
            if (!waitState.IsWaiting)
            {
                continue;
            }

            hasWaitingAction = true;

            if (!_actionsByLabel.TryGetValue(action, out var actionViewModel))
            {
                continue;
            }

            if (!_tcpDeviceConnectionService.TryReadCoil(waitState.CompletionCoilAddress, out var isComplete))
            {
                LastAction = $"{waitState.WaitingText}，完成状态读取失败";
                Debug.WriteLine(
                    $"C value calibration completion coil {waitState.CompletionCoilAddress} read failed for '{action}'.");
                continue;
            }

            if (!isComplete)
            {
                waitState.MarkCompletionInactive();
                continue;
            }

            if (waitState.RequiresInactiveBeforeCompletion && !waitState.HasSeenInactiveCompletionState)
            {
                continue;
            }

            CompletePairedAction(waitState, actionViewModel);
        }

        if (!hasWaitingAction)
        {
            _pairedActionCompletionTimer.Stop();
        }
    }

    private void CompletePairedAction(
        PairedActionWaitState waitState,
        CValueCalibrationActionViewModel actionViewModel)
    {
        LastAction = waitState.CompletedText;
        waitState.End();

        try
        {
            waitState.CompletedAction();
        }
        finally
        {
            actionViewModel.FinishWaiting();
        }
    }

    private bool TryStartCoilPulse(ushort coilAddress, string actionText)
    {
        if (!_tcpDeviceConnectionService.TryWriteCoil(coilAddress, true))
        {
            return false;
        }

        _ = ReleaseCoilAfterPulseAsync(coilAddress, actionText);
        return true;
    }

    private async Task ReleaseCoilAfterPulseAsync(ushort coilAddress, string actionText)
    {
        await Task.Delay(PairedActionPulseDuration);
        if (!_tcpDeviceConnectionService.TryWriteCoil(coilAddress, false))
        {
            Debug.WriteLine($"C value calibration action '{actionText}' pulse release failed.");
        }
    }

    private static bool TryGetHoldActionCoil(string action, out ushort coilAddress)
    {
        switch (action)
        {
            case CirculatingWaterAction:
                coilAddress = CirculatingWaterCoil;
                return true;
            case MethaneValveAction:
                coilAddress = MethaneValveCoil;
                return true;
            case FanAction:
                coilAddress = FanCoil;
                return true;
            case IgniterAction:
                coilAddress = IgniterCoil;
                return true;
            case SamplingPumpAction:
                coilAddress = SamplingPumpCoil;
                return true;
            default:
                coilAddress = 0;
                return false;
        }
    }

    private static bool TryGetPairedActionCoils(
        string action,
        out ushort startCoilAddress,
        out ushort endCoilAddress,
        out string endActionText)
    {
        switch (action)
        {
            case BaselineCollectionAction:
                startCoilAddress = BaselineCollectionCoil;
                endCoilAddress = BaselineEndCoil;
                endActionText = "基线结束";
                return true;
            case CalibrationStartAction:
                startCoilAddress = CalibrationStartCoil;
                endCoilAddress = CalibrationEndCoil;
                endActionText = "标定结束";
                return true;
            default:
                startCoilAddress = 0;
                endCoilAddress = 0;
                endActionText = string.Empty;
                return false;
        }
    }

    private static bool IsPairedAction(string action)
    {
        return action is BaselineCollectionAction or CalibrationStartAction;
    }

    private sealed class PairedActionWaitState(
        ushort completionCoilAddress,
        string waitingText,
        string completedText,
        Action completedAction)
    {
        public ushort CompletionCoilAddress { get; } = completionCoilAddress;

        public string WaitingText { get; } = waitingText;

        public string CompletedText { get; } = completedText;

        public Action CompletedAction { get; } = completedAction;

        public bool IsWaiting { get; private set; }

        public bool RequiresInactiveBeforeCompletion { get; private set; }

        public bool HasSeenInactiveCompletionState { get; private set; }

        public void Begin(bool completionIsAlreadyActive)
        {
            IsWaiting = true;
            RequiresInactiveBeforeCompletion = completionIsAlreadyActive;
            HasSeenInactiveCompletionState = !completionIsAlreadyActive;
        }

        public void MarkCompletionInactive()
        {
            HasSeenInactiveCompletionState = true;
        }

        public void End()
        {
            IsWaiting = false;
            RequiresInactiveBeforeCompletion = false;
            HasSeenInactiveCompletionState = false;
        }
    }
}
