package im

import (
	"context"
	"encoding/json"
	"fmt"
	"io"
	"net/url"

	lark "github.com/larksuite/oapi-sdk-go/v3"
	larkcore "github.com/larksuite/oapi-sdk-go/v3/core"
	larkim "github.com/larksuite/oapi-sdk-go/v3/service/im/v1"
	"github.com/tidbcloud/devops/pkg/infra/log"
	"github.com/tidbcloud/devops/pkg/infra/utils/pointer"
)

type IM interface {
	SendMessageViaWebhook(ctx context.Context, webhookURL string, msg IMMessage) error
	SendMessage(ctx context.Context, receiveType, receiveID string, msg IMMessage) (*string /*message id*/, error)
	ReplyMessage(ctx context.Context, msgID string, msg IMMessage, replyInThread bool) (*string /*message id*/, error)
	PatchMessage(ctx context.Context, msgID string, msg IMMessage) error
	ListMessages(ctx context.Context, chatID string, startTime string, endTime string) (*larkim.ListMessageRespData, error)
	GenerateChatShareLink(ctx context.Context, chatID string, validityPeriod string) (string /* link */, error)

	UploadImage(ctx context.Context, image io.Reader) (*string /*image key*/, error)
	UploadFile(ctx context.Context, fileType string, fileName string, file io.Reader) (*string /*file key*/, error)
}

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

type client struct {
	larkCli *lark.Client
}

// SendMessage sends a message to a chat/user
//
// receiveType: open_id/user_id/union_id/email/chat_id
func (c *client) SendMessage(ctx context.Context, receiveType, receiveID string, msg IMMessage) (*string /*message id*/, error) {
	logger := log.FromContext(ctx)

	logger.V(4).Info("try to send a message", "message", msg, "receive_type", receiveType, "receive_id", receiveID)

	content, err := EncodeIMMessage(msg)
	if err != nil {
		return nil, err
	}

	req := larkim.NewCreateMessageReqBuilder().
		ReceiveIdType(receiveType).
		Body(&larkim.CreateMessageReqBody{
			ReceiveId: pointer.Pointer(receiveID),
			MsgType:   pointer.Pointer(msg.Type()),
			Content:   pointer.Pointer(content),
		}).
		Build()

	resp, err := c.larkCli.Im.Message.Create(ctx, req)
	if err != nil {
		return nil, fmt.Errorf("create message failed: %w", err)
	}
	if !resp.Success() {
		return nil, fmt.Errorf("create message failed: %s", resp.CodeError.String())
	}

	return resp.Data.MessageId, nil
}

func (c *client) SendMessageViaWebhook(ctx context.Context, webhookURL string, msg IMMessage) error {
	logger := log.FromContext(ctx)

	u, err := url.Parse(webhookURL)
	if err != nil {
		return fmt.Errorf("invalid webhook url: %w", err)
	}

	body := IMMessageCardBody{
		MsgType: msg.Type(),
		Card:    msg,
	}
	logger.V(4).Info("try to send a message via webhook", "body", body)

	resp, err := c.larkCli.Post(ctx, u.Path, body, larkcore.AccessTokenTypeNone)
	if err != nil {
		return fmt.Errorf("failed to send message via webhook: %w", err)
	}

	respErr := larkcore.CodeError{}
	err = json.Unmarshal(resp.RawBody, &respErr)
	if err != nil {
		return fmt.Errorf("failed to unmarshal response body: %w", err)
	}

	if respErr.Code != 0 {
		return fmt.Errorf("failed to send message via webhook: %s", string(resp.RawBody))
	}

	logger.Info("sent message via webhook", "status_code", resp.StatusCode, "response", string(resp.RawBody))
	return nil
}

// UpdateMessage updates a card message
//
// Ref: https://open.feishu.cn/document/server-docs/im-v1/message-card/patch
//
// NOTE: Note to set the config `update_mutli: true`, see https://open.feishu.cn/document/common-capabilities/message-card/message-cards-content/card-structure/card-configuration
func (c *client) PatchMessage(ctx context.Context, msgID string, msg IMMessage) error {
	logger := log.FromContext(ctx)

	logger.V(4).Info("try to patch a message", "message", msg, "msg_id", msgID)

	content, err := EncodeIMMessage(msg)
	if err != nil {
		return err
	}

	req := larkim.NewPatchMessageReqBuilder().
		MessageId(msgID).
		Body(&larkim.PatchMessageReqBody{
			Content: pointer.Pointer(content),
		}).
		Build()

	resp, err := c.larkCli.Im.Message.Patch(ctx, req)
	if err != nil {
		return fmt.Errorf("patch message failed: %w", err)
	}
	if !resp.Success() {
		return fmt.Errorf("patch message failed: %s", resp.CodeError.String())
	}

	return nil
}

func (c *client) ReplyMessage(ctx context.Context, msgID string, msg IMMessage, replyInThread bool) (*string /*message id*/, error) {
	logger := log.FromContext(ctx)

	logger.V(4).Info("try to reply a message", "message", msg, "msg_id", msgID)

	content, err := EncodeIMMessage(msg)
	if err != nil {
		return nil, err
	}

	req := larkim.NewReplyMessageReqBuilder().
		MessageId(msgID).
		Body(&larkim.ReplyMessageReqBody{
			Content:       &content,
			MsgType:       pointer.Pointer(msg.Type()),
			ReplyInThread: pointer.Pointer(replyInThread),
		}).
		Build()

	resp, err := c.larkCli.Im.Message.Reply(ctx, req)
	if err != nil {
		return nil, fmt.Errorf("reply message failed: %w", err)
	}
	if !resp.Success() {
		return nil, fmt.Errorf("reply message failed: %s", resp.CodeError.String())
	}

	return resp.Data.MessageId, nil
}

func (c *client) ListMessages(ctx context.Context, chatID string, startTime string, endTime string) (*larkim.ListMessageRespData, error) {
	req := larkim.NewListMessageReqBuilder().
		ContainerIdType("chat").
		ContainerId(chatID).
		StartTime(startTime).
		EndTime(endTime).
		SortType(`ByCreateTimeAsc`).
		PageSize(20).
		Build()
	resp, err := c.larkCli.Im.Message.List(ctx, req)
	if err != nil {
		return nil, err
	}

	if !resp.Success() {
		return nil, err
	}

	return resp.Data, nil
}

func (c *client) GenerateChatShareLink(ctx context.Context, chatID string, linkPeriod string) (string /* link */, error) {
	req := larkim.NewLinkChatReqBuilder().
		ChatId(chatID).
		Body(&larkim.LinkChatReqBody{
			ValidityPeriod: pointer.Pointer(linkPeriod),
		}).
		Build()

	resp, err := c.larkCli.Im.Chat.Link(ctx, req)
	if err != nil {
		return "", fmt.Errorf("gen chat link failed: %w", err)
	}
	if !resp.Success() {
		return "", fmt.Errorf("reply message failed: %s", resp.CodeError.String())
	}

	return *resp.Data.ShareLink, nil
}

func (c *client) UploadImage(ctx context.Context, image io.Reader) (*string, error) {
	req := larkim.NewCreateImageReqBuilder().
		Body(larkim.NewCreateImageReqBodyBuilder().
			ImageType("message").
			Image(image).
			Build()).
		Build()
	resp, err := c.larkCli.Im.Image.Create(ctx, req)
	if err != nil {
		return nil, fmt.Errorf("upload image failed: %s", err)
	}
	if !resp.Success() {
		return nil, fmt.Errorf("upload image failed: code: %d, msg: %s", resp.Code, resp.Msg)
	}
	return resp.Data.ImageKey, nil
}

func (c *client) UploadFile(ctx context.Context, fileType string, fileName string, file io.Reader) (*string, error) {
	req := larkim.NewCreateFileReqBuilder().
		Body(larkim.NewCreateFileReqBodyBuilder().
			FileType(fileType).
			FileName(fileName).
			File(file).
			Build()).
		Build()
	resp, err := c.larkCli.Im.File.Create(ctx, req)
	if err != nil {
		return nil, fmt.Errorf("upload file failed: %s", err)
	}
	if !resp.Success() {
		return nil, fmt.Errorf("upload file failed: code: %d, msg: %s", resp.Code, resp.Msg)
	}
	return resp.Data.FileKey, nil
}
