//go:generate ${GOBIN}/mockgen -destination notification_mock.go -package=notify ${GO_MODULE}/pkg/infra/clients/larkcli/notify Notification
package notify

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

	larkcore "github.com/larksuite/oapi-sdk-go/v3/core"
	larkutil "github.com/tidbcloud/devops/pkg/infra/clients/larkcli/util"
	"github.com/tidbcloud/devops/pkg/infra/log"
	"github.com/tidbcloud/devops/pkg/infra/utils/pointer"

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

const (
	ShareLinkPeriodWeek        = "week"
	ShareLinkPeriodDay         = "year"
	ShareLinkPeriodPermanently = "permanently"

	ReceiveTypeChat    = "chat_id"
	ReceiveTypeUser    = "user_id"
	ReceiveTypeOpenID  = "open_id"
	ReceiveTypeUnionID = "union_id"
	ReceiveTypeEmail   = "email"
)

type MessageCardSet struct {
	// release related
	ReleaseNotification           string
	ReleaseDeploymentNotification string
	ReleaseCloseNotification      string

	// maintenance related
	MaintenanceTaskRunning string
	MaintenanceTaskClosed  string

	// incident related
	IncidentTopic                    string
	IncidentToDoActionTCOCPCreated   string
	IncidentToDoActionOverdue        string
	IncidentToDoActionOverdueSummary string

	// operation related
	OperationNotify string

	// approval related
	ApprovalStart     string
	ApprovalEnd       string
	ApprovalTaskStart string
	ApprovalTaskEnd   string
	ApprovalCC        string

	// k8s upgrade related
	K8sUpgradeTaskApproved string
	K8sUpgradeCancelled    string

	// privilege related
	PrivilegeGranted    string
	PrivilegeWillExpire string
	PrivilegeRevoked    string

	// alert related
	AlertNotification                string
	LongTailNotificationUnify        string
	JiraIssueInformationForS1Tickets string
}

var (
	// Ref: https://open.feishu.cn/cardkit
	MsgCardSet = MessageCardSet{
		ReleaseNotification:              "AAqDoxKv21LX8",
		ReleaseDeploymentNotification:    "ctp_AA6Jm8m8OcrG",
		ReleaseCloseNotification:         "ctp_AAmV8y22GZu7",
		MaintenanceTaskRunning:           "ctp_AAqkQZo2tkF9",
		MaintenanceTaskClosed:            "ctp_AAq3aH1IUpCl",
		IncidentTopic:                    "ctp_AAuwYYurkv6T",
		IncidentToDoActionTCOCPCreated:   "AAqtEBfkVM0hD",
		IncidentToDoActionOverdue:        "AAqtKMyfq1NR2",
		IncidentToDoActionOverdueSummary: "AAqtKce0VtcED",
		OperationNotify:                  "ctp_AAw91UHnuwTV",
		ApprovalStart:                    "ctp_AA1otNaxNJjD",
		ApprovalEnd:                      "ctp_AA1obGMrIhoZ",
		ApprovalTaskStart:                "ctp_AA1WUWSddPPS",
		ApprovalTaskEnd:                  "ctp_AA1WR1jV6NMJ",
		ApprovalCC:                       "ctp_AA1oJnapYPWC",
		K8sUpgradeTaskApproved:           "AAqHBDJC60Wwy",
		K8sUpgradeCancelled:              "AAq060frQl8qn",
		PrivilegeGranted:                 "AAqjJpksrlZzu",
		PrivilegeWillExpire:              "AAqjJbqymrXPM",
		PrivilegeRevoked:                 "AAqjJQLJGIwjF",
		AlertNotification:                "AAqI1PLeQLH26",
		LongTailNotificationUnify:        "AAqI5NFR1kMsJ",
		JiraIssueInformationForS1Tickets: "AAqhsGDnbbCxv",
	}
)

type Param interface {
	Message(cards MessageCardSet) (larkutil.IMMessage, error)
}

type TextMessageParam string

func (r TextMessageParam) Message(cards MessageCardSet) (larkutil.IMMessage, error) {
	return larkutil.IMMessageText{
		Text: string(r),
	}, nil
}

type CreateChatParam struct {
	Name        string
	Description string
	OwnerID     string // bot is owner when empty
	UserIDs     []string
}

type Notification interface {
	// raw api
	// Deprecated: use im client instead
	SendMessageViaWebhook(ctx context.Context, webhookURL string, param Param) error
	SendMessage(ctx context.Context, receiveType, receiveID string, param Param) (*string /*message id*/, error)
	ReplyMessage(ctx context.Context, msgID string, param Param, replyInThread bool) (*string /*message id*/, error)
	PatchMessage(ctx context.Context, msgID string, param Param) 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)

	// release related
	ReleaseTopic(ctx context.Context, chatID string, param ReleaseTopicParam) (*string /*message id*/, error)
	ReleaseDeployment(ctx context.Context, msgID string, param ReleaseDeploymentParam) (*string /*message id*/, error)
	ReleaseClose(ctx context.Context, msgID string, param ReleaseCloseParam) (*string /*message id*/, error)

	// maintenance related
	MaintenanceTaskRunning(ctx context.Context, chatID string, param MaintenanceTaskRunningParam) (*string /*message id*/, error)
	MaintenanceTaskClosed(ctx context.Context, chatID string, param MaintenanceTaskClosedParam) (*string /*message id*/, error)

	// incident related
	IncidentTopic(ctx context.Context, chatID string, param IncidentTopicParam) (*string /*message id*/, error)
	IncidentToDoActionTCOCPCreated(ctx context.Context, email string, param IncidentToDoActionTCOCPCreatedParam) (*string, error)
	IncidentToDoActionOverdue(ctx context.Context, email string, param IncidentToDoActionOverdueParam) (*string, error)
	IncidentToDoActionOverdueSummary(ctx context.Context, email string, param IncidentToDoActionOverdueSummaryParam) (*string, error)
	GetTopicReplies(ctx context.Context, chatID string, topicID string, startTime string, endTime string) ([]*larkim.Message, error)

	// approval related
	ApprovalStart(ctx context.Context, userID string, param ApprovalStartParam) (*string /*msg id*/, error)
	ApprovalEnd(ctx context.Context, userID string, param ApprovalEndParam) (*string /*msg id*/, error)
	ApprovalTaskStart(ctx context.Context, userID string, param ApprovalTaskStartParam) (*string /*msg id*/, error)
	ApprovalTaskEnd(ctx context.Context, msgID string, param ApprovalTaskEndParam) error
	ApprovalCC(ctx context.Context, userID string, param ApprovalCCParam) (*string /*msg id*/, error)

	// alert related
	NotifyLongTailAlert(ctx context.Context, userID string, param NotifyAlertParam) (*string /*message id*/, error)
	NotifyLongTailTicketsUnify(ctx context.Context, userID string, param NotifyLongTailTicketsUnifyParam) (*string /*message id*/, error)
	NotifyJiraIssueForS1Ticket(ctx context.Context, chatID string, param JiraIssueInformationForS1TicketsParam) (*string /*message id*/, error)
}

func New(larkCli *lark.Client, msgCards MessageCardSet) Notification {
	cli := &client{
		larkCli:  larkCli,
		msgCards: msgCards,
	}

	return cli
}

type client struct {
	larkCli  *lark.Client
	msgCards MessageCardSet
}

// 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, param Param) (*string /*message id*/, error) {
	logger := log.FromContext(ctx)

	msg, err := param.Message(c.msgCards)
	if err != nil {
		return nil, err
	}

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

	content, err := larkutil.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, param Param) error {
	logger := log.FromContext(ctx)

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

	msg, err := param.Message(c.msgCards)
	if err != nil {
		return err
	}

	body := larkutil.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, param Param) error {
	logger := log.FromContext(ctx)

	msg, err := param.Message(c.msgCards)
	if err != nil {
		return err
	}

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

	content, err := larkutil.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, param Param, replyInThread bool) (*string /*message id*/, error) {
	logger := log.FromContext(ctx)

	msg, err := param.Message(c.msgCards)
	if err != nil {
		return nil, err
	}

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

	content, err := larkutil.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 resp.Code != 0 {
		return nil, fmt.Errorf("list messages error, %v", resp.Msg)
	}
	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
}
