using System.Net;
using System.Net.Http.Json;
using System.Security.Cryptography;
using System.Text;
using System.Text.Json;
using System.Text.Json.Serialization;
using AntdUI;
using WinLabel = System.Windows.Forms.Label;
using WinPanel = System.Windows.Forms.Panel;
using WinTextBox = System.Windows.Forms.TextBox;

namespace Vmianqian
{
    public partial class Form1 : BorderlessForm
    {
        private readonly string _configFilePath = Path.Combine(AppContext.BaseDirectory, "appsettings.client.json");
        private readonly JsonSerializerOptions _jsonOptions = new() { WriteIndented = true, PropertyNameCaseInsensitive = true };
        private readonly HttpClient _httpClient = new();
        private readonly System.Windows.Forms.Timer _runtimeTimer = new();
        private readonly System.Windows.Forms.Timer _heartbeatTimer = new();

        private DateTime _appStartTime = DateTime.Now;
        private HttpListener? _httpListener;
        private CancellationTokenSource? _listenerCancellationTokenSource;
        private ClientConfig _config = new();
        private bool _isSynchronizingNavigation;

        private AntdUI.PageHeader titlebar = null!;
        private AntdUI.PageHeader bottomBar = null!;
        private AntdUI.Menu menu = null!;
        private WinPanel contentHost = null!;

        private AntdUI.Button buttonCollapse = null!;
        private WinLabel lblRuntimeBottom = null!;

        private string _currentPageKey = "home";

        private WinPanel pageHome = null!;
        private WinPanel pageWechat = null!;
        private WinPanel pageAlipay = null!;
        private WinPanel pageSettings = null!;

        private AntdUI.Label lblTopNotice = null!;
        private AntdUI.Label lblWechatStatusValue = null!;
        private AntdUI.Label lblAlipayStatusValue = null!;

        private AntdUI.Input txtServerUrl = null!;
        private AntdUI.Input txtApiKey = null!;
        private AntdUI.Input txtNotifyEmail = null!;
        private AntdUI.Switch chkHeartbeatEnabled = null!;
        private AntdUI.Button btnHeartbeatCheck = null!;
        private WinTextBox txtLog = null!;

        private AntdUI.Input txtWechatPath = null!;
        private AntdUI.Input txtWechatId = null!;
        private NumericUpDown numWechatInterval = null!;
        private AntdUI.Checkbox chkWheel = null!;

        private AntdUI.Input txtAliPath = null!;
        private AntdUI.Input txtAliAppId = null!;
        private AntdUI.Input txtAliPid = null!;
        private NumericUpDown numAlipayInterval = null!;

        private AntdUI.Input txtServicePort = null!;
        private AntdUI.Input txtListenPath = null!;

        private DataGridView gridWechatLogs = null!;
        private DataGridView gridAlipayLogs = null!;

        private AntdUI.Button btnSaveConfig = null!;
        private AntdUI.Button btnStartService = null!;
        private AntdUI.Button btnStopService = null!;

        public Form1()
        {
            InitializeComponent();
            BuildAntdDemoStyleUi();
            Load += Form1_Load;
            FormClosing += Form1_FormClosing;
        }

        private void BuildAntdDemoStyleUi()
        {
            BackColor = Color.White;
            Font = new Font("Microsoft YaHei UI", 9F);
            Text = "V免签 Demo";

            BuildTitlebar();
            BuildBottomBar();
            BuildMenu();
            BuildContentHost();

            Controls.Add(contentHost);
            Controls.Add(menu);
            Controls.Add(bottomBar);
            Controls.Add(titlebar);
        }

        private void BuildTitlebar()
        {
            titlebar = new AntdUI.PageHeader
            {
                Dock = DockStyle.Top,
                Height = 44,
                DividerShow = true,
                ShowButton = true,
                ShowIcon = true,
                Text = "AntdUI",
                SubText = "Demo"
            };

            lblTopNotice = new AntdUI.Label
            {
                Dock = DockStyle.Right,
                Width = 260,
                Text = "准备接入 V免签服务端通信",
                TextAlign = ContentAlignment.MiddleRight,
                ForeColor = Color.FromArgb(22, 119, 255)
            };

            lblAlipayStatusValue = new AntdUI.Label
            {
                Dock = DockStyle.Right,
                Width = 88,
                Text = "支付宝: 离线",
                TextAlign = ContentAlignment.MiddleRight,
                ForeColor = Color.Red
            };

            lblWechatStatusValue = new AntdUI.Label
            {
                Dock = DockStyle.Right,
                Width = 76,
                Text = "微信: 离线",
                TextAlign = ContentAlignment.MiddleRight,
                ForeColor = Color.Red
            };

            titlebar.Controls.Add(lblTopNotice);
            titlebar.Controls.Add(lblAlipayStatusValue);
            titlebar.Controls.Add(lblWechatStatusValue);
        }

        private void BuildBottomBar()
        {
            bottomBar = new AntdUI.PageHeader
            {
                Dock = DockStyle.Bottom,
                Height = 40,
                DividerShow = true,
                UseLeftMargin = false
            };

            buttonCollapse = new AntdUI.Button
            {
                Dock = DockStyle.Left,
                Width = 50,
                Ghost = true,
                Radius = 0,
                WaveSize = 0,
                IconRatio = 0.6F,
                IconSvg = "MenuUnfoldOutlined",
                ToggleIconSvg = "MenuFoldOutlined",
                Toggle = true
            };
            buttonCollapse.Click += ButtonCollapse_Click;

            lblRuntimeBottom = new WinLabel
            {
                Dock = DockStyle.Right,
                Width = 220,
                TextAlign = ContentAlignment.MiddleRight,
                ForeColor = Color.Gray,
                Text = "00天:00时:00分:00秒"
            };

            bottomBar.Controls.Add(lblRuntimeBottom);
            bottomBar.Controls.Add(buttonCollapse);
        }

        private void BuildMenu()
        {
            menu = new AntdUI.Menu
            {
                Dock = DockStyle.Left,
                Width = 220,
                Collapsed = false,
                Unique = true,
                IconRatio = 1F,
                Indent = true,
                Round = false,
                Radius = 0,
                Font = new Font("Microsoft YaHei UI", 9F)
            };
            menu.SelectChanged += Menu_SelectChanged;
            ReloadMenuItems();
        }

        private void ReloadMenuItems()
        {
            menu.Items.Clear();
            menu.Items.Add(new AntdUI.MenuItem { Text = "首页", IconSvg = "HomeOutlined", Tag = "home" });
            menu.Items.Add(new AntdUI.MenuItem { Text = "微信监控", IconSvg = "WechatOutlined", Tag = "wechat" });
            menu.Items.Add(new AntdUI.MenuItem { Text = "支付宝监控", IconSvg = "AlipayCircleOutlined", Tag = "alipay" });
            menu.Items.Add(new AntdUI.MenuItem { Text = "软件设置", IconSvg = "SettingOutlined", Tag = "settings" });
        }

        private void BuildContentHost()
        {
            contentHost = new WinPanel
            {
                Dock = DockStyle.Fill,
                BackColor = Color.White
            };

            pageHome = CreatePagePanel();
            pageWechat = CreatePagePanel();
            pageAlipay = CreatePagePanel();
            pageSettings = CreatePagePanel();

            contentHost.Controls.Add(pageSettings);
            contentHost.Controls.Add(pageAlipay);
            contentHost.Controls.Add(pageWechat);
            contentHost.Controls.Add(pageHome);

            BuildHomePage();
            BuildWechatPage();
            BuildAlipayPage();
            BuildSettingsPage();

            pageHome.Resize += (_, _) => LayoutHomePage();
            pageWechat.Resize += (_, _) => LayoutWechatPage();
            pageAlipay.Resize += (_, _) => LayoutAlipayPage();
            pageSettings.Resize += (_, _) => LayoutSettingsPage();

            LayoutHomePage();
            LayoutWechatPage();
            LayoutAlipayPage();
            LayoutSettingsPage();
        }

        private WinPanel CreatePagePanel()
        {
            return new WinPanel
            {
                Dock = DockStyle.Fill,
                BackColor = Color.FromArgb(245, 247, 250),
                AutoScroll = true
            };
        }

        private void LayoutHomePage()
        {
            LayoutPageCards(pageHome, 20);

            var logCard = FindCard(pageHome, "home-log");
            if (logCard != null)
            {
                var bottom = pageHome.ClientSize.Height - 20;
                logCard.Height = Math.Max(220, bottom - logCard.Top);

                if (txtLog != null)
                {
                    txtLog.Size = new Size(Math.Max(240, logCard.ClientSize.Width - 50), Math.Max(120, logCard.ClientSize.Height - 76));
                }
            }
        }

        private void LayoutWechatPage()
        {
            LayoutPageCards(pageWechat, 20);

            var gridCard = FindCard(pageWechat, "wechat-log");
            if (gridCard != null)
            {
                var bottom = pageWechat.ClientSize.Height - 20;
                gridCard.Height = Math.Max(260, bottom - gridCard.Top);

                if (gridWechatLogs != null)
                {
                    gridWechatLogs.Size = new Size(Math.Max(240, gridCard.ClientSize.Width - 50), Math.Max(120, gridCard.ClientSize.Height - 76));
                }
            }
        }

        private void LayoutAlipayPage()
        {
            LayoutPageCards(pageAlipay, 20);

            var gridCard = FindCard(pageAlipay, "alipay-log");
            if (gridCard != null)
            {
                var bottom = pageAlipay.ClientSize.Height - 20;
                gridCard.Height = Math.Max(260, bottom - gridCard.Top);

                if (gridAlipayLogs != null)
                {
                    gridAlipayLogs.Size = new Size(Math.Max(240, gridCard.ClientSize.Width - 50), Math.Max(120, gridCard.ClientSize.Height - 76));
                }
            }
        }

        private void LayoutSettingsPage()
        {
            LayoutPageCards(pageSettings, 20);
        }

        private static void LayoutPageCards(WinPanel page, int margin)
        {
            var targetWidth = Math.Max(320, page.ClientSize.Width - margin * 2);

            foreach (Control control in page.Controls)
            {
                control.Left = margin;
                control.Width = targetWidth;
            }
        }

        private static Control? FindCard(Control parent, string tag)
        {
            foreach (Control control in parent.Controls)
            {
                if (string.Equals(control.Tag?.ToString(), tag, StringComparison.Ordinal))
                {
                    return control;
                }
            }

            return null;
        }

        private void BuildHomePage()
        {
            var summaryCard = CreateCardPanel(new Rectangle(20, 20, 1080, 120));
            summaryCard.Tag = "home-summary";
            summaryCard.Anchor = AnchorStyles.Top | AnchorStyles.Left | AnchorStyles.Right;

            var summaryTitle = new AntdUI.Label
            {
                Text = "欢迎使用 V免签 PC 监听客户端",
                Font = new Font("Microsoft YaHei UI", 22F, FontStyle.Regular),
                AutoSize = true,
                Location = new Point(28, 24)
            };

            var summaryDesc = new AntdUI.Label
            {
                Text = "当前阶段先打通“PC端本地监听 -> V免签服务端回调 -> 心跳检测”链路，整体布局按 AntdUI Demo 风格复刻。",
                AutoSize = false,
                Location = new Point(30, 64),
                Size = new Size(980, 28),
                ForeColor = Color.DimGray
            };

            summaryCard.Controls.Add(summaryTitle);
            summaryCard.Controls.Add(summaryDesc);

            var configCard = CreateCardPanel(new Rectangle(20, 160, 1080, 250));
            configCard.Tag = "home-config";
            configCard.Anchor = AnchorStyles.Top | AnchorStyles.Left | AnchorStyles.Right;

            configCard.Controls.Add(CreateTitleLabel("V免签回调地址", 24, 20));
            txtServerUrl = CreateInput(24, 48, 470, "例如：https://你的域名/pay/notify");
            configCard.Controls.Add(txtServerUrl);

            configCard.Controls.Add(CreateTitleLabel("通信密钥 / Token", 530, 20));
            txtApiKey = CreateInput(530, 48, 350, "请输入与服务端约定的密钥");
            configCard.Controls.Add(txtApiKey);

            configCard.Controls.Add(CreateTitleLabel("通知邮箱", 24, 102));
            txtNotifyEmail = CreateInput(24, 130, 260, "可选");
            configCard.Controls.Add(txtNotifyEmail);

            btnSaveConfig = new AntdUI.Button
            {
                Text = "保存配置",
                Location = new Point(530, 128),
                Size = new Size(110, 36),
            };
            btnSaveConfig.Click += btnSaveConfig_Click;

            btnStartService = new AntdUI.Button
            {
                Text = "启动监听",
                Location = new Point(660, 128),
                Size = new Size(110, 36),
            };
            btnStartService.Click += btnStart_Click;

            btnStopService = new AntdUI.Button
            {
                Text = "停止监听",
                Location = new Point(790, 128),
                Size = new Size(110, 36),
            };
            btnStopService.Click += btnStop_Click;

            configCard.Controls.Add(btnSaveConfig);
            configCard.Controls.Add(btnStartService);
            configCard.Controls.Add(btnStopService);

            configCard.Controls.Add(CreateTitleLabel("心跳检测", 24, 188));
            chkHeartbeatEnabled = new AntdUI.Switch
            {
                Location = new Point(24, 214),
                Size = new Size(62, 28),
                AutoSize = false
            };
            chkHeartbeatEnabled.CheckedChanged += chkHeartbeatEnabled_CheckedChanged;

            var heartbeatDesc = new AntdUI.Label
            {
                Text = "开启后后台每 30 秒自动检测一次，自动成功不写日志，失败才写。",
                AutoSize = true,
                Location = new Point(104, 219),
                ForeColor = Color.DimGray
            };

            btnHeartbeatCheck = new AntdUI.Button
            {
                Text = "立即检测",
                Location = new Point(530, 210),
                Size = new Size(110, 34)
            };
            btnHeartbeatCheck.Click += btnHeartbeatCheck_Click;

            configCard.Controls.Add(chkHeartbeatEnabled);
            configCard.Controls.Add(heartbeatDesc);
            configCard.Controls.Add(btnHeartbeatCheck);

            var logCard = CreateCardPanel(new Rectangle(20, 430, 1080, 300));
            logCard.Tag = "home-log";
            logCard.Anchor = AnchorStyles.Top | AnchorStyles.Bottom | AnchorStyles.Left | AnchorStyles.Right;

            logCard.Controls.Add(CreateTitleLabel("运行日志", 24, 18));

            txtLog = new WinTextBox
            {
                Location = new Point(24, 50),
                Size = new Size(1030, 224),
                Anchor = AnchorStyles.Top | AnchorStyles.Bottom | AnchorStyles.Left | AnchorStyles.Right,
                Multiline = true,
                ScrollBars = ScrollBars.Vertical,
                ReadOnly = true,
                BackColor = Color.Black,
                ForeColor = Color.Lime,
                BorderStyle = BorderStyle.FixedSingle
            };
            logCard.Controls.Add(txtLog);

            pageHome.Controls.Add(summaryCard);
            pageHome.Controls.Add(configCard);
            pageHome.Controls.Add(logCard);
        }

        private void BuildWechatPage()
        {
            var card = CreateCardPanel(new Rectangle(20, 20, 1080, 150));
            card.Tag = "wechat-config";
            card.Anchor = AnchorStyles.Top | AnchorStyles.Left | AnchorStyles.Right;

            card.Controls.Add(CreateTitleLabel("微信安装路径", 24, 20));
            txtWechatPath = CreateInput(24, 48, 390, "微信.exe 路径");
            card.Controls.Add(txtWechatPath);

            card.Controls.Add(CreateTitleLabel("微信 SID", 440, 20));
            txtWechatId = CreateInput(440, 48, 220, "可选");
            card.Controls.Add(txtWechatId);

            card.Controls.Add(CreateTitleLabel("轮询频率（秒）", 690, 20));
            numWechatInterval = new NumericUpDown
            {
                Location = new Point(690, 51),
                Size = new Size(96, 23),
                Minimum = 1,
                Maximum = 3600,
                Value = 5
            };
            card.Controls.Add(numWechatInterval);

            chkWheel = new AntdUI.Checkbox
            {
                Text = "启用轮询",
                Location = new Point(820, 50),
                AutoSize = true
            };
            card.Controls.Add(chkWheel);

            var desc = new AntdUI.Label
            {
                Text = "后续这里接入微信真实到账监听逻辑，目前保留参数配置与回调结果展示。",
                AutoSize = false,
                Location = new Point(24, 102),
                Size = new Size(960, 24),
                ForeColor = Color.DimGray
            };
            card.Controls.Add(desc);

            var gridCard = CreateCardPanel(new Rectangle(20, 190, 1080, 540));
            gridCard.Tag = "wechat-log";
            gridCard.Anchor = AnchorStyles.Top | AnchorStyles.Bottom | AnchorStyles.Left | AnchorStyles.Right;
            gridCard.Controls.Add(CreateTitleLabel("微信监听记录", 24, 18));

            gridWechatLogs = CreateWechatGrid();
            gridWechatLogs.Location = new Point(24, 50);
            gridWechatLogs.Size = new Size(1030, 464);
            gridWechatLogs.Anchor = AnchorStyles.Top | AnchorStyles.Bottom | AnchorStyles.Left | AnchorStyles.Right;
            gridCard.Controls.Add(gridWechatLogs);

            pageWechat.Controls.Add(card);
            pageWechat.Controls.Add(gridCard);
        }

        private void BuildAlipayPage()
        {
            var card = CreateCardPanel(new Rectangle(20, 20, 1080, 150));
            card.Tag = "alipay-config";
            card.Anchor = AnchorStyles.Top | AnchorStyles.Left | AnchorStyles.Right;

            card.Controls.Add(CreateTitleLabel("旺旺/支付宝路径", 24, 20));
            txtAliPath = CreateInput(24, 48, 300, "程序路径");
            card.Controls.Add(txtAliPath);

            card.Controls.Add(CreateTitleLabel("应用 ID", 350, 20));
            txtAliAppId = CreateInput(350, 48, 170, "AppId");
            card.Controls.Add(txtAliAppId);

            card.Controls.Add(CreateTitleLabel("用户 ID", 550, 20));
            txtAliPid = CreateInput(550, 48, 170, "Pid/UserId");
            card.Controls.Add(txtAliPid);

            card.Controls.Add(CreateTitleLabel("轮询频率（秒）", 750, 20));
            numAlipayInterval = new NumericUpDown
            {
                Location = new Point(750, 51),
                Size = new Size(96, 23),
                Minimum = 1,
                Maximum = 3600,
                Value = 5
            };
            card.Controls.Add(numAlipayInterval);

            var desc = new AntdUI.Label
            {
                Text = "后续这里接入支付宝真实到账监听逻辑，目前保留参数配置与回调结果展示。",
                AutoSize = false,
                Location = new Point(24, 102),
                Size = new Size(960, 24),
                ForeColor = Color.DimGray
            };
            card.Controls.Add(desc);

            var gridCard = CreateCardPanel(new Rectangle(20, 190, 1080, 540));
            gridCard.Tag = "alipay-log";
            gridCard.Anchor = AnchorStyles.Top | AnchorStyles.Bottom | AnchorStyles.Left | AnchorStyles.Right;
            gridCard.Controls.Add(CreateTitleLabel("支付宝监听记录", 24, 18));

            gridAlipayLogs = CreateAlipayGrid();
            gridAlipayLogs.Location = new Point(24, 50);
            gridAlipayLogs.Size = new Size(1030, 464);
            gridAlipayLogs.Anchor = AnchorStyles.Top | AnchorStyles.Bottom | AnchorStyles.Left | AnchorStyles.Right;
            gridCard.Controls.Add(gridAlipayLogs);

            pageAlipay.Controls.Add(card);
            pageAlipay.Controls.Add(gridCard);
        }

        private void BuildSettingsPage()
        {
            var card = CreateCardPanel(new Rectangle(20, 20, 1080, 260));
            card.Tag = "settings-main";
            card.Anchor = AnchorStyles.Top | AnchorStyles.Left | AnchorStyles.Right;

            card.Controls.Add(CreateTitleLabel("本地监听端口", 24, 20));
            txtServicePort = CreateInput(24, 48, 180, "8989");
            card.Controls.Add(txtServicePort);

            card.Controls.Add(CreateTitleLabel("本地监听路径", 240, 20));
            txtListenPath = CreateInput(240, 48, 240, "/notify/");
            card.Controls.Add(txtListenPath);

            var info = new AntdUI.Label
            {
                Text = "这个页面用于维护本地监听设置与项目说明。\r\n\r\n目标顺序：\r\n1. 先让本地监听和服务端回调稳定运行\r\n2. 再接入微信 PC 端监听\r\n3. 再接入支付宝 PC 端监听",
                AutoSize = false,
                Location = new Point(24, 105),
                Size = new Size(920, 120),
                ForeColor = Color.DimGray
            };
            card.Controls.Add(info);

            pageSettings.Controls.Add(card);
        }

        private WinPanel CreateCardPanel(Rectangle bounds)
        {
            return new WinPanel
            {
                Bounds = bounds,
                BackColor = Color.White,
                BorderStyle = BorderStyle.FixedSingle
            };
        }

        private AntdUI.Label CreateTitleLabel(string text, int x, int y)
        {
            return new AntdUI.Label
            {
                Text = text,
                AutoSize = true,
                Location = new Point(x, y)
            };
        }

        private AntdUI.Input CreateInput(int x, int y, int width, string placeholder)
        {
            return new AntdUI.Input
            {
                Location = new Point(x, y),
                Size = new Size(width, 36),
                PlaceholderText = placeholder
            };
        }

        private DataGridView CreateWechatGrid()
        {
            var grid = new DataGridView
            {
                AllowUserToAddRows = false,
                AllowUserToDeleteRows = false,
                ReadOnly = true,
                RowHeadersVisible = false,
                AutoSizeColumnsMode = DataGridViewAutoSizeColumnsMode.Fill,
                BackgroundColor = Color.White,
                BorderStyle = BorderStyle.FixedSingle
            };

            grid.Columns.Add("Shop", "收款方");
            grid.Columns.Add("Amount", "金额");
            grid.Columns.Add("Time", "时间");
            grid.Columns.Add("Remark", "备注/订单号");
            grid.Columns.Add("Callback", "回调状态");
            return grid;
        }

        private DataGridView CreateAlipayGrid()
        {
            var grid = new DataGridView
            {
                AllowUserToAddRows = false,
                AllowUserToDeleteRows = false,
                ReadOnly = true,
                RowHeadersVisible = false,
                AutoSizeColumnsMode = DataGridViewAutoSizeColumnsMode.Fill,
                BackgroundColor = Color.White,
                BorderStyle = BorderStyle.FixedSingle
            };

            grid.Columns.Add("Index", "序号");
            grid.Columns.Add("OrderNo", "订单号");
            grid.Columns.Add("Amount", "金额");
            grid.Columns.Add("Time", "时间");
            grid.Columns.Add("Remark", "备注");
            grid.Columns.Add("Callback", "回调状态");
            return grid;
        }

        private void Form1_Load(object? sender, EventArgs e)
        {
            LoadConfig();
            BindConfigToUi();
            InitializeRuntimeTimer();
            InitializeHeartbeatTimer();
            SelectPage("home");
            UpdateServiceStatus(false);
            ApplyHeartbeatSetting();
            Log("程序已启动。");
        }

        private void Form1_FormClosing(object? sender, FormClosingEventArgs e)
        {
            StopListener();
            StopHeartbeat();
            _runtimeTimer.Stop();
            _runtimeTimer.Dispose();
            _heartbeatTimer.Dispose();
            _httpClient.Dispose();
        }

        private void InitializeRuntimeTimer()
        {
            _runtimeTimer.Interval = 1000;
            _runtimeTimer.Tick += (_, _) =>
            {
                var span = DateTime.Now - _appStartTime;
                lblRuntimeBottom.Text = $"{span.Days:00}天:{span.Hours:00}时:{span.Minutes:00}分:{span.Seconds:00}秒";
            };
            _runtimeTimer.Start();
        }

        private void InitializeHeartbeatTimer()
        {
            _heartbeatTimer.Tick += async (_, _) => await SendHeartbeatAsync(false);
        }

        private void ButtonCollapse_Click(object? sender, EventArgs e)
        {
            var nextCollapsed = !menu.Collapsed;

            menu.SuspendLayout();
            menu.Collapsed = nextCollapsed;
            menu.Width = nextCollapsed ? 56 : 220;
            ReloadMenuItems();
            menu.ResumeLayout();
            menu.Refresh();

            buttonCollapse.Toggle = !nextCollapsed;
            SyncMenuSelection();
        }

        private void Menu_SelectChanged(object? sender, MenuSelectEventArgs e)
        {
            if (_isSynchronizingNavigation)
            {
                return;
            }

            var tag = e.Value.Tag?.ToString();
            if (!string.IsNullOrWhiteSpace(tag))
            {
                SelectPage(tag);
            }
        }

        private void SyncMenuSelection()
        {
            _isSynchronizingNavigation = true;
            try
            {
                switch (_currentPageKey)
                {
                    case "home":
                        menu.SelectIndex(0, true);
                        break;
                    case "wechat":
                        menu.SelectIndex(1, true);
                        break;
                    case "alipay":
                        menu.SelectIndex(2, true);
                        break;
                    case "settings":
                        menu.SelectIndex(3, true);
                        break;
                }
            }
            finally
            {
                _isSynchronizingNavigation = false;
            }
        }

        private void SelectPage(string pageKey)
        {
            _currentPageKey = pageKey;

            pageHome.Visible = pageKey == "home";
            pageWechat.Visible = pageKey == "wechat";
            pageAlipay.Visible = pageKey == "alipay";
            pageSettings.Visible = pageKey == "settings";

            if (pageHome.Visible) pageHome.BringToFront();
            else if (pageWechat.Visible) pageWechat.BringToFront();
            else if (pageAlipay.Visible) pageAlipay.BringToFront();
            else if (pageSettings.Visible) pageSettings.BringToFront();

            SyncMenuSelection();
        }

        private void LoadConfig()
        {
            if (!File.Exists(_configFilePath))
            {
                _config = new ClientConfig();
                return;
            }

            var json = File.ReadAllText(_configFilePath, Encoding.UTF8);
            _config = JsonSerializer.Deserialize<ClientConfig>(json, _jsonOptions) ?? new ClientConfig();
        }

        private void SaveConfig()
        {
            var json = JsonSerializer.Serialize(_config, _jsonOptions);
            File.WriteAllText(_configFilePath, json, Encoding.UTF8);
        }

        private void BindConfigToUi()
        {
            txtServerUrl.Text = _config.ServerUrl;
            txtApiKey.Text = _config.ApiKey;
            txtNotifyEmail.Text = _config.NotifyEmail;
            txtWechatPath.Text = _config.WechatPath;
            txtWechatId.Text = _config.WechatSid;
            txtAliPath.Text = _config.AlipayPath;
            txtAliAppId.Text = _config.AlipayAppId;
            txtAliPid.Text = _config.AlipayUserId;
            txtServicePort.Text = _config.ListenPort.ToString();
            txtListenPath.Text = _config.ListenPath;
            numWechatInterval.Value = Math.Min(Math.Max(_config.WechatIntervalSeconds, 1), 3600);
            numAlipayInterval.Value = Math.Min(Math.Max(_config.AlipayIntervalSeconds, 1), 3600);
            chkWheel.Checked = _config.EnableWheelPolling;
            chkHeartbeatEnabled.Checked = _config.EnableHeartbeat;
        }

        private void SaveUiToConfig()
        {
            _config.ServerUrl = NormalizeServerUrl(txtServerUrl.Text);
            _config.ApiKey = txtApiKey.Text.Trim();
            _config.NotifyEmail = txtNotifyEmail.Text.Trim();
            _config.WechatPath = txtWechatPath.Text.Trim();
            _config.WechatSid = txtWechatId.Text.Trim();
            _config.AlipayPath = txtAliPath.Text.Trim();
            _config.AlipayAppId = txtAliAppId.Text.Trim();
            _config.AlipayUserId = txtAliPid.Text.Trim();
            _config.ListenPort = ParsePort(txtServicePort.Text);
            _config.ListenPath = string.IsNullOrWhiteSpace(txtListenPath.Text) ? "/notify/" : txtListenPath.Text.Trim();
            _config.WechatIntervalSeconds = (int)numWechatInterval.Value;
            _config.AlipayIntervalSeconds = (int)numAlipayInterval.Value;
            _config.EnableWheelPolling = chkWheel.Checked;
            _config.EnableHeartbeat = chkHeartbeatEnabled.Checked;
        }

        private static int ParsePort(string text)
        {
            if (!int.TryParse(text, out var port) || port < 1 || port > 65535)
            {
                return 8989;
            }

            return port;
        }

        private void btnSaveConfig_Click(object? sender, EventArgs e)
        {
            try
            {
                SaveUiToConfig();
                SaveConfig();
                ApplyHeartbeatSetting();
                Log("配置已保存。");
            }
            catch (Exception ex)
            {
                Log($"保存配置失败：{ex.Message}");
                MessageBox.Show(ex.Message, "保存失败", MessageBoxButtons.OK, MessageBoxIcon.Error);
            }
        }

        private async void btnStart_Click(object? sender, EventArgs e)
        {
            try
            {
                SaveUiToConfig();
                ValidateConfig(_config);
                SaveConfig();
                await StartListenerAsync();
                UpdateServiceStatus(true);
                Log($"本地监听已启动：{BuildLocalListenUrl(_config)}");
            }
            catch (Exception ex)
            {
                UpdateServiceStatus(false);
                Log($"启动监听失败：{ex.Message}");
                MessageBox.Show(ex.Message, "启动失败", MessageBoxButtons.OK, MessageBoxIcon.Error);
            }
        }

        private void btnStop_Click(object? sender, EventArgs e)
        {
            StopListener();
            UpdateServiceStatus(false);
            Log("监听已停止。");
        }

        private async Task StartListenerAsync()
        {
            StopListener();

            _listenerCancellationTokenSource = new CancellationTokenSource();
            _httpListener = new HttpListener();

            var prefix = BuildHttpPrefix(_config);
            _httpListener.Prefixes.Add(prefix);
            _httpListener.Start();

            _ = Task.Run(() => ListenLoopAsync(_listenerCancellationTokenSource.Token));
            await Task.CompletedTask;
        }

        private void StopListener()
        {
            try
            {
                _listenerCancellationTokenSource?.Cancel();
            }
            catch
            {
            }

            try
            {
                if (_httpListener is { IsListening: true })
                {
                    _httpListener.Stop();
                }
            }
            catch
            {
            }

            try
            {
                _httpListener?.Close();
            }
            catch
            {
            }

            _httpListener = null;
            _listenerCancellationTokenSource?.Dispose();
            _listenerCancellationTokenSource = null;
        }

        private async Task ListenLoopAsync(CancellationToken cancellationToken)
        {
            if (_httpListener == null)
            {
                return;
            }

            while (!cancellationToken.IsCancellationRequested && _httpListener.IsListening)
            {
                try
                {
                    var context = await _httpListener.GetContextAsync();
                    _ = Task.Run(() => HandleRequestAsync(context), cancellationToken);
                }
                catch (HttpListenerException)
                {
                    break;
                }
                catch (ObjectDisposedException)
                {
                    break;
                }
                catch (Exception ex)
                {
                    Log($"监听循环异常：{ex.Message}");
                }
            }
        }

        private async Task HandleRequestAsync(HttpListenerContext context)
        {
            try
            {
                if (!string.Equals(context.Request.HttpMethod, "POST", StringComparison.OrdinalIgnoreCase))
                {
                    await WriteJsonResponseAsync(context.Response, 405, new { ok = false, message = "Only POST is supported." });
                    return;
                }

                using var reader = new StreamReader(context.Request.InputStream, context.Request.ContentEncoding ?? Encoding.UTF8);
                var body = await reader.ReadToEndAsync();
                Log($"收到本地事件：{body}");

                var paymentEvent = JsonSerializer.Deserialize<PaymentEvent>(body, _jsonOptions);
                if (paymentEvent == null)
                {
                    await WriteJsonResponseAsync(context.Response, 400, new { ok = false, message = "请求体不能为空。" });
                    return;
                }

                paymentEvent.ReceivedAt = paymentEvent.ReceivedAt == default ? DateTimeOffset.Now : paymentEvent.ReceivedAt;
                paymentEvent.Raw ??= body;

                var callbackResult = await ForwardEventToServerAsync(paymentEvent);
                AddPaymentLog(paymentEvent, callbackResult);
                await WriteJsonResponseAsync(context.Response, 200, new
                {
                    ok = true,
                    forwarded = true,
                    statusCode = (int)callbackResult.StatusCode,
                    responseBody = callbackResult.ResponseBody
                });
            }
            catch (Exception ex)
            {
                Log($"处理请求失败：{ex.Message}");
                await WriteJsonResponseAsync(context.Response, 500, new { ok = false, message = ex.Message });
            }
        }

        private async Task<ServerCallbackResult> ForwardEventToServerAsync(PaymentEvent paymentEvent)
        {
            var serverUrl = NormalizeServerUrl(_config.ServerUrl);
            if (string.IsNullOrWhiteSpace(serverUrl))
            {
                return new ServerCallbackResult
                {
                    StatusCode = HttpStatusCode.OK,
                    ResponseBody = "未配置服务端 URL，已跳过回调。"
                };
            }

            var payload = new ServerCallbackPayload
            {
                ApiKey = _config.ApiKey,
                Channel = paymentEvent.Channel,
                Amount = paymentEvent.Amount,
                OrderNo = paymentEvent.OrderNo,
                TradeNo = paymentEvent.TradeNo,
                Payer = paymentEvent.Payer,
                Status = paymentEvent.Status,
                ReceivedAt = paymentEvent.ReceivedAt,
                Raw = paymentEvent.Raw
            };

            Log($"转发到服务端：{serverUrl}");
            var response = await _httpClient.PostAsJsonAsync(serverUrl, payload, _jsonOptions);
            var responseBody = await response.Content.ReadAsStringAsync();
            Log($"服务端响应：HTTP {(int)response.StatusCode} {response.StatusCode}，内容：{responseBody}");

            return new ServerCallbackResult
            {
                StatusCode = response.StatusCode,
                ResponseBody = responseBody
            };
        }

        private void AddPaymentLog(PaymentEvent paymentEvent, ServerCallbackResult callbackResult)
        {
            if (InvokeRequired)
            {
                BeginInvoke(() => AddPaymentLog(paymentEvent, callbackResult));
                return;
            }

            var callbackText = callbackResult.StatusCode == HttpStatusCode.OK ? "成功" : $"失败({(int)callbackResult.StatusCode})";

            if (string.Equals(paymentEvent.Channel, "alipay", StringComparison.OrdinalIgnoreCase))
            {
                gridAlipayLogs.Rows.Insert(
                    0,
                    gridAlipayLogs.Rows.Count + 1,
                    paymentEvent.OrderNo,
                    paymentEvent.Amount.ToString("0.00"),
                    paymentEvent.ReceivedAt.LocalDateTime.ToString("yyyy-MM-dd HH:mm:ss"),
                    paymentEvent.Payer,
                    callbackText);

                lblAlipayStatusValue.Text = "支付宝: 在线";
                lblAlipayStatusValue.ForeColor = Color.LimeGreen;
            }
            else
            {
                gridWechatLogs.Rows.Insert(
                    0,
                    paymentEvent.Payer,
                    paymentEvent.Amount.ToString("0.00"),
                    paymentEvent.ReceivedAt.LocalDateTime.ToString("yyyy-MM-dd HH:mm:ss"),
                    paymentEvent.OrderNo,
                    callbackText);

                lblWechatStatusValue.Text = "微信: 在线";
                lblWechatStatusValue.ForeColor = Color.LimeGreen;
            }
        }

        private void chkHeartbeatEnabled_CheckedChanged(object? sender, EventArgs e)
        {
            ApplyHeartbeatSetting();
        }

        private async void btnHeartbeatCheck_Click(object? sender, EventArgs e)
        {
            await SendHeartbeatAsync(true);
        }

        private void ApplyHeartbeatSetting()
        {
            SaveUiToConfig();

            if (_config.EnableHeartbeat)
            {
                StartHeartbeat();
            }
            else
            {
                StopHeartbeat();
            }
        }

        private void StartHeartbeat()
        {
            const int intervalSeconds = 30;
            _heartbeatTimer.Interval = intervalSeconds * 1000;

            if (!_heartbeatTimer.Enabled)
            {
                _heartbeatTimer.Start();
            }
            else
            {
                _heartbeatTimer.Stop();
                _heartbeatTimer.Start();
            }
        }

        private void StopHeartbeat()
        {
            if (_heartbeatTimer.Enabled)
            {
                _heartbeatTimer.Stop();
            }
        }

        private async Task SendHeartbeatAsync(bool writeSuccessLog)
        {
            try
            {
                var serverUrl = NormalizeServerUrl(txtServerUrl.Text);
                var apiKey = txtApiKey.Text.Trim();

                if (string.IsNullOrWhiteSpace(serverUrl))
                {
                    Log("心跳检测异常：未配置服务端地址。");
                    return;
                }

                if (string.IsNullOrWhiteSpace(apiKey))
                {
                    Log("心跳检测异常：未配置通信密钥。");
                    return;
                }

                var heartbeatCheckResult = await RequestHeartbeatStateAsync(serverUrl, apiKey);

                if (!heartbeatCheckResult.IsSuccessStatusCode)
                {
                    Log($"心跳检测异常：HTTP {(int)heartbeatCheckResult.StatusCode} {heartbeatCheckResult.StatusCode}，内容：{heartbeatCheckResult.ResponseBody}");
                    return;
                }

                var heartbeatResponse = heartbeatCheckResult.Response;
                if (heartbeatResponse == null)
                {
                    Log($"心跳检测异常：服务端返回无法解析，内容：{heartbeatCheckResult.ResponseBody}");
                    return;
                }

                if (heartbeatResponse.Code != 1)
                {
                    Log($"心跳检测异常：code={heartbeatResponse.Code}，msg={heartbeatResponse.Msg}");
                    return;
                }

                if (heartbeatResponse.Data == null)
                {
                    if (writeSuccessLog)
                    {
                        Log("心跳检测：心跳=正常，最后支付=-，最后心跳=-");
                    }

                    return;
                }

                var lastPayText = FormatUnixTimestamp(heartbeatResponse.Data.LastPay);
                var lastHeartText = FormatUnixTimestamp(heartbeatResponse.Data.LastHeart);

                if (heartbeatResponse.Data.JkState == 1)
                {
                    if (writeSuccessLog)
                    {
                        Log($"心跳检测：心跳=正常，最后支付={lastPayText}，最后心跳={lastHeartText}");
                    }

                    return;
                }

                var stateText = heartbeatResponse.Data.JkState switch
                {
                    0 => "掉线",
                    -1 => "未绑定监控端",
                    _ => $"未知({heartbeatResponse.Data.JkState?.ToString() ?? "null"})"
                };

                Log($"心跳检测异常：状态={stateText}，最后支付={lastPayText}，最后心跳={lastHeartText}，返回JSON={heartbeatCheckResult.ResponseBody}");
            }
            catch (Exception ex)
            {
                Log($"心跳检测异常：{ex.Message}");
            }
        }

        private async Task<HeartbeatRequestResult> RequestHeartbeatStateAsync(string serverUrl, string apiKey)
        {
            var timestamp = DateTimeOffset.Now.ToUnixTimeSeconds().ToString();

            var candidates = new[]
            {
                BuildHeartbeatStateUrl(serverUrl, timestamp, CreateMd5(timestamp + apiKey)),
                BuildHeartbeatStateUrl(serverUrl, timestamp, CreateMd5(apiKey + timestamp)),
                BuildHeartbeatStateUrl(serverUrl, timestamp, CreateMd5(timestamp + apiKey), apiKey, null),
                BuildHeartbeatStateUrl(serverUrl, timestamp, CreateMd5(apiKey + timestamp), apiKey, null),
                BuildHeartbeatStateUrl(serverUrl, timestamp, CreateMd5(timestamp + apiKey), null, apiKey),
                BuildHeartbeatStateUrl(serverUrl, timestamp, CreateMd5(apiKey + timestamp), null, apiKey)
            };

            HeartbeatRequestResult? lastResult = null;

            foreach (var url in candidates.Distinct())
            {
                var response = await _httpClient.GetAsync(url);
                var responseBody = await response.Content.ReadAsStringAsync();

                HeartbeatStateResponse? parsed = null;
                try
                {
                    parsed = JsonSerializer.Deserialize<HeartbeatStateResponse>(responseBody, _jsonOptions);
                }
                catch
                {
                }

                var result = new HeartbeatRequestResult
                {
                    StatusCode = response.StatusCode,
                    ResponseBody = responseBody,
                    Response = parsed
                };

                lastResult = result;

                if (!response.IsSuccessStatusCode)
                {
                    continue;
                }

                if (parsed?.Code == 1)
                {
                    return result;
                }

                if (parsed?.Msg?.Contains("签名校验不通过", StringComparison.OrdinalIgnoreCase) == true)
                {
                    continue;
                }

                return result;
            }

            return lastResult ?? new HeartbeatRequestResult
            {
                StatusCode = HttpStatusCode.ServiceUnavailable,
                ResponseBody = "未获得有效心跳响应。"
            };
        }

        private async Task WriteJsonResponseAsync(HttpListenerResponse response, int statusCode, object payload)
        {
            response.StatusCode = statusCode;
            response.ContentType = "application/json; charset=utf-8";

            var json = JsonSerializer.Serialize(payload, _jsonOptions);
            var bytes = Encoding.UTF8.GetBytes(json);
            response.ContentLength64 = bytes.Length;
            await response.OutputStream.WriteAsync(bytes);
            response.OutputStream.Close();
        }

        private void UpdateServiceStatus(bool isRunning)
        {
            lblTopNotice.Text = isRunning
                ? $"监听中：{BuildLocalListenUrl(_config)}"
                : "准备接入 V免签服务端通信";
        }

        private static void ValidateConfig(ClientConfig config)
        {
            if (config.ListenPort < 1 || config.ListenPort > 65535)
            {
                throw new InvalidOperationException("监听端口必须在 1-65535 之间。");
            }

            var serverUrl = NormalizeServerUrl(config.ServerUrl);
            if (!string.IsNullOrWhiteSpace(serverUrl) &&
                (!Uri.TryCreate(serverUrl, UriKind.Absolute, out var serverUri) ||
                 (serverUri.Scheme != Uri.UriSchemeHttp && serverUri.Scheme != Uri.UriSchemeHttps)))
            {
                throw new InvalidOperationException("服务端 URL 必须是有效的 HTTP/HTTPS 地址。");
            }
        }

        private static string NormalizeServerUrl(string? url)
        {
            var value = url?.Trim() ?? string.Empty;
            if (string.IsNullOrWhiteSpace(value))
            {
                return string.Empty;
            }

            if (!value.StartsWith("http://", StringComparison.OrdinalIgnoreCase) &&
                !value.StartsWith("https://", StringComparison.OrdinalIgnoreCase))
            {
                value = "https://" + value;
            }

            return value;
        }

        private void Log(string message)
        {
            if (InvokeRequired)
            {
                BeginInvoke(() => Log(message));
                return;
            }

            txtLog.AppendText($"[{DateTime.Now:yyyy-MM-dd HH:mm:ss}] {message}{Environment.NewLine}");
        }

        private static string NormalizeListenPath(string? path)
        {
            var normalized = string.IsNullOrWhiteSpace(path) ? "/notify/" : path.Trim();

            if (!normalized.StartsWith('/'))
            {
                normalized = "/" + normalized;
            }

            if (!normalized.EndsWith('/'))
            {
                normalized += "/";
            }

            return normalized;
        }

        private static string BuildHttpPrefix(ClientConfig config)
        {
            return $"http://+:{config.ListenPort}{NormalizeListenPath(config.ListenPath)}";
        }

        private static string BuildLocalListenUrl(ClientConfig config)
        {
            return $"http://127.0.0.1:{config.ListenPort}{NormalizeListenPath(config.ListenPath)}";
        }

        private static string BuildHeartbeatStateUrl(string serverUrl, string timestamp, string sign, string? key = null, string? userKey = null)
        {
            if (!Uri.TryCreate(serverUrl, UriKind.Absolute, out var baseUri))
            {
                throw new InvalidOperationException("服务端地址格式无效。");
            }

            var stateUri = new Uri(baseUri, "/getState");
            var query = new List<string>
            {
                $"t={Uri.EscapeDataString(timestamp)}",
                $"sign={Uri.EscapeDataString(sign)}"
            };

            if (!string.IsNullOrWhiteSpace(key))
            {
                query.Add($"key={Uri.EscapeDataString(key)}");
            }

            if (!string.IsNullOrWhiteSpace(userKey))
            {
                query.Add($"userkey={Uri.EscapeDataString(userKey)}");
            }

            return $"{stateUri}?{string.Join("&", query)}";
        }

        private static string CreateMd5(string input)
        {
            var bytes = Encoding.UTF8.GetBytes(input);
            var hash = MD5.HashData(bytes);
            var builder = new StringBuilder(hash.Length * 2);

            foreach (var b in hash)
            {
                builder.Append(b.ToString("x2"));
            }

            return builder.ToString();
        }

        private static string FormatUnixTimestamp(long? timestamp)
        {
            if (timestamp == null || timestamp <= 0)
            {
                return "-";
            }

            try
            {
                return DateTimeOffset.FromUnixTimeSeconds(timestamp.Value).LocalDateTime.ToString("yyyy-MM-dd HH:mm:ss");
            }
            catch
            {
                return timestamp.Value.ToString();
            }
        }
    }

    public sealed class ClientConfig
    {
        public string ServerUrl { get; set; } = "https://zf.yunzer.cn/7eed756ca37ca057370ea9bb208d25fa";
        public string ApiKey { get; set; } = "7eed756ca37ca057370ea9bb208d25fa";
        public string NotifyEmail { get; set; } = string.Empty;
        public string WechatPath { get; set; } = string.Empty;
        public string WechatSid { get; set; } = string.Empty;
        public string AlipayPath { get; set; } = string.Empty;
        public string AlipayAppId { get; set; } = string.Empty;
        public string AlipayUserId { get; set; } = string.Empty;
        public int ListenPort { get; set; } = 8989;
        public int WechatIntervalSeconds { get; set; } = 5;
        public int AlipayIntervalSeconds { get; set; } = 5;
        public bool EnableWheelPolling { get; set; } = true;
        public bool EnableHeartbeat { get; set; } = false;
        public int HeartbeatIntervalSeconds { get; set; } = 30;
        public string ListenPath { get; set; } = "/notify/";
    }

    public sealed class PaymentEvent
    {
        public string Channel { get; set; } = "wechat";
        public decimal Amount { get; set; }
        public string OrderNo { get; set; } = string.Empty;
        public string TradeNo { get; set; } = string.Empty;
        public string Payer { get; set; } = string.Empty;
        public string Status { get; set; } = "success";
        public DateTimeOffset ReceivedAt { get; set; }
        public string? Raw { get; set; }
    }

    public sealed class ServerCallbackPayload
    {
        public string ApiKey { get; set; } = string.Empty;
        public string Channel { get; set; } = string.Empty;
        public decimal Amount { get; set; }
        public string OrderNo { get; set; } = string.Empty;
        public string TradeNo { get; set; } = string.Empty;
        public string Payer { get; set; } = string.Empty;
        public string Status { get; set; } = string.Empty;
        public DateTimeOffset ReceivedAt { get; set; }
        public string? Raw { get; set; }
    }

    public sealed class ServerCallbackResult
    {
        public HttpStatusCode StatusCode { get; set; }
        public string ResponseBody { get; set; } = string.Empty;
    }

    public sealed class HeartbeatRequestResult
    {
        public HttpStatusCode StatusCode { get; set; }
        public string ResponseBody { get; set; } = string.Empty;
        public HeartbeatStateResponse? Response { get; set; }
        public bool IsSuccessStatusCode => (int)StatusCode >= 200 && (int)StatusCode <= 299;
    }

    public sealed class HeartbeatStateResponse
    {
        public int Code { get; set; }
        public string Msg { get; set; } = string.Empty;
        public HeartbeatStateData? Data { get; set; }
    }

    public sealed class HeartbeatStateData
    {
        [JsonNumberHandling(JsonNumberHandling.AllowReadingFromString)]
        [JsonPropertyName("lastpay")]
        public long? LastPay { get; set; }

        [JsonNumberHandling(JsonNumberHandling.AllowReadingFromString)]
        [JsonPropertyName("lastheart")]
        public long? LastHeart { get; set; }

        [JsonPropertyName("jkstate")]
        [JsonNumberHandling(JsonNumberHandling.AllowReadingFromString)]
        public int? JkState { get; set; }
    }
}
