using Microsoft.Data.Sqlite;

namespace NatsNet.PortTracker.Data;

public sealed class Database : IDisposable
{
    private readonly SqliteConnection _connection;

    public Database(string dbPath)
    {
        var connectionString = new SqliteConnectionStringBuilder
        {
            DataSource = dbPath,
            Mode = SqliteOpenMode.ReadWriteCreate,
            ForeignKeys = true
        }.ToString();

        _connection = new SqliteConnection(connectionString);
        _connection.Open();

        using var cmd = _connection.CreateCommand();
        cmd.CommandText = "PRAGMA journal_mode=WAL;";
        cmd.ExecuteNonQuery();
    }

    public SqliteConnection Connection => _connection;

    public SqliteCommand CreateCommand(string sql)
    {
        var cmd = _connection.CreateCommand();
        cmd.CommandText = sql;
        return cmd;
    }

    public int Execute(string sql, params (string name, object? value)[] parameters)
    {
        using var cmd = CreateCommand(sql);
        foreach (var (name, value) in parameters)
            cmd.Parameters.AddWithValue(name, value ?? DBNull.Value);
        return cmd.ExecuteNonQuery();
    }

    public T? ExecuteScalar<T>(string sql, params (string name, object? value)[] parameters)
    {
        using var cmd = CreateCommand(sql);
        foreach (var (name, value) in parameters)
            cmd.Parameters.AddWithValue(name, value ?? DBNull.Value);
        var result = cmd.ExecuteScalar();
        if (result is null or DBNull) return default;
        return (T)Convert.ChangeType(result, typeof(T));
    }

    public List<Dictionary<string, object?>> Query(string sql, params (string name, object? value)[] parameters)
    {
        using var cmd = CreateCommand(sql);
        foreach (var (name, value) in parameters)
            cmd.Parameters.AddWithValue(name, value ?? DBNull.Value);

        var results = new List<Dictionary<string, object?>>();
        using var reader = cmd.ExecuteReader();
        while (reader.Read())
        {
            var row = new Dictionary<string, object?>();
            for (int i = 0; i < reader.FieldCount; i++)
            {
                row[reader.GetName(i)] = reader.IsDBNull(i) ? null : reader.GetValue(i);
            }
            results.Add(row);
        }
        return results;
    }

    public int ExecuteInTransaction(string sql, params (string name, object? value)[] parameters)
    {
        using var transaction = _connection.BeginTransaction();
        try
        {
            using var cmd = CreateCommand(sql);
            cmd.Transaction = transaction;
            foreach (var (name, value) in parameters)
                cmd.Parameters.AddWithValue(name, value ?? DBNull.Value);
            var affected = cmd.ExecuteNonQuery();
            transaction.Commit();
            return affected;
        }
        catch
        {
            transaction.Rollback();
            throw;
        }
    }

    public void Dispose()
    {
        _connection.Dispose();
    }
}
