//go:generate ${GOBIN}/mockgen -destination chat_mock.go -package=chat ${GO_MODULE}/infra/lark/client/chat Chat
package chat

import (
	"context"
	"fmt"

	lark "github.com/larksuite/oapi-sdk-go/v3"
	larkim "github.com/larksuite/oapi-sdk-go/v3/service/im/v1"
)

type CreateChatRequest struct {
	Name        string
	Description string
	UserIDType  string // e.g. "user_id", "open_id"
	OwnerID     string
	MemberIDs   []string
	BotIDs      []string
}

type Chat interface {
	ListChats(ctx context.Context) ([]*larkim.Chat, error)
	GetChat(ctx context.Context, chatId string) (*larkim.Chat, error)
	CreateChat(ctx context.Context, req CreateChatRequest) (*string, error)
	InviteMembers(ctx context.Context, chatId string, memType string, memIDs []string) error

	PutTopicNotice(ctx context.Context, chatID string, action, messageID string) error
}

func New(larkCli *lark.Client) Chat {
	return &client{
		larkCli: larkCli,
	}
}

type client struct {
	larkCli *lark.Client
}

func (c *client) ListChats(ctx context.Context) ([]*larkim.Chat, error) {
	chats := make([]*larkim.Chat, 0, 100)
	req := larkim.NewListChatReqBuilder().
		PageSize(100).
		Build()
	resp, err := c.larkCli.Im.Chat.List(ctx, req)
	if err != nil {
		return nil, fmt.Errorf("fail to batch get lark users, err: %w", err)
	}
	if !resp.Success() {
		return nil, fmt.Errorf("fail to batch get lark users, code: %d, message: %s", resp.Code, resp.Msg)
	}
	for _, listChat := range resp.Data.Items {
		chats = append(chats, larkListChatToLarkChat(listChat))
	}
	return chats, nil
}

func (c *client) GetChat(ctx context.Context, chatId string) (*larkim.Chat, error) {
	req := larkim.NewGetChatReqBuilder().
		ChatId(chatId).
		UserIdType("user_id").
		Build()
	resp, err := c.larkCli.Im.Chat.Get(ctx, req)
	if err != nil {
		return nil, fmt.Errorf("get chat's information %s via lark api: %w", chatId, err)
	}
	if !resp.Success() {
		return nil, fmt.Errorf("get chat's information %s via lark api, code: %d, msg: %s", chatId, resp.Code, resp.Msg)
	}
	return larkGetChatRespToLarkChat(&chatId, resp.Data), nil
}

func (c *client) CreateChat(ctx context.Context, req CreateChatRequest) (*string, error) {
	larkReq := larkim.NewCreateChatReqBuilder().
		UserIdType(req.UserIDType).
		Body(larkim.NewCreateChatReqBodyBuilder().
			Name(req.Name).
			Description(req.Description).
			OwnerId(req.OwnerID).
			UserIdList(req.MemberIDs).
			BotIdList(req.BotIDs).
			Build()).
		Build()
	resp, err := c.larkCli.Im.Chat.Create(ctx, larkReq)
	if err != nil {
		return nil, fmt.Errorf("create chat %s via lark api: %w", req.Name, err)
	}
	if !resp.Success() {
		return nil, fmt.Errorf("create chat %s via lark api, code: %d, msg: %s", req.Name, resp.Code, resp.Msg)
	}
	return resp.Data.ChatId, nil
}

func (c *client) InviteMembers(ctx context.Context, chatId string, memType string, memIDs []string) error {
	req := larkim.NewCreateChatMembersReqBuilder().
		ChatId(chatId).
		MemberIdType(memType).
		SucceedType(2). // fail and not invite all member if any id is invalid
		Body(larkim.NewCreateChatMembersReqBodyBuilder().
			IdList(memIDs).
			Build()).
		Build()
	resp, err := c.larkCli.Im.ChatMembers.Create(ctx, req)
	if err != nil {
		return fmt.Errorf("invite members to chat %s via lark api: %w", chatId, err)
	}
	if !resp.Success() {
		return fmt.Errorf("invite members to chat %s via lark api, code: %d, msg: %s", chatId, resp.Code, resp.Msg)
	}

	return nil
}

func (c *client) PutTopicNotice(ctx context.Context, chatID string, action, messageID string) error {
	req := larkim.NewPutTopNoticeChatTopNoticeReqBuilder().
		ChatId(chatID).
		Body(larkim.NewPutTopNoticeChatTopNoticeReqBodyBuilder().
			ChatTopNotice([]*larkim.ChatTopNotice{
				larkim.NewChatTopNoticeBuilder().
					ActionType(action).
					MessageId(messageID).
					Build(),
			}).
			Build()).
		Build()
	resp, err := c.larkCli.Im.ChatTopNotice.PutTopNotice(ctx, req)
	if err != nil {
		return fmt.Errorf("put topic notice to chat %s via lark api: %w", chatID, err)
	}
	if !resp.Success() {
		return fmt.Errorf("put topic notice to chat %s via lark api, code: %d, msg: %s", chatID, resp.Code, resp.Msg)
	}

	return nil
}

func larkListChatToLarkChat(larkListChat *larkim.ListChat) *larkim.Chat {
	return &larkim.Chat{
		ChatId:      larkListChat.ChatId,
		Avatar:      larkListChat.Avatar,
		Name:        larkListChat.Name,
		Description: larkListChat.Description,
	}
}

func larkGetChatRespToLarkChat(chatId *string, larkGetChatResp *larkim.GetChatRespData) *larkim.Chat {
	return &larkim.Chat{
		ChatId:      chatId,
		Avatar:      larkGetChatResp.Avatar,
		Name:        larkGetChatResp.Name,
		Description: larkGetChatResp.Description,
		OwnerUserId: larkGetChatResp.OwnerId,
	}
}
