using System.Text.Json;
using ClawdDotNet.Core.Tools;
using Microsoft.Extensions.Logging;
using TL;

namespace ClawdDotNet.Tools.TelegramClient;

public sealed class TelegramClientTool : IAgentTool
{
    private readonly TelegramClientManager _tg;

    public TelegramClientTool(TelegramClientManager tg) => _tg = tg;

    public string Name => "TelegramClient";

    public string Description => """
        Liest Nachrichten aus dem persönlichen Telegram-Account des Nutzers.
        Zugriff auf alle Chats, Gruppen und Kanäle in denen der Nutzer Mitglied ist.
        NUR LESEN — kein Senden, kein Löschen, kein Bearbeiten.
        Aktionen: list_chats, read_messages, read_new
        """;

    public JsonElement InputSchema { get; } = JsonDocument.Parse("""
        {
            "type": "object",
            "required": ["action"],
            "properties": {
                "action": {
                    "type": "string",
                    "enum": ["list_chats", "read_messages", "read_new"],
                    "description": "list_chats: alle Chats/Gruppen/Kanäle auflisten. read_messages: letzte N Nachrichten aus einem Chat lesen. read_new: nur neue Nachrichten seit letztem Abruf."
                },
                "chatId": {
                    "type": "integer",
                    "description": "Chat-ID aus list_chats Ergebnis. Erforderlich für read_messages und read_new."
                },
                "username": {
                    "type": "string",
                    "description": "Alternativ zu chatId: @username einer Gruppe/Person auflösen."
                },
                "limit": {
                    "type": "integer",
                    "description": "Max. Anzahl Nachrichten (default: 30, max: 100)"
                }
            }
        }
        """).RootElement.Clone();

    public async Task<ToolResult> ExecuteAsync(
        JsonElement input, AgentToolContext context, CancellationToken ct)
    {
        var allowedChats = ParseLongList(context.ToolConfig, "allowedChatIds");
        var allowedUsernames = ParseStringList(context.ToolConfig, "allowedUsernames");

        if (!_tg.IsConnected)
            return ToolResult.Fail(
                "Telegram-Client ist nicht verbunden. Bitte zuerst authentifizieren.");

        var action = input.GetProperty("action").GetString()
            ?? throw new ArgumentException("'action' is required");

        context.Logger.LogInformation("TelegramClient executing action: {Action}", action);

        return action switch
        {
            "list_chats" => await ListChatsAsync(allowedChats, ct),
            "read_messages" => await ReadMessagesAsync(input, allowedChats, context, ct),
            "read_new" => await ReadNewAsync(input, allowedChats, context, ct),
            _ => ToolResult.Fail($"Unknown action: {action}")
        };
    }

    private async Task<ToolResult> ListChatsAsync(
        List<long>? allowedChats, CancellationToken ct)
    {
        var dialogs = await _tg.GetAllDialogsAsync(ct);

        var chatList = new List<object>();
        foreach (var dialog in dialogs.dialogs.OfType<Dialog>())
        {
            var peer = dialogs.UserOrChat(dialog);
            if (peer == null) continue;

            var chatId = dialog.Peer.ID;

            if (allowedChats != null && !allowedChats.Contains(chatId))
                continue;

            var info = peer switch
            {
                User user when user.IsActive => new
                {
                    chatId,
                    type = "user",
                    name = $"{user.first_name} {user.last_name}".Trim(),
                    username = user.MainUsername,
                    unread = dialog.unread_count,
                    lastMsgId = dialog.TopMessage
                } as object,

                ChatBase chat when chat.IsActive => new
                {
                    chatId,
                    type = chat is Channel ch
                        ? (ch.IsGroup ? "supergroup" : "channel")
                        : "group",
                    name = chat.Title,
                    username = (chat as Channel)?.MainUsername,
                    unread = dialog.unread_count,
                    lastMsgId = dialog.TopMessage
                } as object,

                _ => null
            };

            if (info != null) chatList.Add(info);
        }

        var result = new
        {
            fetchedAt = DateTime.UtcNow,
            dataAsOf = DateTime.UtcNow,
            source = "telegram_client_api",
            data = new
            {
                totalChats = chatList.Count,
                chats = chatList
            }
        };

        return ToolResult.Ok(JsonSerializer.Serialize(result));
    }

    private async Task<ToolResult> ReadMessagesAsync(
        JsonElement input, List<long>? allowedChats,
        AgentToolContext ctx, CancellationToken ct)
    {
        var (peer, chatId, error) = await ResolvePeerAsync(input, allowedChats, ct);
        if (error != null) return ToolResult.Fail(error);

        var limit = input.TryGetProperty("limit", out var l)
            ? Math.Clamp(l.GetInt32(), 1, 100)
            : 30;

        var messages = await _tg.GetMessagesAsync(peer!, minId: 0, limit: limit, ct: ct);
        var msgList = FormatMessages(messages);

        var result = new
        {
            fetchedAt = DateTime.UtcNow,
            dataAsOf = DateTime.UtcNow,
            source = $"telegram_chat_{chatId}",
            data = new
            {
                chatId,
                count = msgList.Count,
                messages = msgList
            }
        };

        return ToolResult.Ok(JsonSerializer.Serialize(result));
    }

    private async Task<ToolResult> ReadNewAsync(
        JsonElement input, List<long>? allowedChats,
        AgentToolContext ctx, CancellationToken ct)
    {
        var (peer, chatId, error) = await ResolvePeerAsync(input, allowedChats, ct);
        if (error != null) return ToolResult.Fail(error);

        var stateKey = $"tgclient:{ctx.AgentId}:chat_{chatId}:lastMsgId";
        var lastIdStr = await ctx.StateStore.GetAsync(stateKey, ct);
        var lastId = int.TryParse(lastIdStr, out var id) ? id : 0;

        var limit = input.TryGetProperty("limit", out var l)
            ? Math.Clamp(l.GetInt32(), 1, 100)
            : 50;

        var messages = await _tg.GetMessagesAsync(peer!, minId: lastId, limit: limit, ct: ct);
        var msgList = FormatMessages(messages);

        if (msgList.Count > 0)
        {
            var newMaxId = msgList.Max(m => m.MessageId);
            await ctx.StateStore.SetAsync(stateKey, newMaxId.ToString(), ct);
        }

        var result = new
        {
            fetchedAt = DateTime.UtcNow,
            dataAsOf = DateTime.UtcNow,
            source = $"telegram_chat_{chatId}",
            data = new
            {
                chatId,
                sinceId = lastId,
                newCount = msgList.Count,
                messages = msgList
            }
        };

        return ToolResult.Ok(JsonSerializer.Serialize(result));
    }

    private async Task<(InputPeer? Peer, long ChatId, string? Error)> ResolvePeerAsync(
        JsonElement input, List<long>? allowedChats, CancellationToken ct)
    {
        long chatId = 0;
        InputPeer? peer = null;

        if (input.TryGetProperty("chatId", out var cid))
        {
            chatId = cid.GetInt64();
            if (allowedChats != null && !allowedChats.Contains(chatId))
                return (null, chatId, $"Agent hat keinen Zugriff auf Chat {chatId}.");

            peer = _tg.GetInputPeerFromCache(chatId);
            if (peer == null)
            {
                await _tg.GetAllDialogsAsync(ct);
                peer = _tg.GetInputPeerFromCache(chatId);
            }
        }
        else if (input.TryGetProperty("username", out var uname))
        {
            var resolved = await _tg.ResolveUsernameAsync(uname.GetString()!, ct);
            peer = resolved.UserOrChat switch
            {
                User u => (InputPeer)u,
                ChatBase c => (InputPeer)c,
                _ => null
            };
            chatId = resolved.peer.ID;

            if (allowedChats != null && !allowedChats.Contains(chatId))
                return (null, chatId, $"Agent hat keinen Zugriff auf Chat @{uname.GetString()}.");
        }

        if (peer == null)
            return (null, 0, "chatId oder username muss angegeben werden.");

        return (peer, chatId, null);
    }

    private static List<FormattedMessage> FormatMessages(Messages_MessagesBase messages)
    {
        var result = new List<FormattedMessage>();

        foreach (var msgBase in messages.Messages)
        {
            var from = messages.UserOrChat(msgBase.From ?? msgBase.Peer);
            var fromName = from switch
            {
                User u => $"{u.first_name} {u.last_name}".Trim(),
                ChatBase c => c.Title,
                _ => "Unknown"
            };

            if (msgBase is Message msg)
            {
                result.Add(new FormattedMessage(
                    msg.ID,
                    msg.Date,
                    fromName,
                    msgBase.From?.ID ?? 0,
                    msg.message,
                    msg.media != null,
                    msg.media?.GetType().Name,
                    (msg.reply_to as MessageReplyHeader)?.reply_to_msg_id,
                    msg.fwd_from != null
                        ? msg.fwd_from.from_name ?? "forwarded"
                        : null,
                    msg.views));
            }
        }

        return result.OrderBy(m => m.MessageId).ToList();
    }

    private static List<long>? ParseLongList(
        IReadOnlyDictionary<string, object?> config, string key)
    {
        if (!config.TryGetValue(key, out var val) || val == null)
            return null;

        if (val is JsonElement je && je.ValueKind == JsonValueKind.Array)
            return je.EnumerateArray().Select(e => e.GetInt64()).ToList();

        return null;
    }

    private static List<string>? ParseStringList(
        IReadOnlyDictionary<string, object?> config, string key)
    {
        if (!config.TryGetValue(key, out var val) || val == null)
            return null;

        if (val is JsonElement je && je.ValueKind == JsonValueKind.Array)
            return je.EnumerateArray().Select(e => e.GetString()!).ToList();

        return null;
    }

    private sealed record FormattedMessage(
        int MessageId,
        DateTime Date,
        string From,
        long FromId,
        string? Text,
        bool HasMedia,
        string? MediaType,
        int? ReplyToId,
        string? ForwardFrom,
        int? Views);
}
