using System;
using System.Net;
using System.Text.RegularExpressions;
using Robust.Client;
using Robust.Client.AutoGenerated;
using Robust.Client.UserInterface;
using Robust.Client.UserInterface.Controls;
using Robust.Client.UserInterface.XAML;
using Robust.Shared;
using Robust.Shared.AuthLib;
using Robust.Shared.Configuration;
using Robust.Shared.ContentPack;
using Robust.Shared.IoC;
using Robust.Shared.Localization;
using Robust.Shared.Log;
using Robust.Shared.Network;
using Robust.Shared.Utility;

namespace Robust.Client.UserInterface.Screens;

[GenerateTypedNameReferences, ContentAccessAllowed]
public sealed partial class DebugBuiltinConnectionScreen : UIScreen
{
    [Dependency] private IBaseClient _client = default!;
    [Dependency] private IConfigurationManager _cfg = default!;
    [Dependency] private IUserInterfaceManager _userInterface = default!;
    [Dependency] private IClientNetManager _net = default!;

    // ReSharper disable once InconsistentNaming
    private static readonly Regex IPv6Regex = new(@"\[(.*:.*:.*)](?::(\d+))?");

    private bool _isConnecting = false;

    public DebugBuiltinConnectionScreen()
    {
        RobustXamlLoader.Load(this);
        IoCManager.InjectDependencies(this);
        SetAnchorPreset(this, LayoutPreset.Wide);

        SetAnchorPreset(ConnectionBox, LayoutPreset.TopRight);
        SetMarginRight(ConnectionBox, -25);
        SetMarginTop(ConnectionBox, 30);
        SetGrowHorizontal(ConnectionBox, GrowDirection.Begin);

        UsernameEdit.Text = _cfg.GetCVar(CVars.PlayerName);
        _client.RunLevelChanged += RunLevelChanged;

        ConnectButton.OnPressed += ConnectButtonOnOnPressed;
    }

    private void ConnectButtonOnOnPressed(BaseButton.ButtonEventArgs obj)
    {
        var inputName = UsernameEdit.Text.Trim();
        var configName = _cfg.GetCVar(CVars.PlayerName);

        if (!UsernameHelpers.IsNameValid(inputName, out var reason))
        {
            var invalidReason = Loc.GetString(reason.ToText());
            _userInterface.Popup(
                Loc.GetString("debug-builtin-connection-screen-invalid-username-with-reason", ("invalidReason", invalidReason)),
                Loc.GetString("debug-builtin-connection-screen-invalid-username"));

            return;
        }

        if (UsernameEdit.Text != configName)
        {
            _cfg.SetCVar(CVars.PlayerName, inputName);
            _cfg.SaveToFile();
        }

        SetConnectingState(true);
        _net.ConnectFailed += OnConnectFailed;

        try
        {
            ParseAddress(ConnectionAddress.Text, out var ip, out var port);
            _client.ConnectToServer(ip, port);
        }
        catch (ArgumentException e)
        {
            _userInterface.Popup(Loc.GetString("debug-builtin-connection-screen-failed-to-connect", ("reason", e.Message)));
            Logger.Warning(e.ToString());
            _net.ConnectFailed -= OnConnectFailed;
            SetConnectingState(false);
        }
    }

    private void ParseAddress(string address, out string ip, out ushort port)
    {
        var match6 = IPv6Regex.Match(address);
        if (match6 != Match.Empty)
        {
            ip = match6.Groups[1].Value;
            if (!match6.Groups[2].Success)
            {
                port = _client.DefaultPort;
            }
            else if (!ushort.TryParse(match6.Groups[2].Value, out port))
            {
                throw new ArgumentException("Not a valid port.");
            }

            return;
        }

        // See if the IP includes a port.
        var split = address.Split(':');
        ip = address;
        port = _client.DefaultPort;
        if (split.Length > 2)
        {
            throw new ArgumentException("Not a valid Address.");
        }

        // IP:port format.
        if (split.Length == 2)
        {
            ip = split[0];
            if (!ushort.TryParse(split[1], out port))
            {
                throw new ArgumentException("Not a valid port.");
            }
        }
    }

    private void RunLevelChanged(object? obj, RunLevelChangedEventArgs args)
    {
        switch (args.NewLevel)
        {
            case ClientRunLevel.Connecting:
                SetConnectingState(true);
                break;
            case ClientRunLevel.Connected:
                SetConnectingState(false);
                _net.ConnectFailed -= OnConnectFailed;
                break;
        }
    }

    private void OnConnectFailed(object? _, NetConnectFailArgs args)
    {
        _userInterface.Popup(Loc.GetString("debug-builtin-connection-screen-failed-to-connect",("reason", args.Reason)));
        _net.ConnectFailed -= OnConnectFailed;
        SetConnectingState(false);
    }

    private void SetConnectingState(bool state)
    {
        _isConnecting = state;
        ConnectButton.Disabled = state;
    }
}
