using Microsoft.Extensions.Logging;
using SharpChat;
using SharpChat.Configuration;
using SharpChat.Flashii;
using SharpChat.MariaDB;
using SharpChat.SQLite;
using System.Text;
using ZLogger;
using ZLogger.Providers;

const string CONFIG = "sharpchat.cfg";

Console.WriteLine(@"   _____ __                     ________          __ ");
Console.WriteLine(@"  / ___// /_  ____ __________  / ____/ /_  ____ _/ /_");
Console.WriteLine(@"  \__ \/ __ \/ __ `/ ___/ __ \/ /   / __ \/ __ `/ __/");
Console.WriteLine(@" ___/ / / / / /_/ / /  / /_/ / /___/ / / / /_/ / /_  ");
Console.WriteLine(@"/____/_/ /_/\__,_/_/  / .___/\____/_/ /_/\__,_/\__/  ");
/**/Console.Write(@"                     /__/");
if(SharpInfo.IsDebugBuild) {
    Console.WriteLine();
    Console.Write(@"== ");
    Console.Write(SharpInfo.VersionString);
    Console.WriteLine(@" == DBG ==");
} else
    Console.WriteLine(SharpInfo.VersionStringShort.PadLeft(28, ' '));

using ILoggerFactory logFactory = LoggerFactory.Create(logging => {
    logging.ClearProviders();

#if DEBUG
    logging.SetMinimumLevel(LogLevel.Trace);
#else
    logging.SetMinimumLevel(LogLevel.Information);
#endif

    logging.AddZLoggerConsole(opts => {
        opts.OutputEncodingToUtf8 = true;
        opts.UsePlainTextFormatter(formatter => {
            formatter.SetPrefixFormatter($"{0} [{1} {2}] ", (in MessageTemplate template, in LogInfo info) => template.Format(info.Timestamp, info.Category, info.LogLevel));
        });
    });
    logging.AddZLoggerRollingFile(opts => {
        opts.FilePathSelector = (ts, seqNo) => $"logs/{ts.ToLocalTime():yyyy-MM-dd_HH-mm-ss}_{seqNo:000}.json";
        opts.RollingInterval = RollingInterval.Day;
        opts.RollingSizeKB = 1024;
        opts.FileShared = true;
        opts.UseJsonFormatter(formatter => {
            formatter.UseUtcTimestamp = true;
        });
    });
});

ILogger logger = logFactory.CreateLogger("main");

using CancellationTokenSource cts = new();

void exitHandler() {
    if(cts.IsCancellationRequested)
        return;

    try {
        cts.Cancel();
        logger.ZLogInformation($"Shutdown requested through console...");
    } catch(ObjectDisposedException) { }
}

AppDomain.CurrentDomain.ProcessExit += (sender, ev) => { exitHandler(); };
Console.CancelKeyPress += (sender, ev) => { ev.Cancel = true; exitHandler(); };

if(cts.IsCancellationRequested) return;

string configFile = CONFIG;

// If the config file doesn't exist and we're using the default path, run the converter
if(!File.Exists(configFile) && configFile == CONFIG) {
    logger.ZLogInformation($"Creating example configuration file...");

    using Stream s = new FileStream(CONFIG, FileMode.OpenOrCreate, FileAccess.ReadWrite, FileShare.None);
    s.SetLength(0);
    s.Flush();

    using StreamWriter sw = new(s, new UTF8Encoding(false));
    sw.WriteLine("# and ; can be used at the start of a line for comments.");
    sw.WriteLine();

    sw.WriteLine("# General Configuration");
    sw.WriteLine($"#chat:msgMaxLength    {Context.DEFAULT_MSG_LENGTH_MAX}");
    sw.WriteLine($"#chat:connMaxCount    {Context.DEFAULT_MAX_CONNECTIONS}");
    sw.WriteLine($"#chat:floodKickLength {Context.DEFAULT_FLOOD_KICK_LENGTH}");

    sw.WriteLine("# Sock Chat Configuration");
    sw.WriteLine($"#sockchat:port {SockChatServer.DEFAULT_PORT}");

    sw.WriteLine();
    sw.WriteLine("# Channels");
    sw.WriteLine("chat:channels lounge staff");
    sw.WriteLine();

    sw.WriteLine("# Lounge channel settings");
    sw.WriteLine("chat:channels:lounge:name Lounge");
    sw.WriteLine("chat:channels:lounge:autoJoin true");
    sw.WriteLine();

    sw.WriteLine("# Staff channel settings");
    sw.WriteLine("chat:channels:staff:name Staff");
    sw.WriteLine("chat:channels:staff:minRank 5");


    const string msz_secret = "login_key.txt";
    const string msz_url = "msz_url.txt";

    sw.WriteLine();
    sw.WriteLine("# Misuzu integration settings");
    if(File.Exists(msz_secret))
        sw.WriteLine(string.Format("msz:secret {0}", File.ReadAllText(msz_secret).Trim()));
    else
        sw.WriteLine("#msz:secret woomy");
    if(File.Exists(msz_url))
        sw.WriteLine(string.Format("msz:url    {0}/_sockchat", File.ReadAllText(msz_url).Trim()));
    else
        sw.WriteLine("#msz:url    https://flashii.net/_sockchat");


    const string mdb_config = @"mariadb.txt";
    string[] mdbCfg = File.Exists(mdb_config) ? File.ReadAllLines(mdb_config) : [];

    sw.WriteLine();
    sw.WriteLine("# MariaDB configuration");
    if(mdbCfg.Length > 0)
        sw.WriteLine($"mariadb:host {mdbCfg[0]}");
    else
        sw.WriteLine($"#mariadb:host <username>");
    if(mdbCfg.Length > 1)
        sw.WriteLine($"mariadb:user {mdbCfg[1]}");
    else
        sw.WriteLine($"#mariadb:user <username>");
    if(mdbCfg.Length > 2)
        sw.WriteLine($"mariadb:pass {mdbCfg[2]}");
    else
        sw.WriteLine($"#mariadb:pass <password>");
    if(mdbCfg.Length > 3)
        sw.WriteLine($"mariadb:db   {mdbCfg[3]}");
    else
        sw.WriteLine($"#mariadb:db   <database>");

    sw.Flush();
}

logger.ZLogInformation($"Initialising configuration...");
using StreamConfig config = StreamConfig.FromPath(configFile);

if(cts.IsCancellationRequested) return;

if(args.Contains("--migrate-storage") || args.Contains("--convert-db")) {
    MariaDBStorage mariadb = new(logFactory.CreateLogger("mariadb"), MariaDBStorage.BuildConnectionString(config.ScopeTo("mariadb")));
    using SQLiteStorage sqlite = new(logFactory.CreateLogger("sqlite"), SQLiteStorage.BuildConnectionString(config.ScopeTo("sqlite"), false));
    await new StorageMigrator(logFactory.CreateLogger("migrate"), sqlite, mariadb).Migrate(cts.Token);
    return;
}

logger.ZLogInformation($"Initialising HTTP client...");
using HttpClient httpClient = new(new HttpClientHandler() {
    UseProxy = false,
});
httpClient.DefaultRequestHeaders.Add("User-Agent", SharpInfo.ProgramName);

if(cts.IsCancellationRequested) return;

logger.ZLogInformation($"Initialising Flashii client...");
FlashiiClient flashii = new(logFactory.CreateLogger("flashii"), httpClient, config.ScopeTo("msz"));

if(cts.IsCancellationRequested) return;

logger.ZLogInformation($"Initialising storage...");
Storage storage = string.IsNullOrWhiteSpace(config.SafeReadValue("mariadb:host", string.Empty))
    ? new SQLiteStorage(logFactory.CreateLogger("sqlite"), SQLiteStorage.BuildConnectionString(config.ScopeTo("sqlite")))
    : new MariaDBStorage(logFactory.CreateLogger("mariadb"), MariaDBStorage.BuildConnectionString(config.ScopeTo("mariadb")));

try {
    if(cts.IsCancellationRequested) return;

    await storage.UpgradeStorage();

    if(cts.IsCancellationRequested) return;

    logger.ZLogDebug($"Creating context...");
    Config chatConfig = config.ScopeTo("chat");
    Context ctx = new(logFactory, chatConfig, storage, flashii, flashii);

    if(cts.IsCancellationRequested) return;

    logger.ZLogInformation($"Preparing server...");
    await new SockChatServer(
        cts,
        ctx,
        config.ScopeTo("sockchat")
    ).Listen(cts.Token);
} finally {
    if(storage is IDisposable disp) {
        logger.ZLogInformation($"Cleaning storage...");
        disp.Dispose();
    }
}

logger.ZLogInformation($"Exiting...");