using Microsoft.Extensions.Logging;
using SharpChat.Data;
using SharpChat.Messages;
using SharpChat.Users;
using System.Data;
using System.Data.Common;
using System.Data.SQLite;
using System.Text;
using System.Text.Json;
using ZLogger;

namespace SharpChat.SQLite;

public class SQLiteMessageStorage(ILogger logger, SQLiteConnection conn) : MessageStorage {
    public async Task LogMessage(Message msg) {
        try {
            await conn.RunCommand(
                "INSERT OR IGNORE INTO messages (msg_id, msg_type, msg_created, msg_channel, msg_sender, msg_sender_name, msg_sender_colour, msg_sender_rank, msg_sender_nick, msg_sender_perms, msg_data)"
                + " VALUES (@id, @type, @created, @channel, @sender, @sender_name, @sender_colour, @sender_rank, @sender_nick, @sender_perms, @data)",
                new SQLiteParameter("id", msg.Id),
                new SQLiteParameter("type", msg.Type),
                new SQLiteParameter("channel", string.IsNullOrWhiteSpace(msg.ChannelName) ? null : msg.ChannelName),
                new SQLiteParameter("data", JsonSerializer.SerializeToUtf8Bytes(msg.Data)),
                new SQLiteParameter("sender", long.TryParse(msg.SenderId, out long senderId64) && senderId64 > 0 ? senderId64 : null),
                new SQLiteParameter("sender_name", msg.SenderName),
                new SQLiteParameter("sender_colour", msg.SenderColour.Rgb.HasValue ? msg.SenderColour.Rgb.Value.Raw : null),
                new SQLiteParameter("sender_rank", msg.SenderRank),
                new SQLiteParameter("sender_nick", string.IsNullOrWhiteSpace(msg.SenderNickName) ? null : msg.SenderNickName),
                new SQLiteParameter("sender_perms", SQLiteUserPermissionsConverter.To(msg.SenderPermissions)),
                new SQLiteParameter("created", $"{msg.Created:s}Z"),
                new SQLiteParameter("deleted", msg.Deleted is null ? null : $"{msg.Deleted:s}Z")
            );
        } catch(SQLiteException ex) {
            logger.ZLogError($"Error in LogMessage(Message): {ex}");
        }
    }

    public async Task LogMessage(
        long id,
        string type,
        string channelName,
        string senderId,
        string senderName,
        ColourInheritable senderColour,
        int senderRank,
        string senderNick,
        UserPermissions senderPerms,
        object? data = null
    ) {
        try {
            await conn.RunCommand(
                "INSERT INTO messages (msg_id, msg_type, msg_created, msg_channel, msg_sender, msg_sender_name, msg_sender_colour, msg_sender_rank, msg_sender_nick, msg_sender_perms, msg_data)"
                + " VALUES (@id, @type, @created, @channel, @sender, @sender_name, @sender_colour, @sender_rank, @sender_nick, @sender_perms, @data)",
                new SQLiteParameter("id", id),
                new SQLiteParameter("type", type),
                new SQLiteParameter("created", $"{DateTimeOffset.UtcNow:s}Z"),
                new SQLiteParameter("channel", string.IsNullOrWhiteSpace(channelName) ? null : channelName),
                new SQLiteParameter("sender", long.TryParse(senderId, out long senderId64) && senderId64 > 0 ? senderId64 : null),
                new SQLiteParameter("sender_name", senderName),
                new SQLiteParameter("sender_colour", senderColour.Rgb.HasValue ? senderColour.Rgb.Value.Raw : null),
                new SQLiteParameter("sender_rank", senderRank),
                new SQLiteParameter("sender_nick", string.IsNullOrWhiteSpace(senderNick) ? null : senderNick),
                new SQLiteParameter("sender_perms", SQLiteUserPermissionsConverter.To(senderPerms)),
                new SQLiteParameter("data", data == null ? "{}" : JsonSerializer.SerializeToUtf8Bytes(data))
            );
        } catch(SQLiteException ex) {
            logger.ZLogError($"Error in LogMessage(long, ...): {ex}");
        }
    }

    public async Task DeleteMessage(Message msg) {
        try {
            await conn.RunCommand(
                "UPDATE IGNORE messages SET msg_deleted = NOW() WHERE msg_id = @id AND msg_deleted IS NULL",
                new SQLiteParameter("id", msg.Id)
            );
        } catch(SQLiteException ex) {
            logger.ZLogError($"Error in DeleteMessage(): {ex}");
        }
    }

    private static Message ReadMessage(DbDataReader reader) {
        return new Message(
            reader.GetInt64("msg_id"),
            reader.GetString("msg_type"),
            reader.IsDBNull(reader.GetOrdinal("msg_sender")) ? null : reader.GetString("msg_sender"),
            reader.IsDBNull(reader.GetOrdinal("msg_sender_name")) ? string.Empty : reader.GetString("msg_sender_name"),
            reader.IsDBNull(reader.GetOrdinal("msg_sender_colour")) ? ColourInheritable.None : ColourInheritable.FromRaw((int)reader.GetInt64("msg_sender_colour")),
            (int)reader.GetInt64("msg_sender_rank"),
            SQLiteUserPermissionsConverter.From((SQLiteUserPermissions)reader.GetInt64("msg_sender_perms")),
            reader.IsDBNull(reader.GetOrdinal("msg_sender_nick")) ? string.Empty : reader.GetString("msg_sender_nick"),
            DateTimeOffset.Parse(reader.GetString("msg_created")),
            reader.IsDBNull(reader.GetOrdinal("msg_deleted")) ? null : DateTimeOffset.Parse(reader.GetString("msg_deleted")),
            reader.IsDBNull(reader.GetOrdinal("msg_channel")) ? null : reader.GetString("msg_channel"),
            JsonDocument.Parse(reader.GetString("msg_data"))
        );
    }

    public async Task<Message?> GetMessage(long id) {
        try {
            using DbDataReader? reader = await conn.RunQuery(
                "SELECT msg_id, msg_type, msg_created, msg_deleted, msg_channel, msg_sender, msg_sender_name, msg_sender_colour, msg_sender_rank, msg_sender_nick, msg_sender_perms, msg_data"
                + " FROM messages WHERE msg_id = @id",
                new SQLiteParameter("id", id)
            );

            return reader?.Read() == true ? ReadMessage(reader) : null;
        } catch(SQLiteException ex) {
            logger.ZLogError($"Error in GetMessage(): {ex}");
            return null;
        }
    }

    public async Task<long> CountMessages(
        string? channelName = null,
        bool includeDeleted = false
    ) {
        List<SQLiteParameter> parameters = [];
        bool firstParam = true;
        StringBuilder qb = new();
        qb.Append("SELECT COUNT(*) FROM messages");

        if(!includeDeleted) {
            firstParam = false;
            qb.Append(" WHERE msg_deleted IS NULL");
        }

        if(!string.IsNullOrEmpty(channelName)) {
            qb.AppendFormat(" {0} (msg_channel = @channel OR msg_channel IS NULL)", firstParam ? "WHERE" : "AND");
            parameters.Add(new SQLiteParameter("channel", channelName));
        }

        try {
            return await conn.RunQueryValue<long>(qb.ToString(), [.. parameters]);
        } catch(SQLiteException ex) {
            logger.ZLogError($"Error in CountMessages({channelName}, {includeDeleted}): {ex}");
            return 0;
        }
    }

    public async Task<IEnumerable<Message>> GetMessages(
        string? channelName = null,
        int? take = 20,
        long? beforeId = null,
        bool includeDeleted = false
    ) {
        List<SQLiteParameter> parameters = [];
        bool firstParam = true;
        StringBuilder qb = new();
        qb.Append("SELECT msg_id, msg_type, msg_created, msg_deleted, msg_channel, msg_data");
        qb.Append(", msg_sender, msg_sender_name, msg_sender_colour, msg_sender_rank, msg_sender_nick, msg_sender_perms");
        qb.Append(" FROM messages");

        if(!includeDeleted) {
            firstParam = false;
            qb.Append(" WHERE msg_deleted IS NULL");
        }

        if(!string.IsNullOrEmpty(channelName)) {
            qb.AppendFormat(" {0} (msg_channel = @channel OR msg_channel IS NULL)", firstParam ? "WHERE" : "AND");
            parameters.Add(new SQLiteParameter("channel", channelName));
            firstParam = false;
        }

        if(beforeId.HasValue) {
            qb.AppendFormat(" {0} msg_id < @before", firstParam ? "WHERE" : "AND");
            parameters.Add(new SQLiteParameter("before", beforeId.Value));
        }

        qb.Append(" ORDER BY msg_id DESC");

        if(take.HasValue) {
            qb.Append(" LIMIT @take");
            parameters.Add(new SQLiteParameter("take", take.Value));
        }

        string query = string.Format("SELECT * FROM ({0}) AS _ ORDER BY msg_id ASC", qb);

        try {
            DbDataReader? reader = await conn.RunQuery(query, [.. parameters]);
            return reader is null ? [] : new DbObjectEnumerable<Message>(reader, ReadMessage);
        } catch(SQLiteException ex) {
            logger.ZLogError($"Error in GetMessages({channelName}, {take}, {beforeId}, {includeDeleted}): {ex}");
            return [];
        }
    }
}