using MySqlConnector;
using System.Text;
using System.Text.Json;

namespace SharpChat.EventStorage;

public partial class MariaDBEventStorage(string connString) : EventStorage {
    private string ConnectionString { get; } = connString ?? throw new ArgumentNullException(nameof(connString));

    public void AddEvent(
        long id,
        string type,
        string channelName,
        string senderId,
        string senderName,
        ColourInheritable senderColour,
        int senderRank,
        string senderNick,
        UserPermissions senderPerms,
        object? data = null,
        StoredEventFlags flags = StoredEventFlags.None
    ) {
        RunCommand(
            "INSERT INTO `sqc_events` (`event_id`, `event_created`, `event_type`, `event_target`, `event_flags`, `event_data`"
            + ", `event_sender`, `event_sender_name`, `event_sender_colour`, `event_sender_rank`, `event_sender_nick`, `event_sender_perms`)"
            + " VALUES (@id, NOW(), @type, @target, @flags, @data"
            + ", @sender, @sender_name, @sender_colour, @sender_rank, @sender_nick, @sender_perms)",
            new MySqlParameter("id", id),
            new MySqlParameter("type", type),
            new MySqlParameter("target", string.IsNullOrWhiteSpace(channelName) ? null : channelName),
            new MySqlParameter("flags", (byte)flags),
            new MySqlParameter("data", data == null ? "{}" : JsonSerializer.SerializeToUtf8Bytes(data)),
            new MySqlParameter("sender", long.TryParse(senderId, out long senderId64) && senderId64 > 0 ? senderId64 : null),
            new MySqlParameter("sender_name", senderName),
            new MySqlParameter("sender_colour", senderColour.ToMisuzu()),
            new MySqlParameter("sender_rank", senderRank),
            new MySqlParameter("sender_nick", string.IsNullOrWhiteSpace(senderNick) ? null : senderNick),
            new MySqlParameter("sender_perms", senderPerms)
        );
    }

    public StoredEventInfo? GetEvent(long seqId) {
        try {
            using MySqlDataReader? reader = RunQuery(
                "SELECT `event_id`, `event_type`, `event_flags`, `event_data`, `event_target`"
                + ", `event_sender`, `event_sender_name`, `event_sender_colour`, `event_sender_rank`, `event_sender_nick`, `event_sender_perms`"
                + ", UNIX_TIMESTAMP(`event_created`) AS `event_created`"
                + ", UNIX_TIMESTAMP(`event_deleted`) AS `event_deleted`"
                + " FROM `sqc_events`"
                + " WHERE `event_id` = @id",
                new MySqlParameter("id", seqId)
            );

            if(reader is null)
                return null;

            while(reader.Read()) {
                StoredEventInfo evt = ReadEvent(reader);
                if(evt != null)
                    return evt;
            }
        } catch(MySqlException ex) {
            Logger.Write(ex);
        }

        return null;
    }

    private static StoredEventInfo ReadEvent(MySqlDataReader reader) {
        return new StoredEventInfo(
            reader.GetInt64("event_id"),
            Encoding.ASCII.GetString((byte[])reader["event_type"]),
            reader.IsDBNull(reader.GetOrdinal("event_sender")) ? null : new User(
                reader.GetInt64("event_sender").ToString(),
                reader.IsDBNull(reader.GetOrdinal("event_sender_name")) ? string.Empty : reader.GetString("event_sender_name"),
                ColourInheritable.FromMisuzu(reader.GetInt32("event_sender_colour")),
                reader.GetInt32("event_sender_rank"),
                (UserPermissions)reader.GetInt32("event_sender_perms"),
                reader.IsDBNull(reader.GetOrdinal("event_sender_nick")) ? string.Empty : reader.GetString("event_sender_nick")
            ),
            DateTimeOffset.FromUnixTimeSeconds(reader.GetInt32("event_created")),
            reader.IsDBNull(reader.GetOrdinal("event_deleted")) ? null : DateTimeOffset.FromUnixTimeSeconds(reader.GetInt32("event_deleted")),
            reader.IsDBNull(reader.GetOrdinal("event_target")) ? null : Encoding.ASCII.GetString((byte[])reader["event_target"]),
            JsonDocument.Parse(Encoding.ASCII.GetString((byte[])reader["event_data"])),
            (StoredEventFlags)reader.GetByte("event_flags")
        );
    }

    public IEnumerable<StoredEventInfo> GetChannelEventLog(string channelName, int amount = 20, int offset = 0) {
        List<StoredEventInfo> events = [];

        try {
            using MySqlDataReader? reader = RunQuery(
                "SELECT `event_id`, `event_type`, `event_flags`, `event_data`, `event_target`"
                + ", `event_sender`, `event_sender_name`, `event_sender_colour`, `event_sender_rank`, `event_sender_nick`, `event_sender_perms`"
                + ", UNIX_TIMESTAMP(`event_created`) AS `event_created`"
                + ", UNIX_TIMESTAMP(`event_deleted`) AS `event_deleted`"
                + " FROM `sqc_events`"
                + " WHERE `event_deleted` IS NULL AND (`event_target` = @target OR `event_target` IS NULL)"
                + " AND `event_id` > @offset"
                + " ORDER BY `event_id` DESC"
                + " LIMIT @amount",
                new MySqlParameter("target", channelName),
                new MySqlParameter("amount", amount),
                new MySqlParameter("offset", offset)
            );
            if(reader is null)
                return events;

            while(reader.Read()) {
                StoredEventInfo evt = ReadEvent(reader);
                if(evt != null)
                    events.Add(evt);
            }
        } catch(MySqlException ex) {
            Logger.Write(ex);
        }

        events.Reverse();

        return events;
    }

    public void RemoveEvent(StoredEventInfo evt) {
        ArgumentNullException.ThrowIfNull(evt);
        RunCommand(
            "UPDATE IGNORE `sqc_events` SET `event_deleted` = NOW() WHERE `event_id` = @id AND `event_deleted` IS NULL",
            new MySqlParameter("id", evt.Id)
        );
    }
}