using System.Data;
using Dapper;
using Foxnouns.DataMigrator.Models;
using Newtonsoft.Json;
using Npgsql;

namespace Foxnouns.DataMigrator;

public static class GoDatabase
{
    private static NpgsqlDataSource? _dataSource;

    public static async Task<NpgsqlConnection> GetConnectionAsync()
    {
        if (_dataSource != null)
            return await _dataSource.OpenConnectionAsync();

        DefaultTypeMap.MatchNamesWithUnderscores = true;

        SqlMapper.RemoveTypeMap(typeof(ulong));
        SqlMapper.AddTypeHandler(new UlongEncodeAsLongHandler());
        SqlMapper.AddTypeHandler(new JsonTypeHandler<GoFieldEntry[]>());
        SqlMapper.AddTypeHandler(new JsonTypeHandler<Dictionary<string, GoCustomPreference>>());
        SqlMapper.AddTypeHandler(new JsonTypeHandler<GoPronounEntry[]>());
        SqlMapper.AddTypeHandler(new UlongListHandler());

        string dsn =
            Environment.GetEnvironmentVariable("GO_DATABASE")
            ?? throw new Exception("$GO_DATABASE is not set");

        var dataSourceBuilder = new NpgsqlDataSourceBuilder(dsn);
        dataSourceBuilder.UseJsonNet();

        _dataSource = dataSourceBuilder.Build();

        return await _dataSource.OpenConnectionAsync();
    }

    // dapper why
    // taken from https://codeberg.org/starshine/catalogger/src/branch/main/Catalogger.Backend/Database/DatabasePool.cs
    private class UlongEncodeAsLongHandler : SqlMapper.TypeHandler<ulong>
    {
        public override void SetValue(IDbDataParameter parameter, ulong value) =>
            parameter.Value = (long)value;

        public override ulong Parse(object value) =>
            // Cast to long to unbox, then to ulong (???)
            (ulong)(long)value;
    }

    private class UlongListHandler : SqlMapper.TypeHandler<List<ulong>>
    {
        public override void SetValue(IDbDataParameter parameter, List<ulong>? value) =>
            parameter.Value = value?.Select(i => (long)i).ToArray();

        public override List<ulong>? Parse(object value) =>
            ((long[])value).Select(i => (ulong)i).ToList();
    }

    private class JsonTypeHandler<T> : SqlMapper.TypeHandler<T>
    {
        public override void SetValue(IDbDataParameter parameter, T? value) =>
            parameter.Value = JsonConvert.SerializeObject(value);

        public override T? Parse(object value)
        {
            var json = (string)value;
            return JsonConvert.DeserializeObject<T>(json) ?? default;
        }
    }
}