using VoiceCat.App.Models;
using VoiceCat.Interop;

namespace VoiceCat.App.Forms;

/// <summary>
/// Saved-server list + connect flow. On success, ConnectedClient/SelfUserId/Nickname are set
/// and DialogResult == OK; the caller (Program.cs) takes ownership of ConnectedClient (does
/// NOT dispose it here — MainForm owns its lifetime from that point on).
/// </summary>
public partial class ConnectDialog : Form
{
    private readonly List<SavedServer> _servers;
    private readonly System.Windows.Forms.Timer _pumpTimer = new() { Interval = 30 };

    private VoiceCatClient? _client;
    private bool _identityDialogShown;

    public VoiceCatClient? ConnectedClient { get; private set; }
    public uint SelfUserId { get; private set; }
    public string Nickname { get; private set; } = "";
    public string ServerName { get; private set; } = "";

    public ConnectDialog()
    {
        InitializeComponent();
        _servers = ServerListStore.Load();
        RefreshServerList();

        _pumpTimer.Tick += (_, _) =>
        {
            try { _client?.PumpEvents(); }
            catch (Exception ex) { Console.Error.WriteLine($"[ConnectDialog] EXCEPTION in PumpEvents/event handler: {ex}"); }
        };

        btnAddNew.Click += BtnAddNew_Click;
        btnEdit.Click += BtnEdit_Click;
        btnRemove.Click += BtnRemove_Click;
        btnConnect.Click += BtnConnect_Click;
        lstServers.DoubleClick += BtnConnect_Click;
    }

    private void RefreshServerList()
    {
        object? previouslySelected = lstServers.SelectedItem;
        lstServers.Items.Clear();
        foreach (var s in _servers) lstServers.Items.Add(s);
        if (previouslySelected is not null && _servers.Contains(previouslySelected))
            lstServers.SelectedItem = previouslySelected;
        else if (lstServers.Items.Count > 0)
            lstServers.SelectedIndex = 0;
        UpdateButtonsEnabled();
    }

    private void UpdateButtonsEnabled()
    {
        bool hasSelection = lstServers.SelectedItem is not null;
        btnConnect.Enabled = hasSelection;
        btnEdit.Enabled = hasSelection;
        btnRemove.Enabled = hasSelection;
    }

    private void BtnAddNew_Click(object? sender, EventArgs e)
    {
        using var dlg = new AddServerDialog();
        if (dlg.ShowDialog(this) != DialogResult.OK || dlg.Result is null) return;
        _servers.Add(dlg.Result);
        ServerListStore.Save(_servers);
        RefreshServerList();
        lstServers.SelectedItem = dlg.Result;
    }

    private void BtnEdit_Click(object? sender, EventArgs e)
    {
        if (lstServers.SelectedItem is not SavedServer existing) return;
        using var dlg = new AddServerDialog(existing);
        if (dlg.ShowDialog(this) != DialogResult.OK) return;
        ServerListStore.Save(_servers);
        RefreshServerList();
    }

    private void BtnRemove_Click(object? sender, EventArgs e)
    {
        if (lstServers.SelectedItem is not SavedServer existing) return;
        var confirm = MessageBox.Show(this, $"Remove '{existing}' from the saved-server list?",
            "VoiceCat", MessageBoxButtons.YesNo, MessageBoxIcon.Question);
        if (confirm != DialogResult.Yes) return;
        _servers.Remove(existing);
        ServerListStore.Save(_servers);
        RefreshServerList();
    }

    private void BtnConnect_Click(object? sender, EventArgs e)
    {
        if (lstServers.SelectedItem is not SavedServer server)
            return;
        try
        {
            StartConnect(server);
        }
        catch (Exception ex)
        {
            Console.Error.WriteLine($"[ConnectDialog] EXCEPTION in StartConnect: {ex}");
            lblStatus.Text = $"Internal error: {ex.Message}";
            CleanupFailedAttempt();
        }
    }

    private void StartConnect(SavedServer server)
    {
        SetBusy(true);
        lblStatus.Text = "Connecting...";

        ServerName = string.IsNullOrWhiteSpace(server.DisplayName)
            ? $"{server.Host}:{server.Port}"
            : server.DisplayName;

        string tofuDir = Path.GetDirectoryName(ServerListStore.TofuStorePath)!;
        Directory.CreateDirectory(tofuDir);

        _client = new VoiceCatClient("VoiceCat-Windows", VoiceCatClient.VersionString,
            VcLogLevel.Info, ServerListStore.TofuStorePath);
        _client.EventReceived += OnEvent;
        _identityDialogShown = false;
        _pumpTimer.Start();

        var connectResult = _client.Connect(server.Host, server.Port);
        if (connectResult != VcResult.Ok)
        {
            lblStatus.Text = $"Connect failed: {connectResult}";
            CleanupFailedAttempt();
            return;
        }

        if (server.AuthMode == AuthMode.Guest)
        {
            Nickname = string.IsNullOrWhiteSpace(server.LastNickname) ? Environment.UserName : server.LastNickname;
            _client.AuthenticateGuest(Nickname);
        }
        else
        {
            string password;
            if (server.ProtectedPasswordBase64 is not null)
            {
                password = PasswordProtector.Unprotect(server.ProtectedPasswordBase64);
            }
            else
            {
                using var pwDlg = new PasswordPromptDialog($"Password for {server.SavedUsername}@{server.Host}:");
                if (pwDlg.ShowDialog(this) != DialogResult.OK)
                {
                    lblStatus.Text = "Cancelled.";
                    CleanupFailedAttempt();
                    return;
                }
                password = pwDlg.Password;
            }
            Nickname = server.SavedUsername ?? "";
            _client.AuthenticateUser(server.SavedUsername ?? "", password);
        }
    }

    private void OnEvent(VoiceCatEvent ev)
    {
        switch (ev.Type)
        {
            case VcEventType.ConnectionState:
                lblStatus.Text = ev.ConnectionState switch
                {
                    VcConnectionState.Connecting => "Connecting...",
                    VcConnectionState.TlsHandshake => "TLS handshake...",
                    VcConnectionState.VerifyingIdentity => "Verifying server identity...",
                    VcConnectionState.Authenticating => "Authenticating...",
                    VcConnectionState.Connected => "Connected.",
                    _ => lblStatus.Text,
                };
                break;

            case VcEventType.ServerIdentity:
                HandleServerIdentity((VcTofuStatus)ev.U32a, ev.Text ?? "");
                break;

            case VcEventType.AuthResult:
                if (ev.Result == VcResult.Ok)
                {
                    SelfUserId = ev.UserId;
                    ConnectedClient = _client;
                    _pumpTimer.Stop();
                    _client!.EventReceived -= OnEvent;
                    DialogResult = DialogResult.OK;
                    Close();
                }
                else
                {
                    lblStatus.Text = $"Authentication failed: {ev.Text}";
                    CleanupFailedAttempt();
                }
                break;

            case VcEventType.Disconnected:
                if (ConnectedClient is null)
                {
                    lblStatus.Text = string.IsNullOrEmpty(ev.Text) ? "Disconnected." : $"Disconnected: {ev.Text}";
                    CleanupFailedAttempt();
                }
                break;
        }
    }

    private void HandleServerIdentity(VcTofuStatus status, string certFingerprintHex)
    {
        if (_identityDialogShown) return; // one decision per connect attempt
        if (status == VcTofuStatus.Matched)
        {
            // Silent success path — no dialog. See ServerIdentityDialog's doc comment.
            _client!.ConfirmServerIdentity(true);
            return;
        }

        _identityDialogShown = true;
        using var dlg = new ServerIdentityDialog(status, certFingerprintHex, _client!.GetServerIdentityDisplay());
        bool accept = dlg.ShowDialog(this) == DialogResult.OK;
        _client.ConfirmServerIdentity(accept);
        if (!accept) lblStatus.Text = "Server identity rejected.";
    }

    private void CleanupFailedAttempt()
    {
        _pumpTimer.Stop();
        if (_client is not null)
        {
            _client.EventReceived -= OnEvent;
            _client.Dispose();
            _client = null;
        }
        SetBusy(false);
    }

    private void SetBusy(bool busy)
    {
        lstServers.Enabled = !busy;
        btnConnect.Enabled = !busy && lstServers.SelectedItem is not null;
        btnAddNew.Enabled = !busy;
        btnEdit.Enabled = !busy && lstServers.SelectedItem is not null;
        btnRemove.Enabled = !busy && lstServers.SelectedItem is not null;
    }

    protected override void OnFormClosing(FormClosingEventArgs e)
    {
        if (DialogResult != DialogResult.OK)
        {
            _pumpTimer.Stop();
            if (_client is not null)
            {
                _client.EventReceived -= OnEvent;
                _client.Dispose();
                _client = null;
            }
        }
        base.OnFormClosing(e);
    }
}
