using System.Collections.ObjectModel;
using System.Collections.Specialized;
using System.ComponentModel;
using System.Globalization;
using System.IO;
using System.Windows;
using CommunityToolkit.Mvvm.ComponentModel;
using CommunityToolkit.Mvvm.Input;
using ConeCalorimeter.Models;
using ConeCalorimeter.Services;
using Microsoft.Win32;
using Serilog;

namespace ConeCalorimeter.ViewModels;

public sealed class ReportPageViewModel : PageViewModel
{
    private const ushort CFactorRegister = 308;
    private const double CFactorMinimum = 0;
    private const double CFactorMaximum = 1000;
    private const double DisplayZeroTolerance = 0.00005;
    private const string FourDecimalDisplayFormat = "0.0000";

    private readonly IExperimentDataService _experimentDataService;
    private readonly IReportExportService _reportExportService;
    private readonly IReportInputPersistenceService _reportInputPersistenceService;
    private readonly ITcpDeviceConnectionService _tcpDeviceConnectionService;
    private ModbusFloatByteOrder? _cFactorByteOrder;
    private string _statusText = "报表信息可手动录入，内容会自动保存，导出时合并当前采集数据。";

    public ReportPageViewModel(
        IExperimentDataService experimentDataService,
        IReportExportService reportExportService,
        IReportInputPersistenceService reportInputPersistenceService,
        ITcpDeviceConnectionService tcpDeviceConnectionService) : base("报表")
    {
        _experimentDataService = experimentDataService;
        _reportExportService = reportExportService;
        _reportInputPersistenceService = reportInputPersistenceService;
        _tcpDeviceConnectionService = tcpDeviceConnectionService;

        Sections = CreateSections();
        LoadPersistedReportInput();
        AttachReportFieldPersistence();
        SummaryItems =
        [
            new ReportSummaryItemViewModel("记录数"),
            new ReportSummaryItemViewModel("起始时间"),
            new ReportSummaryItemViewModel("结束时间"),
            new ReportSummaryItemViewModel("最大热释放"),
            new ReportSummaryItemViewModel("总热释放"),
            new ReportSummaryItemViewModel("总产烟量"),
            new ReportSummaryItemViewModel("质量损失")
        ];

        ExportCommand = new RelayCommand(ExportReport);
        _experimentDataService.Records.CollectionChanged += RecordsChanged;
        _experimentDataService.HeatReleaseCorrectionStatusChanged += (_, _) => HeatReleaseCorrectionStatusChanged();
        UpdateSummary();
    }

    public ObservableCollection<ReportSectionViewModel> Sections { get; }

    public ObservableCollection<ReportSummaryItemViewModel> SummaryItems { get; }

    public IRelayCommand ExportCommand { get; }

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

    private static ObservableCollection<ReportSectionViewModel> CreateSections()
    {
        return
        [
            new ReportSectionViewModel("报告信息",
            [
                new ReportFieldViewModel("LaboratoryName", "实验室名称"),
                new ReportFieldViewModel("Operator", "实验员"),
                new ReportFieldViewModel("FileName", "文件名"),
                new ReportFieldViewModel("ReportName", "报告名")
            ]),
            new ReportSectionViewModel("样品信息",
            [
                new ReportFieldViewModel("SampleDescription", "样品描述"),
                new ReportFieldViewModel("Material", "材料"),
                new ReportFieldViewModel("SampleName", "样品"),
                new ReportFieldViewModel("Thickness", "厚度"),
                new ReportFieldViewModel("InitialMass", "初始质量"),
                new ReportFieldViewModel("Manufacturer", "制造商")
            ]),
            new ReportSectionViewModel("测试条件",
            [
                new ReportFieldViewModel("IrradiatedArea", "辐射面积"),
                new ReportFieldViewModel("Irradiance", "热辐射值"),
                new ReportFieldViewModel("IrradianceDistance", "辐射距离"),
                new ReportFieldViewModel("SampleOrientation", "试样方向"),
                new ReportFieldViewModel("SampleCount", "试样数目"),
                new ReportFieldViewModel("RequiredExhaustFlow", "要求烟气流量"),
                new ReportFieldViewModel("FrameSelected", "边框选用?"),
                new ReportFieldViewModel("GridSelected", "栅格选用?"),
                new ReportFieldViewModel("SubstrateSelected", "是否选用基材?"),
                new ReportFieldViewModel("AmbientTemperature", "环境温度"),
                new ReportFieldViewModel("AmbientHumidity", "环境湿度"),
                new ReportFieldViewModel("TestCondition", "测试条件"),
                new ReportFieldViewModel("TestStandard", "符合标准"),
                new ReportFieldViewModel("TestDate", "测试日期", DateTime.Today.ToString("yyyy-MM-dd", CultureInfo.CurrentCulture)),
                new ReportFieldViewModel("ForecastCondition", "预测条件")
            ]),
            new ReportSectionViewModel("初始与结束条件",
            [
                new ReportFieldViewModel("AtmosphericTemperature", "大气温度"),
                new ReportFieldViewModel("AtmosphericPressure", "大气压力"),
                new ReportFieldViewModel("AtmosphericHumidity", "大气湿度"),
                new ReportFieldViewModel("TimeRecord", "时间记录"),
                new ReportFieldViewModel("IgnitionTime", "点燃时间"),
                new ReportFieldViewModel("FlameoutTime", "熄灭时间"),
                new ReportFieldViewModel("EndCriteria", "结束标准"),
                new ReportFieldViewModel("EndTime", "结束时间")
            ]),
            new ReportSectionViewModel("设备参数",
            [
                new ReportFieldViewModel("EquivalentHeatValue", "E等价热值"),
                new ReportFieldViewModel("CFactor", "C-系数"),
                new ReportFieldViewModel("LightPath", "光程"),
                new ReportFieldViewModel("O2DelayTime", "O2延迟时间"),
                new ReportFieldViewModel("CO2DelayTime", "CO2延迟时间"),
                new ReportFieldViewModel("CODelayTime", "CO延迟时间"),
                new ReportFieldViewModel("OdCorrectionFactor", "OD矫正系数"),
                new ReportFieldViewModel("BaselineAtmosphericOxygen", "基线大气压氧含量"),
                new ReportFieldViewModel("BaselineOxygen", "基线氧含量"),
                new ReportFieldViewModel("BaselineC2Oxygen", "基线C2氧含量"),
                new ReportFieldViewModel("BaselineCO2Oxygen", "基线CO2氧含量")
            ])
        ];
    }

    private void RecordsChanged(object? sender, NotifyCollectionChangedEventArgs e)
    {
        UpdateSummary();
    }

    private void UpdateSummary()
    {
        var records = _experimentDataService.Records;
        SummaryItems[0].Value = records.Count.ToString(CultureInfo.CurrentCulture);

        if (records.Count == 0)
        {
            for (var i = 1; i < SummaryItems.Count; i++)
            {
                SummaryItems[i].Value = "-";
            }

            return;
        }

        SummaryItems[1].Value = records.First().Timestamp.ToString("yyyy-MM-dd HH:mm:ss", CultureInfo.CurrentCulture);
        SummaryItems[2].Value = records.Last().Timestamp.ToString("yyyy-MM-dd HH:mm:ss", CultureInfo.CurrentCulture);
        var last = records.Last();
        SummaryItems[3].Value = FormatWithUnit(
            records.Where(record => double.IsFinite(record.HeatReleaseRate))
                .Select(record => record.HeatReleaseRate)
                .DefaultIfEmpty(double.NaN)
                .Max(),
            "kW/㎡");
        SummaryItems[4].Value = FormatWithUnit(LastFinite(records, record => record.TotalHeatRelease), "MJ/㎡");
        SummaryItems[5].Value = FormatWithUnit(LastFinite(records, record => record.TotalSmoke), "m²");
        SummaryItems[6].Value = FormatWithUnit(LastFinite(records, record => record.MassLoss), "g");
        FillCollectedReportFields(records);
    }

    private void HeatReleaseCorrectionStatusChanged()
    {
        UpdateSummary();
        StatusText = _experimentDataService.HeatReleaseCorrectionStatus;
    }

    private void ExportReport()
    {
        var records = _experimentDataService.Records.ToList();
        if (!records.Any(IsValidExperimentRecord))
        {
            StatusText = "请先点击“测试开始”，产生有效实验数据后再导出报表";
            Log.Warning("Report export blocked because no valid experiment records were available. Records={RecordCount}.", records.Count);
            return;
        }

        if (!_experimentDataService.IsHeatReleaseCorrectionApplied)
        {
            StatusText = $"无法导出正式报表：{_experimentDataService.HeatReleaseCorrectionStatus}";
            Log.Warning(
                "Formal report export blocked because heat release correction was not applied. Status={Status}.",
                _experimentDataService.HeatReleaseCorrectionStatus);
            MessageBox.Show(StatusText, "导出报表", MessageBoxButton.OK, MessageBoxImage.Warning);
            return;
        }

        RefreshCurrentCValueReportField(overwriteManual: false);
        var input = BuildInput();
        var defaultName = BuildDefaultFileName(input);
        var dialog = new SaveFileDialog
        {
            Title = "导出报表",
            Filter = "Excel 97-2003 工作簿 (*.xls)|*.xls",
            FileName = defaultName,
            DefaultExt = ".xls",
            AddExtension = true,
            OverwritePrompt = true
        };

        if (dialog.ShowDialog() != true)
        {
            StatusText = "已取消导出";
            return;
        }

        try
        {
            _reportExportService.Export(dialog.FileName, input, records);
            StatusText = $"已导出：{dialog.FileName}";
            Log.Information(
                "Report export succeeded. Path={Path}, Records={RecordCount}, SampleName={SampleName}.",
                dialog.FileName,
                records.Count,
                input.SampleName);
        }
        catch (Exception ex)
        {
            StatusText = $"导出失败：{ex.Message}";
            Log.Error(ex, "Report export failed. Path={Path}, Records={RecordCount}.", dialog.FileName, records.Count);
            MessageBox.Show(StatusText, "导出报表", MessageBoxButton.OK, MessageBoxImage.Error);
        }
    }

    private ReportInput BuildInput()
    {
        var values = Sections
            .SelectMany(section => section.Fields)
            .ToDictionary(field => field.Key, field => field.Value.Trim());

        var input = new ReportInput();
        foreach (var property in typeof(ReportInput).GetProperties())
        {
            if (!values.TryGetValue(property.Name, out var value))
            {
                continue;
            }

            if (property.PropertyType == typeof(DateTime))
            {
                if (DateTime.TryParse(value, CultureInfo.CurrentCulture, DateTimeStyles.None, out var parsed))
                {
                    property.SetValue(input, parsed);
                }
            }
            else
            {
                property.SetValue(input, value);
            }
        }

        return input;
    }

    private void LoadPersistedReportInput()
    {
        var persistedFields = _reportInputPersistenceService.Load();
        if (persistedFields.Count == 0)
        {
            return;
        }

        foreach (var field in Sections.SelectMany(section => section.Fields))
        {
            if (!persistedFields.TryGetValue(field.Key, out var state))
            {
                continue;
            }

            field.SetPersistedValue(state.Value ?? string.Empty, state.IsAutoFilled);
        }
    }

    private void AttachReportFieldPersistence()
    {
        foreach (var field in Sections.SelectMany(section => section.Fields))
        {
            field.PropertyChanged += ReportFieldPropertyChanged;
        }
    }

    private void ReportFieldPropertyChanged(object? sender, PropertyChangedEventArgs e)
    {
        if (e.PropertyName != nameof(ReportFieldViewModel.Value)
            && e.PropertyName != nameof(ReportFieldViewModel.IsAutoFilled))
        {
            return;
        }

        SaveReportInput();
    }

    private void SaveReportInput()
    {
        try
        {
            var state = Sections
                .SelectMany(section => section.Fields)
                .ToDictionary(
                    field => field.Key,
                    field => new ReportManualEntryFieldState
                    {
                        Value = field.Value,
                        IsAutoFilled = field.IsAutoFilled
                    });

            _reportInputPersistenceService.Save(state);
        }
        catch (Exception ex)
        {
            StatusText = $"报表录入保存失败：{ex.Message}";
            Log.Error(ex, "Report manual input JSON save failed. Path={Path}.", _reportInputPersistenceService.StoragePath);
        }
    }

    private static string BuildDefaultFileName(ReportInput input)
    {
        var name = FirstNonEmpty(input.ReportName, input.FileName, input.SampleName, "锥形量热仪测试报告");
        foreach (var invalid in Path.GetInvalidFileNameChars())
        {
            name = name.Replace(invalid, '_');
        }

        return name.EndsWith(".xls", StringComparison.OrdinalIgnoreCase) ? name : $"{name}.xls";
    }

    private static string FirstNonEmpty(params string[] values)
    {
        return values.FirstOrDefault(value => !string.IsNullOrWhiteSpace(value)) ?? string.Empty;
    }

    private static string FormatWithUnit(double value, string unit)
    {
        return double.IsFinite(value) ? $"{value:0.00} {unit}" : "-";
    }

    private void FillCollectedReportFields(IReadOnlyList<RealtimeDataRecord> records)
    {
        FindField("InitialMass")?.SetAutoValue(FormatWithUnit(LastFinite(records, record => record.InitialMass), "g"));
        FindField("CFactor")?.SetAutoValue(FormatValue(LastFinite(records, record => record.CFactor)));
        FindField("IgnitionTime")?.SetAutoValue(FormatSeconds(LastNonNegative(records, record => record.IgnitionSeconds)));
        FindField("EndTime")?.SetAutoValue(FormatSeconds(LastNonNegative(records, record => record.TestSeconds)));
    }

    private void RefreshCurrentCValueReportField(bool overwriteManual)
    {
        if (TryReadCurrentCValueText(out var value))
        {
            FindField("CFactor")?.SetAutoValue(value, overwriteManual);
        }
    }

    private bool TryReadCurrentCValueText(out string value)
    {
        value = string.Empty;
        if (!_tcpDeviceConnectionService.TryReadFloatValues(CFactorRegister, out var result))
        {
            Log.Warning(
                "Report C factor D{RegisterAddress} read failed before export. ConnectionStatus={ConnectionStatus}.",
                CFactorRegister,
                _tcpDeviceConnectionService.StatusText);
            return false;
        }

        if (!ModbusFloatSelector.TrySelectRangedFloat(
                result,
                CFactorMinimum,
                CFactorMaximum,
                out var byteOrder,
                out var cFactor,
                out var matchCount,
                _cFactorByteOrder))
        {
            Log.Warning(
                "Report C factor D{RegisterAddress} has no valid decoded value. Raw={RawHex}, Matches={MatchCount}.",
                CFactorRegister,
                result.RawHex,
                matchCount);
            return false;
        }

        _cFactorByteOrder = byteOrder;
        value = FormatValue(NormalizeDisplayValue(cFactor));
        return true;
    }

    private ReportFieldViewModel? FindField(string key)
    {
        return Sections.SelectMany(section => section.Fields)
            .FirstOrDefault(field => field.Key == key);
    }

    private static double LastFinite(IReadOnlyList<RealtimeDataRecord> records, Func<RealtimeDataRecord, double> selector)
    {
        for (var i = records.Count - 1; i >= 0; i--)
        {
            var value = selector(records[i]);
            if (double.IsFinite(value))
            {
                return value;
            }
        }

        return double.NaN;
    }

    private static int? LastNonNegative(IReadOnlyList<RealtimeDataRecord> records, Func<RealtimeDataRecord, int> selector)
    {
        for (var i = records.Count - 1; i >= 0; i--)
        {
            var value = selector(records[i]);
            if (value >= 0)
            {
                return value;
            }
        }

        return null;
    }

    private static string FormatSeconds(int? value)
    {
        return value.HasValue ? $"{value.Value} s" : string.Empty;
    }

    private static string FormatValue(double value)
    {
        return double.IsFinite(value)
            ? value.ToString(FourDecimalDisplayFormat, CultureInfo.InvariantCulture)
            : string.Empty;
    }

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

    private static bool IsValidExperimentRecord(RealtimeDataRecord record)
    {
        return record.TestSeconds >= 0
            && double.IsFinite(record.TotalHeatRelease)
            && double.IsFinite(record.TotalSmoke);
    }
}
