using App.Metrics;

using Autofac;

using Myriad.Cache;
using Myriad.Gateway;
using Myriad.Rest;

using NodaTime;

using PluralKit.Core;

using Sentry;

using Serilog;

using IClock = NodaTime.IClock;

namespace PluralKit.Bot;

public class BotModule: Module
{
    protected override void Load(ContainerBuilder builder)
    {
        // Clients
        builder.Register(c =>
        {
            var botConfig = c.Resolve<BotConfig>();
            return new GatewaySettings
            {
                Token = botConfig.Token,
                MaxShardConcurrency = botConfig.MaxShardConcurrency,
                GatewayQueueUrl = botConfig.GatewayQueueUrl,
                UseRedisRatelimiter = botConfig.UseRedisRatelimiter,
                Intents = GatewayIntent.Guilds |
                          GatewayIntent.DirectMessages |
                          GatewayIntent.DirectMessageReactions |
                          GatewayIntent.GuildEmojis |
                          GatewayIntent.GuildMessages |
                          GatewayIntent.GuildWebhooks |
                          GatewayIntent.GuildMessageReactions |
                          GatewayIntent.MessageContent
            };
        }).AsSelf().SingleInstance();
        builder.RegisterType<Cluster>().AsSelf().SingleInstance();
        builder.RegisterType<RedisGatewayService>().AsSelf().SingleInstance();
        builder.Register<IDiscordCache>(c =>
        {
            var botConfig = c.Resolve<BotConfig>();

            if (botConfig.UseRedisCache)
                return new RedisDiscordCache(c.Resolve<ILogger>(), botConfig.ClientId);
            return new MemoryDiscordCache(botConfig.ClientId);
        }).AsSelf().SingleInstance();
        builder.RegisterType<PrivateChannelService>().AsSelf().SingleInstance();

        builder.Register(c =>
        {
            var client = new DiscordApiClient(
                c.Resolve<BotConfig>().Token,
                c.Resolve<ILogger>(),
                c.Resolve<BotConfig>().DiscordBaseUrl
            );

            var metrics = c.Resolve<IMetrics>();

            client.OnResponseEvent += (_, ev) =>
            {
                var (endpoint, statusCode, ticks) = ev;
                var timer = metrics.Provider.Timer.Instance(BotMetrics.DiscordApiRequests, new MetricTags(
                    new[] { "endpoint", "status_code" },
                    new[] { endpoint, statusCode.ToString() }
                ));
                timer.Record(ticks / 10, TimeUnit.Microseconds);
            };

            return client;
        }).AsSelf().SingleInstance();

        // Commands
        builder.RegisterType<CommandTree>().AsSelf();
        builder.RegisterType<Admin>().AsSelf();
        builder.RegisterType<Api>().AsSelf();
        builder.RegisterType<Autoproxy>().AsSelf();
        builder.RegisterType<Checks>().AsSelf();
        builder.RegisterType<Config>().AsSelf();
        builder.RegisterType<Fun>().AsSelf();
        builder.RegisterType<Groups>().AsSelf();
        builder.RegisterType<GroupMember>().AsSelf();
        builder.RegisterType<Help>().AsSelf();
        builder.RegisterType<ImportExport>().AsSelf();
        builder.RegisterType<Member>().AsSelf();
        builder.RegisterType<MemberAvatar>().AsSelf();
        builder.RegisterType<MemberEdit>().AsSelf();
        builder.RegisterType<MemberProxy>().AsSelf();
        builder.RegisterType<Misc>().AsSelf();
        builder.RegisterType<ProxiedMessage>().AsSelf();
        builder.RegisterType<Random>().AsSelf();
        builder.RegisterType<ServerConfig>().AsSelf();
        builder.RegisterType<Switch>().AsSelf();
        builder.RegisterType<System>().AsSelf();
        builder.RegisterType<SystemEdit>().AsSelf();
        builder.RegisterType<SystemFront>().AsSelf();
        builder.RegisterType<SystemLink>().AsSelf();
        builder.RegisterType<SystemList>().AsSelf();

        // Bot core
        builder.RegisterType<Bot>().AsSelf().SingleInstance();
        builder.RegisterType<MessageCreated>().As<IEventHandler<MessageCreateEvent>>();
        builder.RegisterType<MessageDeleted>().As<IEventHandler<MessageDeleteEvent>>()
            .As<IEventHandler<MessageDeleteBulkEvent>>();
        builder.RegisterType<MessageEdited>().As<IEventHandler<MessageUpdateEvent>>();
        builder.RegisterType<ReactionAdded>().As<IEventHandler<MessageReactionAddEvent>>();
        builder.RegisterType<InteractionCreated>().As<IEventHandler<InteractionCreateEvent>>();

        // Event handler queue
        builder.RegisterType<HandlerQueue<MessageCreateEvent>>().AsSelf().SingleInstance();
        builder.RegisterType<HandlerQueue<MessageReactionAddEvent>>().AsSelf().SingleInstance();

        // Bot services
        builder.RegisterType<EmbedService>().AsSelf().SingleInstance();
        builder.RegisterType<ProxyService>().AsSelf().SingleInstance();
        builder.RegisterType<LogChannelService>().AsSelf().SingleInstance();
        builder.RegisterType<DataFileService>().AsSelf().SingleInstance();
        builder.RegisterType<WebhookExecutorService>().AsSelf().SingleInstance();
        builder.RegisterType<WebhookCacheService>().AsSelf().SingleInstance();
        builder.RegisterType<ShardInfoService>().AsSelf().SingleInstance();
        builder.RegisterType<CpuStatService>().AsSelf().SingleInstance();
        builder.RegisterType<PeriodicStatCollector>().AsSelf().SingleInstance();
        builder.RegisterType<LastMessageCacheService>().AsSelf().SingleInstance();
        builder.RegisterType<LoggerCleanService>().AsSelf().SingleInstance();
        builder.RegisterType<ErrorMessageService>().AsSelf().SingleInstance();
        builder.RegisterType<CommandMessageService>().AsSelf().SingleInstance();
        builder.RegisterType<InteractionDispatchService>().AsSelf().SingleInstance();

        // Sentry stuff
        builder.Register(_ => new Scope(null)).AsSelf().InstancePerLifetimeScope();
        builder.RegisterType<SentryEnricher>()
            .As<ISentryEnricher<MessageCreateEvent>>()
            .As<ISentryEnricher<MessageDeleteEvent>>()
            .As<ISentryEnricher<MessageUpdateEvent>>()
            .As<ISentryEnricher<MessageDeleteBulkEvent>>()
            .As<ISentryEnricher<MessageReactionAddEvent>>()
            .SingleInstance();

        // Proxy stuff
        builder.RegisterType<ProxyMatcher>().AsSelf().SingleInstance();
        builder.RegisterType<ProxyTagParser>().AsSelf().SingleInstance();

        // Utils
        builder.Register(c => new HttpClient
        {
            Timeout = TimeSpan.FromSeconds(5),
            DefaultRequestHeaders = { { "User-Agent", DiscordApiClient.UserAgent } }
        }).AsSelf().SingleInstance();
        builder.RegisterInstance(SystemClock.Instance).As<IClock>();
        builder.RegisterType<SerilogGatewayEnricherFactory>().AsSelf().SingleInstance();
    }
}