#nullable disable
using NadekoBot.Common.ModuleBehaviors;
using System.Diagnostics;

namespace NadekoBot.Services;

public sealed class StatsService : IStatsService, IReadyExecutor, INService
{
    public static string BotVersion
        => typeof(Bot).Assembly.GetName().Version?.ToString(3) ?? "Custom";

    public string Author
        => "Kwoth#2452";

    public double MessagesPerSecond
        => MessageCounter / GetUptime().TotalSeconds;

    public long TextChannels
        => Interlocked.Read(ref textChannels);

    public long VoiceChannels
        => Interlocked.Read(ref voiceChannels);

    public long MessageCounter
        => Interlocked.Read(ref messageCounter);

    public long CommandsRan
        => Interlocked.Read(ref commandsRan);

    private readonly Process _currentProcess = Process.GetCurrentProcess();
    private readonly DiscordSocketClient _client;
    private readonly IBotCredentials _creds;
    private readonly DateTime _started;

    private long textChannels;
    private long voiceChannels;
    private long messageCounter;
    private long commandsRan;

    private readonly IHttpClientFactory _httpFactory;

    public StatsService(
        DiscordSocketClient client,
        CommandHandler cmdHandler,
        IBotCredentials creds,
        IHttpClientFactory factory)
    {
        _client = client;
        _creds = creds;
        _httpFactory = factory;

        _started = DateTime.UtcNow;
        _client.MessageReceived += _ => Task.FromResult(Interlocked.Increment(ref messageCounter));
        cmdHandler.CommandExecuted += (_, _) => Task.FromResult(Interlocked.Increment(ref commandsRan));

        _client.ChannelCreated += c =>
        {
            _ = Task.Run(() =>
            {
                if (c is ITextChannel)
                    Interlocked.Increment(ref textChannels);
                else if (c is IVoiceChannel)
                    Interlocked.Increment(ref voiceChannels);
            });

            return Task.CompletedTask;
        };

        _client.ChannelDestroyed += c =>
        {
            _ = Task.Run(() =>
            {
                if (c is ITextChannel)
                    Interlocked.Decrement(ref textChannels);
                else if (c is IVoiceChannel)
                    Interlocked.Decrement(ref voiceChannels);
            });

            return Task.CompletedTask;
        };

        _client.GuildAvailable += g =>
        {
            _ = Task.Run(() =>
            {
                var tc = g.Channels.Count(cx => cx is ITextChannel);
                var vc = g.Channels.Count - tc;
                Interlocked.Add(ref textChannels, tc);
                Interlocked.Add(ref voiceChannels, vc);
            });
            return Task.CompletedTask;
        };

        _client.JoinedGuild += g =>
        {
            _ = Task.Run(() =>
            {
                var tc = g.Channels.Count(cx => cx is ITextChannel);
                var vc = g.Channels.Count - tc;
                Interlocked.Add(ref textChannels, tc);
                Interlocked.Add(ref voiceChannels, vc);
            });
            return Task.CompletedTask;
        };

        _client.GuildUnavailable += g =>
        {
            _ = Task.Run(() =>
            {
                var tc = g.Channels.Count(cx => cx is ITextChannel);
                var vc = g.Channels.Count - tc;
                Interlocked.Add(ref textChannels, -tc);
                Interlocked.Add(ref voiceChannels, -vc);
            });

            return Task.CompletedTask;
        };

        _client.LeftGuild += g =>
        {
            _ = Task.Run(() =>
            {
                var tc = g.Channels.Count(cx => cx is ITextChannel);
                var vc = g.Channels.Count - tc;
                Interlocked.Add(ref textChannels, -tc);
                Interlocked.Add(ref voiceChannels, -vc);
            });

            return Task.CompletedTask;
        };
    }

    private void InitializeChannelCount()
    {
        var guilds = _client.Guilds;
        textChannels = guilds.Sum(static g => g.Channels.Count(static cx => cx is ITextChannel));
        voiceChannels = guilds.Sum(static g => g.Channels.Count(static cx => cx is IVoiceChannel));
    }

    public async Task OnReadyAsync()
    {
        InitializeChannelCount();

        using var timer = new PeriodicTimer(TimeSpan.FromHours(1));
        do
        {
            if (string.IsNullOrWhiteSpace(_creds.BotListToken))
                continue;

            try
            {
                using var http = _httpFactory.CreateClient();
                using var content = new FormUrlEncodedContent(new Dictionary<string, string>
                {
                    { "shard_count", _creds.TotalShards.ToString() },
                    { "shard_id", _client.ShardId.ToString() },
                    { "server_count", _client.Guilds.Count().ToString() }
                });
                content.Headers.Clear();
                content.Headers.Add("Content-Type", "application/x-www-form-urlencoded");
                http.DefaultRequestHeaders.Add("Authorization", _creds.BotListToken);

                using var res = await http.PostAsync(
                    new Uri($"https://discordbots.org/api/bots/{_client.CurrentUser.Id}/stats"),
                    content);
            }
            catch (Exception ex)
            {
                Log.Error(ex, "Error in botlist post");
            }
        } while (await timer.WaitForNextTickAsync());
    }

    public TimeSpan GetUptime()
        => DateTime.UtcNow - _started;

    public string GetUptimeString(string separator = ", ")
    {
        var time = GetUptime();
        
        if (time.Days > 0)
            return $"{time.Days}d {time.Hours}h {time.Minutes}m";
        
        if (time.Hours > 0)
            return $"{time.Hours}h {time.Minutes}m";
        
        if (time.Minutes > 0)
            return $"{time.Minutes}m {time.Seconds}s";
        
        return $"{time.Seconds}s";
    }
    
    public double GetPrivateMemoryMegabytes()
    {
        _currentProcess.Refresh();
        return _currentProcess.PrivateMemorySize64 / 1.Megabytes();
    }

    public GuildInfo GetGuildInfo(string name)
        => throw new NotImplementedException();

    public GuildInfo GetGuildInfo(ulong id)
    {
        var g = _client.GetGuild(id);
        
        return new GuildInfo()
        {
            Id = g.Id,
            IconUrl = g.IconUrl,
            Name = g.Name,
            Owner = g.Owner.Username,
            OwnerId = g.OwnerId,
            CreatedAt = g.CreatedAt.UtcDateTime,
            VoiceChannels = g.VoiceChannels.Count,
            TextChannels = g.TextChannels.Count,
            Features = g.Features.Value.ToString().Split(","),
            Emojis = g.Emotes.ToArray(),
            Roles = g.Roles.OrderByDescending(x => x.Position).ToArray(),
            MemberCount = g.MemberCount,
        };
    }
}
