package tg

import (
	"context"
	"encoding/json"
	"fmt"
	"strings"
	"time"

	"github.com/gotd/td/telegram/message"
	"github.com/gotd/td/tg"
	mcp "github.com/metoro-io/mcp-golang"
	"github.com/pkg/errors"
)

type HistoryArguments struct {
	Name   string `json:"name" jsonschema:"required,description=Name of the dialog"`
	Offset int    `json:"offset,omitempty" jsonschema:"description=Offset for continuation"`
}

type HistoryResponse struct {
	Messages []MessageInfo `json:"messages"`
	Offset   int           `json:"offset,omitempty"`
}

func (c *Client) GetHistory(args HistoryArguments) (*mcp.ToolResponse, error) {
	var messagesClass tg.MessagesMessagesClass
	client := c.T()
	if err := client.Run(context.Background(), func(ctx context.Context) (err error) {
		api := client.API()

		inputPeer, err := getInputPeerFromName(ctx, api, args.Name)
		if err != nil {
			return fmt.Errorf("get inputPeer from name: %w", err)
		}

		messagesClass, err = api.MessagesGetHistory(ctx, &tg.MessagesGetHistoryRequest{
			Peer:     inputPeer,
			OffsetID: args.Offset,
		})
		if err != nil {
			return fmt.Errorf("failed to get history: %w", err)
		}

		//Debug
		//jsonData, _ := json.Marshal(messagesClass)
		//log.Info().RawJSON("history", cleanJSON(jsonData)).Msg("history")

		return nil
	}); err != nil {
		return nil, errors.Wrap(err, "failed to get history")
	}

	h, err := newHistory(messagesClass)
	if err != nil {
		return nil, errors.Wrap(err, "failed to process history")
	}

	rsp := HistoryResponse{
		Messages: h.Info(),
		Offset:   h.Offset(),
	}

	jsonData, err := json.Marshal(rsp)
	if err != nil {
		return nil, errors.Wrap(err, "failed to marshal response")
	}

	return mcp.NewToolResponse(mcp.NewTextContent(string(jsonData))), nil
}

func getInputPeerFromName(ctx context.Context, api *tg.Client, name string) (tg.InputPeerClass, error) {
	isCustom := strings.Contains(name, "[") && strings.Contains(name, "]")

	switch {
	case strings.HasPrefix(name, "chn") && isCustom:
		var channelPeer tg.InputPeerChannel
		_, err := fmt.Sscanf(name, "chn[%d:%d]", &channelPeer.ChannelID, &channelPeer.AccessHash)
		if err != nil {
			return nil, errors.Wrapf(err, "scan channel peer(%q)", name)
		}

		return &channelPeer, nil
	case strings.HasPrefix(name, "cht") && isCustom:
		var chatPeer tg.InputPeerChat
		_, err := fmt.Sscanf(name, "cht[%d]", &chatPeer.ChatID)
		if err != nil {
			return nil, errors.Wrapf(err, "scan chat peer(%q)", name)
		}

		return &chatPeer, nil
	default:
		sender := message.NewSender(api)
		inputPeer, err := sender.Resolve(name).AsInputPeer(ctx)
		if err != nil {
			return nil, fmt.Errorf("failed to resolve name: %w", err)
		}

		return inputPeer, nil
	}
}

type history struct {
	tg.MessagesMessages
	users map[int64]*tg.User
}

func newHistory(raw tg.MessagesMessagesClass) (*history, error) {
	var h history
	switch m := raw.(type) {
	case *tg.MessagesMessages:
		h = history{MessagesMessages: *m}
	case *tg.MessagesMessagesSlice:
		h = history{MessagesMessages: tg.MessagesMessages{
			Messages: m.Messages,
			Users:    m.Users,
			Chats:    m.Chats,
		}}
	case *tg.MessagesChannelMessages:
		h = history{MessagesMessages: tg.MessagesMessages{
			Messages: m.Messages,
			Users:    m.Users,
			Chats:    m.Chats,
		}}
	default:
		return nil, fmt.Errorf("unexpected type: %T", raw)
	}

	h.users = make(map[int64]*tg.User)
	for _, u := range h.Users {
		if user, ok := u.(*tg.User); ok {
			h.users[user.ID] = user
		}
	}

	return &h, nil
}

func (h *history) Info() []MessageInfo {
	messages := make([]MessageInfo, 0, len(h.Messages))

	for _, msg := range h.Messages {
		m, ok := msg.(*tg.Message)
		if !ok {
			continue
		}

		var who string
		if m.FromID != nil {
			switch from := m.FromID.(type) {
			case *tg.PeerUser:
				if user, ok := h.users[from.UserID]; ok {
					who = getUsername(user)
				}
			}
		}

		messages = append(messages, MessageInfo{
			Who:  who,
			When: time.Unix(int64(m.Date), 0).Format(time.DateTime),
			Text: m.Message,
			ts:   m.Date,
		})
	}

	return messages
}

func (h *history) Offset() int {
	for i := len(h.Messages) - 1; i >= 0; i-- {
		if msg, ok := h.Messages[i].(*tg.Message); ok {
			return msg.ID
		}
	}

	return 0
}
