using MySqlConnector;
using System;
using System.Collections.Generic;
using System.Text;
using System.Text.Json;

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

        public void AddEvent(
            long id, string type,
            string channelName,
            long senderId, string senderName, ChatColour senderColour, int senderRank, string senderNick, ChatUserPermissions senderPerms,
            object data = null,
            StoredEventFlags flags = StoredEventFlags.None
        ) {
            ArgumentNullException.ThrowIfNull(type);

            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", senderId < 1 ? null : senderId),
                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 long AddEvent(string type, ChatUser user, ChatChannel channel, object data = null, StoredEventFlags flags = StoredEventFlags.None) {
            ArgumentNullException.ThrowIfNull(type);

            long id = SharpId.Next();

            AddEvent(
                id, type,
                channel?.Name,
                user?.UserId ?? 0,
                user?.UserName ?? string.Empty,
                user?.Colour ?? ChatColour.None,
                user?.Rank ?? 0,
                user?.NickName,
                user?.Permissions ?? 0,
                data,
                flags
            );

            return id;
        }

        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)
                );

                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 ChatUser(
                    reader.GetInt64("event_sender"),
                    reader.IsDBNull(reader.GetOrdinal("event_sender_name")) ? string.Empty : reader.GetString("event_sender_name"),
                    ChatColour.FromMisuzu(reader.GetInt32("event_sender_colour")),
                    reader.GetInt32("event_sender_rank"),
                    (ChatUserPermissions)reader.GetInt32("event_sender_perms"),
                    reader.IsDBNull(reader.GetOrdinal("event_sender_nick")) ? null : 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)
                );

                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)
            );
        }
    }
}