namespace Zcbot.WindowsNode;

internal sealed class TrayApplicationContext : ApplicationContext
{
    private readonly NodeConfigStore store;
    private readonly ConfigurationForm form;
    private readonly NotifyIcon tray;
    private readonly ToolStripMenuItem statusItem;
    private readonly ToolStripMenuItem exitItem;
    private CancellationTokenSource? connectionStop;
    private Task? connectionTask;
    private NodeConnectionLoop? connectionLoop;
    private NodeConfig? config;
    private bool exiting;
    private NodeStatus current = NodeStatus.Create(NodeState.NotRegistered, "尚未注册");

    internal TrayApplicationContext(NodeConfigStore store)
    {
        this.store = store;
        form = new ConfigurationForm();
        // Create the hidden form handle before the connection loop starts. Fast local
        // connections can otherwise report Connecting/Online before a handle exists,
        // and PostStatus would discard both updates until the user reconnects manually.
        _ = form.Handle;
        form.RegisterRequested += RegisterAsync;
        form.ReconnectRequested += RestartConnection;
        form.ResetIdentityRequested += ResetIdentity;
        form.DataRootMigrationRequested += MigrateDataRootAsync;

        statusItem = new ToolStripMenuItem("尚未注册") { Enabled = false };
        var menu = new ContextMenuStrip();
        menu.Items.Add(statusItem);
        menu.Items.Add(new ToolStripSeparator());
        menu.Items.Add("打开配置", null, (_, _) => ShowConfiguration());
        menu.Items.Add("立即重连", null, (_, _) => RestartConnection());
        menu.Items.Add(new ToolStripSeparator());
        exitItem = new ToolStripMenuItem("退出");
        exitItem.Click += async (_, _) => await ExitNodeAsync();
        menu.Items.Add(exitItem);

        tray = new NotifyIcon
        {
            ContextMenuStrip = menu,
            Icon = TrayIconFactory.Create(NodeState.NotRegistered),
            Text = "zcbot Windows Node - 尚未注册",
            Visible = true,
        };
        tray.DoubleClick += (_, _) => ShowConfiguration();

        if (store.Exists)
        {
            try
            {
                config = store.Load();
                UpdateStatus(NodeStatus.Create(NodeState.Connecting, "正在连接 zcbot…"));
                StartConnection();
            }
            catch (NodeConfigurationException exception)
            {
                UpdateStatus(NodeStatus.Create(NodeState.AuthenticationRequired, exception.Message));
                ShowConfiguration();
            }
        }
        else
        {
            UpdateStatus(current);
            ShowConfiguration();
        }
    }

    protected override void Dispose(bool disposing)
    {
        if (disposing)
        {
            connectionStop?.Cancel();
            tray.Visible = false;
            tray.Dispose();
            form.Dispose();
            connectionStop?.Dispose();
        }
        base.Dispose(disposing);
    }

    private async Task RegisterAsync(EnrollOptions options)
    {
        UpdateStatus(NodeStatus.Create(NodeState.Connecting, "正在注册节点…"));
        try
        {
            await EnrollmentClient.EnrollAsync(options, store, CancellationToken.None);
            config = store.Load();
            UpdateStatus(NodeStatus.Create(NodeState.Connecting, "注册成功，正在连接…"));
            StartConnection();
        }
        catch
        {
            UpdateStatus(NodeStatus.Create(NodeState.NotRegistered, "注册失败，请检查配置"));
            throw;
        }
    }

    private void StartConnection()
    {
        if (exiting || config is null || connectionTask is { IsCompleted: false })
        {
            return;
        }
        connectionStop?.Dispose();
        connectionStop = new CancellationTokenSource();
        connectionTask = RunConnectionAsync(config, connectionStop.Token);
    }

    private async Task RunConnectionAsync(NodeConfig nodeConfig, CancellationToken cancellationToken)
    {
        var loop = new NodeConnectionLoop(nodeConfig, status => PostStatus(status));
        connectionLoop = loop;
        try
        {
            await loop.RunAsync(cancellationToken);
        }
        catch (OperationCanceledException) when (cancellationToken.IsCancellationRequested)
        {
            PostStatus(NodeStatus.Create(NodeState.Stopped, "连接已停止"));
        }
        catch (NodeConfigurationException exception)
        {
            PostStatus(NodeStatus.Create(NodeState.AuthenticationRequired, exception.Message));
        }
        catch (Exception exception)
        {
            PostStatus(NodeStatus.Create(NodeState.Offline, $"节点异常：{exception.Message}"));
        }
        finally
        {
            if (ReferenceEquals(connectionLoop, loop))
            {
                connectionLoop = null;
            }
        }
    }

    private void RestartConnection()
    {
        if (config is null)
        {
            ShowConfiguration();
            return;
        }
        UpdateStatus(NodeStatus.Create(NodeState.Connecting, "等待本机任务收尾后重连…"));
        connectionStop?.Cancel();
        _ = RestartAfterStopAsync();
    }

    private void ResetIdentity()
    {
        connectionStop?.Cancel();
        store.DeleteLocalIdentity();
        config = null;
        UpdateStatus(NodeStatus.Create(NodeState.NotRegistered, "本机身份已清除，请使用新注册码注册"));
    }

    private async Task RestartAfterStopAsync()
    {
        if (connectionTask is not null)
        {
            await connectionTask;
        }
        StartConnection();
    }

    private async Task<NodeDataMigrationResult> MigrateDataRootAsync(string targetRoot)
    {
        var sourceRoot = NodePaths.ForCurrentMachine().RootDirectory;
        connectionStop?.Cancel();
        try
        {
            if (connectionTask is not null)
            {
                await connectionTask;
            }
            var result = await NodeDataMigrator.MigrateAsync(
                sourceRoot, targetRoot, CancellationToken.None);
            NodeDataRootSettings.SaveUserRoot(result.TargetDirectory);
            return result;
        }
        catch
        {
            StartConnection();
            throw;
        }
    }

    private void PostStatus(NodeStatus status)
    {
        if (form.IsHandleCreated)
        {
            form.BeginInvoke(() => UpdateStatus(status));
        }
    }

    private void UpdateStatus(NodeStatus status)
    {
        current = status;
        form.ApplyStatus(status, config);
        statusItem.Text = CompactMenuText(status.Message, 24);
        tray.Text = Limit($"zcbot Windows Node - {status.Message}", 63);
        var oldIcon = tray.Icon;
        tray.Icon = TrayIconFactory.Create(status.State);
        oldIcon?.Dispose();
    }

    private void ShowConfiguration()
    {
        form.ApplyStatus(current, config);
        form.Show();
        form.WindowState = FormWindowState.Normal;
        form.Activate();
    }

    private async Task ExitNodeAsync()
    {
        if (exiting)
        {
            return;
        }
        var activeJobs = new JobInboxStore(NodePaths.ForCurrentMachine().JobsDirectory)
            .ReadJobSnapshots()
            .Count(item => item.IsActive);
        var cancelJobs = false;
        if (activeJobs > 0)
        {
            var choice = MessageBox.Show(
                $"当前有 {activeJobs} 个本机任务尚未结束。\n\n"
                + "选择“是”：停止接收新任务，等待现有任务完成后退出。\n"
                + "选择“否”：取消现有任务并退出。\n"
                + "选择“取消”：返回，不退出。",
                "退出 zcbot Windows Node",
                MessageBoxButtons.YesNoCancel,
                MessageBoxIcon.Warning,
                MessageBoxDefaultButton.Button1);
            if (choice == DialogResult.Cancel)
            {
                return;
            }
            cancelJobs = choice == DialogResult.No;
            if (cancelJobs && MessageBox.Show(
                    "取消任务会终止正在运行的专业软件进程，未完成结果不会上传。确认取消任务并退出？",
                    "确认取消任务",
                    MessageBoxButtons.YesNo,
                    MessageBoxIcon.Warning,
                    MessageBoxDefaultButton.Button2) != DialogResult.Yes)
            {
                return;
            }
        }
        exiting = true;
        exitItem.Enabled = false;
        if (tray.ContextMenuStrip is not null)
        {
            tray.ContextMenuStrip.Enabled = false;
        }
        form.Enabled = false;
        UpdateStatus(NodeStatus.Create(
            NodeState.Stopped,
            cancelJobs
                ? "正在取消本机任务并退出…"
                : activeJobs > 0 ? "等待本机任务完成后退出…" : "正在退出…"));
        if (cancelJobs)
        {
            connectionLoop?.CancelActiveJobsForExit();
        }
        connectionStop?.Cancel();
        if (connectionTask is not null)
        {
            await connectionTask;
        }
        tray.Visible = false;
        ExitThread();
    }

    private static string Limit(string value, int length) =>
        value.Length <= length ? value : value[..length];

    private static string CompactMenuText(string value, int length)
    {
        var singleLine = value.Replace('\r', ' ').Replace('\n', ' ').Trim();
        return singleLine.Length <= length
            ? singleLine
            : singleLine[..(length - 1)] + "…";
    }
}
