package members

import (
	"context"
	"time"

	"github.com/go-faster/errors"

	"go.mau.fi/mautrix-telegram/pkg/gotd/telegram/peers"
	"go.mau.fi/mautrix-telegram/pkg/gotd/tg"
)

// ChatMembers is chat Members.
type ChatMembers struct {
	m    *peers.Manager
	chat peers.Chat
}

func (c *ChatMembers) queryParticipants(ctx context.Context) (*tg.ChatParticipants, error) {
	full, err := c.chat.FullRaw(ctx)
	if err != nil {
		return nil, errors.Wrap(err, "get full")
	}
	switch p := full.Participants.(type) {
	case *tg.ChatParticipantsForbidden:
		return nil, &ChatInfoUnavailableError{Info: p}
	case *tg.ChatParticipants:
		return p, nil
	default:
		return nil, errors.Errorf("unexpected type %T", p)
	}
}

// ForEach calls cb for every member of chat.
//
// May return ChatInfoUnavailableError.
func (c *ChatMembers) ForEach(ctx context.Context, cb Callback) error {
	chatDate := time.Unix(int64(c.chat.Raw().Date), 0)
	p, err := c.queryParticipants(ctx)
	if err != nil {
		return errors.Wrap(err, "query")
	}

	for i, participant := range p.Participants {
		userID := participant.GetUserID()
		user, err := c.m.ResolveUserID(ctx, userID)
		if err != nil {
			return errors.Wrapf(err, "get member %d", userID)
		}

		var inviter peers.User
		switch p := participant.(type) {
		case *tg.ChatParticipant:
			inviter, err = c.m.ResolveUserID(ctx, p.InviterID)
		case *tg.ChatParticipantAdmin:
			inviter, err = c.m.ResolveUserID(ctx, p.InviterID)
		}
		if err != nil {
			return errors.Wrap(err, "get inviter")
		}

		if err := cb(ChatMember{
			parent:      c,
			creatorDate: chatDate,
			user:        user,
			inviter:     inviter,
			raw:         participant,
		}); err != nil {
			return errors.Wrapf(err, "callback (index: %d)", i)
		}
	}
	return nil
}

// Count returns total count of members.
func (c *ChatMembers) Count(ctx context.Context) (int, error) {
	p, err := c.queryParticipants(ctx)
	if err != nil {
		return 0, errors.Wrap(err, "query")
	}
	return len(p.Participants), nil
}

// Peer returns chat object.
func (c *ChatMembers) Peer() peers.Peer {
	return c.chat
}

// Kick kicks user member.
//
// If revokeHistory is set, will delete all messages from this member.
func (c *ChatMembers) Kick(ctx context.Context, member tg.InputUserClass, revokeHistory bool) error {
	if _, err := c.m.API().MessagesDeleteChatUser(ctx, &tg.MessagesDeleteChatUserRequest{
		RevokeHistory: revokeHistory,
		ChatID:        c.chat.ID(),
		UserID:        member,
	}); err != nil {
		return errors.Wrapf(err, "delete user (revoke: %v)", revokeHistory)
	}
	return nil
}

// EditRights edits rights of all members in this chat.
func (c *ChatMembers) EditRights(ctx context.Context, options MemberRights) error {
	return editDefaultRights(ctx, c.m.API(), c.chat.InputPeer(), options)
}

// EditAdmin edits admin rights for given user.
func (c *ChatMembers) EditAdmin(ctx context.Context, user tg.InputUserClass, isAdmin bool) error {
	if _, err := c.m.API().MessagesEditChatAdmin(ctx, &tg.MessagesEditChatAdminRequest{
		ChatID:  c.chat.ID(),
		UserID:  user,
		IsAdmin: isAdmin,
	}); err != nil {
		return errors.Wrap(err, "edit admin")
	}
	return nil
}

// Chat returns recent chat members.
func Chat(chat peers.Chat) *ChatMembers {
	return &ChatMembers{
		m:    chat.Manager(),
		chat: chat,
	}
}
