package peers

import (
	"context"
	"time"

	"github.com/go-faster/errors"

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

// InviteLinks represents invite links of Chat or Channel.
type InviteLinks struct {
	peer Peer
	m    *Manager
}

// ExportLinkOptions is options for ExportNew.
type ExportLinkOptions struct {
	// Whether users joining the chat via the link need to be approved by chat administrators.
	RequestNeeded bool
	// Expiration date.
	//
	// If zero, will not be used.
	ExpireDate time.Time
	// Maximum number of users that can join using this link.
	//
	// If zero, will not be used.
	UsageLimit int
	// Title of this link.
	//
	// If zero, will not be used.
	Title string
}

// ExportNew creates new primary invite link for a chat.
//
// Notice: Any previously generated primary link is revoked.
//
// See also AddNew.
func (e InviteLinks) ExportNew(ctx context.Context, opts ExportLinkOptions) (InviteLink, error) {
	return e.newLink(ctx, true, opts)
}

// AddNew creates an additional invite link for a chat.
func (e InviteLinks) AddNew(ctx context.Context, opts ExportLinkOptions) (InviteLink, error) {
	return e.newLink(ctx, false, opts)
}

// Get returns link info.
func (e InviteLinks) Get(ctx context.Context, link string) (InviteLink, error) {
	r, err := e.m.api.MessagesGetExportedChatInvite(ctx, &tg.MessagesGetExportedChatInviteRequest{
		Peer: e.peer.InputPeer(),
		Link: link,
	})
	if err != nil {
		return InviteLink{}, errors.Wrap(err, "get chat invite")
	}

	return e.applyExportedInvite(ctx, r)
}

// Edit edits link info.
func (e InviteLinks) Edit(ctx context.Context, link string, opts ExportLinkOptions) (InviteLink, error) {
	req := tg.MessagesEditExportedChatInviteRequest{
		Revoked:       false,
		Peer:          e.peer.InputPeer(),
		Link:          link,
		ExpireDate:    0,
		UsageLimit:    opts.UsageLimit,
		RequestNeeded: opts.RequestNeeded,
		Title:         opts.Title,
	}
	if e := opts.ExpireDate; !e.IsZero() {
		req.ExpireDate = int(e.Unix())
	}
	return e.edit(ctx, "edit chat invite", req)
}

// Revoke revokes invite link and returns revoked link info.
//
// If the primary link is revoked, a new link is automatically generated.
func (e InviteLinks) Revoke(ctx context.Context, link string) (InviteLink, error) {
	return e.edit(ctx, "revoke chat invite", tg.MessagesEditExportedChatInviteRequest{
		Revoked: true,
		Peer:    e.peer.InputPeer(),
		Link:    link,
	})
}

func (e InviteLinks) edit(
	ctx context.Context,
	msg string,
	req tg.MessagesEditExportedChatInviteRequest,
) (InviteLink, error) {
	r, err := e.m.api.MessagesEditExportedChatInvite(ctx, &req)
	if err != nil {
		return InviteLink{}, errors.Wrap(err, msg)
	}

	return e.applyExportedInvite(ctx, r)
}

// Delete deletes invite link.
//
// Not available for bots.
func (e InviteLinks) Delete(ctx context.Context, link string) error {
	if _, err := e.m.api.MessagesDeleteExportedChatInvite(ctx, &tg.MessagesDeleteExportedChatInviteRequest{
		Peer: e.peer.InputPeer(),
		Link: link,
	}); err != nil {
		return errors.Wrap(err, "delete chat invite")
	}

	return nil
}

// ApproveJoin approves join request for given user.
func (e InviteLinks) ApproveJoin(ctx context.Context, user tg.InputUserClass) error {
	return e.hideJoinRequest(ctx, true, user)
}

// DeclineJoin declines join request for given user.
func (e InviteLinks) DeclineJoin(ctx context.Context, user tg.InputUserClass) error {
	return e.hideJoinRequest(ctx, false, user)
}

func (e InviteLinks) hideJoinRequest(ctx context.Context, approved bool, user tg.InputUserClass) error {
	if _, err := e.m.api.MessagesHideChatJoinRequest(ctx, &tg.MessagesHideChatJoinRequestRequest{
		Approved: approved,
		Peer:     e.peer.InputPeer(),
		UserID:   user,
	}); err != nil {
		return errors.Wrapf(err, "hide join (approved: %t)", approved)
	}
	return nil
}

func (InviteLinks) chatInviteExportedFrom(v tg.ExportedChatInviteClass) (*tg.ChatInviteExported, error) {
	// https://github.com/gotd/td/issues/788
	// case *tg.ChatInvitePublicJoinRequests: // chatInvitePublicJoinRequests#ed107ab7 not supported
	switch invite := v.(type) {
	case *tg.ChatInviteExported:
		return invite, nil
	default:
		return nil, errors.Errorf("unsupported %T", invite)
	}
}

func (e InviteLinks) applyExportedInvite(
	ctx context.Context,
	r tg.MessagesExportedChatInviteClass,
) (InviteLink, error) {
	if err := e.m.applyUsers(ctx, r.GetUsers()...); err != nil {
		return InviteLink{}, errors.Wrap(err, "update users")
	}

	switch r := r.(type) {
	case *tg.MessagesExportedChatInviteReplaced:
		from, err := e.chatInviteExportedFrom(r.GetInvite())
		if err != nil {
			return InviteLink{}, errors.Wrap(err, "from")
		}
		to, err := e.chatInviteExportedFrom(r.GetNewInvite())
		if err != nil {
			return InviteLink{}, errors.Wrap(err, "to")
		}
		return e.replacedLink(*from, *to), nil
	}
	exported, err := e.chatInviteExportedFrom(r.GetInvite())
	if err != nil {
		return InviteLink{}, errors.Wrap(err, "convert invite")
	}
	return e.inviteLink(*exported), nil
}

func (e InviteLinks) newLink(
	ctx context.Context,
	revokeOld bool,
	opts ExportLinkOptions,
) (InviteLink, error) {
	req := &tg.MessagesExportChatInviteRequest{
		LegacyRevokePermanent: revokeOld,
		RequestNeeded:         opts.RequestNeeded,
		Peer:                  e.peer.InputPeer(),
		ExpireDate:            0,
		UsageLimit:            opts.UsageLimit,
		Title:                 opts.Title,
	}
	if e := opts.ExpireDate; !e.IsZero() {
		req.ExpireDate = int(e.Unix())
	}

	invite, err := e.m.api.MessagesExportChatInvite(ctx, req)
	if err != nil {
		return InviteLink{}, errors.Wrap(err, "create invite")
	}
	exported, err := e.chatInviteExportedFrom(invite)
	if err != nil {
		return InviteLink{}, errors.Wrap(err, "convert invite")
	}
	return e.inviteLink(*exported), nil
}

// TODO(tdakkota): add methods with pagination, when query will be updated
