package moe.kyokobot.bot;

import com.mewna.catnip.Catnip;
import com.mewna.catnip.CatnipOptions;
import com.mewna.catnip.cache.CacheFlag;
import io.ebean.EbeanServer;
import io.ebean.EbeanServerFactory;
import io.ebean.config.ServerConfig;
import io.ebean.datasource.DataSourceConfig;
import io.vertx.core.Vertx;
import io.vertx.core.VertxOptions;
import moe.kyokobot.bot.command.CommandContextFactory;
import moe.kyokobot.bot.command.CommandManager;
import moe.kyokobot.bot.command.impl.DefaultCommandManager;
import moe.kyokobot.bot.entity.EbeanClassRegistry;
import moe.kyokobot.bot.i18n.I18n;
import moe.kyokobot.bot.module.EventManager;
import moe.kyokobot.bot.module.impl.DefaultModuleManager;
import moe.kyokobot.bot.module.impl.ModuleEventManager;
import moe.kyokobot.bot.module.impl.ModuleManager;
import moe.kyokobot.shared.Settings;
import moe.kyokobot.shared.entity.DatabaseEntity;
import org.codejargon.feather.Feather;
import org.codejargon.feather.Provides;
import org.redisson.Redisson;
import org.redisson.api.RedissonRxClient;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;

import javax.inject.Singleton;
import java.util.Set;

import static com.mewna.catnip.shard.DiscordEvent.Raw;

public class MainModule {
    private static final Logger logger = LoggerFactory.getLogger(MainModule.class);

    @Singleton
    @Provides
    Catnip catnip(Vertx vertx) {
        var settings = Settings.instance();
        var options = new CatnipOptions(settings.apiKeys().get("discord"));
        options.memberChunkTimeout(settings.memberChunkTimeout());
        options.cacheFlags(Set.of(CacheFlag.DROP_GAME_STATUSES));
        options.chunkMembers(settings.memberChunking());
        options.logUncachedPresenceWhenNotChunking(false);
        options.enableGuildSubscriptions(true);
        options.disabledEvents(Set.of(Raw.PRESENCE_UPDATE, "PRESENCES_REPLACE"));
        return Catnip.catnip(options, vertx);
    }

    @Singleton
    @Provides
    CommandManager commandManager(Catnip catnip, CommandContextFactory contextFactory, Feather feather) {
        return new DefaultCommandManager(catnip, contextFactory, feather);
    }

    @Singleton
    @Provides
    EbeanClassRegistry ebeanClassRegistry() {
        return new EbeanClassRegistry();
    }

    @Singleton
    @Provides
    EbeanServer ebeanServer(EbeanClassRegistry registry, ModuleManager moduleManager) {
        var settings = Settings.instance();
        var config = new ServerConfig();
        config.setName("pg");

        var dataSource = new DataSourceConfig()
                .setDriver(settings.jdbcDriverClass())
                .setUrl(settings.databaseUrl())
                .setUsername(settings.jdbcUser())
                .setPassword(settings.jdbcPassword());

        config.setDataSourceConfig(dataSource);
        config.setDefaultServer(true);
        config.setDdlCreateOnly(true);

        registry.get().forEach(className -> {
            try {
                config.addClass(moduleManager.findClass(className.replace("/", ".")).asSubclass(DatabaseEntity.class));
            } catch (Exception e) {
                logger.error("Error registering database entity class: {}", className, e);
            }
        });

        return EbeanServerFactory.create(config);
    }

    @Singleton
    @Provides
    RedissonRxClient redisson(Vertx vertx) {
        try {
            var redisConfig = Settings.instance().configureRedis();
            redisConfig.setEventLoopGroup(vertx.nettyEventLoopGroup());
            return Redisson.createRx(redisConfig);
        } catch (Exception e) {
            logger.error("Cannot create Redis client!", e);
            return null;
        }
    }

    @Singleton
    @Provides
    Globals globals(Catnip catnip) {
        return new Globals(catnip);
    }

    @Singleton
    @Provides
    I18n i18n() {
        return new I18n();
    }

    @Singleton
    @Provides
    ModuleManager moduleManager(CommandManager commandManager, Feather feather, I18n i18n) {
        return new DefaultModuleManager(commandManager, feather, i18n);
    }

    @Singleton
    @Provides
    EventManager eventManager(Catnip catnip) {
        return new ModuleEventManager(catnip);
    }

    @Singleton
    @Provides
    Vertx vertx() {
        var options = new VertxOptions()
                .setEventLoopPoolSize(Settings.instance().eventLoopThreads())
                .setPreferNativeTransport(true);
        return Vertx.vertx(options);
    }
}