package main

import (
	"bytes"
	"context"
	"encoding/json"
	"fmt"
	"html/template"
	"strings"
	"time"

	"github.com/crispgm/go-g/string/tokenizer"
	"github.com/gin-gonic/gin"
	"github.com/larksuite/oapi-sdk-go/api"
	"github.com/larksuite/oapi-sdk-go/api/core/request"
	"github.com/larksuite/oapi-sdk-go/api/core/response"
	"github.com/larksuite/oapi-sdk-go/core"
	"github.com/larksuite/oapi-sdk-go/core/config"
	"github.com/larksuite/oapi-sdk-go/core/tools"
	eventhttp "github.com/larksuite/oapi-sdk-go/event/http"
	im "github.com/larksuite/oapi-sdk-go/service/im/v1"
)

var apiKey string = "6bCeac5frpCwg89afMqkzorrtFg6Hb"

func main() {
	var appSettings = core.GetInternalAppSettingsByEnv()
	var conf = core.NewConfig(core.DomainFeiShu, appSettings, core.SetLoggerLevel(core.LoggerLevelDebug))
	im.SetMessageReceiveEventHandler(conf, func(ctx *core.Context, event *im.MessageReceiveEvent) error {
		// fmt.Println(ctx.GetRequestID())
		// fmt.Println(tools.Prettify(event))
		cmdtext, err := parseReceiveMessageEvent(event)
		if err != nil {
			fmt.Println("parse message receive event error: ", ctx.GetRequestID(), tools.Prettify(event))
			return err
		}
		fmt.Println("Receive message command: ", cmdtext)
		if err := executeMessageCommand(conf, event, cmdtext); err != nil {
			fmt.Println("execute message receive event error: ", ctx.GetRequestID(), tools.Prettify(event))
			return err
		}
		return nil
	})

	g := gin.Default()

	g.POST("/webhook/event", func(context *gin.Context) {
		eventhttp.Handle(conf, context.Request, context.Writer)
	})

	if err := g.Run(":8080"); err != nil {
		fmt.Println(err)
	}
}

func parseReceiveMessageEvent(event *im.MessageReceiveEvent) (string, error) {
	type Content struct {
		Text string `json:"text"`
	}

	var body Content
	err := json.Unmarshal([]byte(event.Event.Message.Content), &body)
	if err != nil {
		return "", err
	}

	text := body.Text
	for _, v := range event.Event.Message.Mentions {
		text = strings.ReplaceAll(text, v.Key, "")
	}

	return text, nil
}

func executeMessageCommand(conf *config.Config, event *im.MessageReceiveEvent, cmdtext string) error {
	parser := tokenizer.NewTokenizer(cmdtext, tokenizer.DefaultDelimiters, " ")
	tokens := parser.Parse()

	if len(tokens) == 0 {
		return sendUsageMessageCard(conf, event)
	}

	cmdToken := tokens[0]
	// fmt.Printf("Execute message command: %s\n", cmdToken)

	switch strings.ToLower(cmdToken) {
	case "refresh-dir":
		urls := []string{}
		dirs := []string{}
		if len(tokens) > 1 {
			for _, t := range tokens[1:] {
				dirs = append(dirs, strings.Trim(t, "\""))
			}
		} else {
			dirs = append(dirs,
				"https://en.pingcap.com/wp-json/hellobar/get/*",
				"https://en.pingcap.com/blog/**",
				"https://en.pingcap.com/case-study/**",
				"https://en.pingcap.com/ebook-whitepaper/**",
				"https://en.pingcap.com/event/**")
		}

		go func() {
			taskid, err := createPurgeReqeust(urls, dirs)
			if err != nil {
				fmt.Println("create purge request error: ", err)
			}

			fmt.Println("create purge request success, taskid: ", taskid)

			retry := 6
			for i := 0; i < retry; i++ {
				time.Sleep(10 * time.Second)
				status, rate, files, dirs, finishTime, err := getPurgeRequestStatus(taskid)
				if err != nil {
					fmt.Println("get purge request status error: ", err)
					return
				}
				if status == "finished" && rate >= 99.99 {
					if err := sendSuccessMessage(conf, event, files, dirs, finishTime); err != nil {
						fmt.Println("send success message error: ", err)
					}
					return
				}
			}
			fmt.Printf("failed to get purge request status after %d retries\n", retry)
		}()
	case "refresh":
		urls := []string{}
		dirs := []string{}
		if len(tokens) > 1 {
			for _, t := range tokens[1:] {
				urls = append(urls, strings.Trim(t, "\""))
			}
		} else {
			urls = append(urls,
				"https://en.pingcap.com/",
				"https://en.pingcap.com/tidb/",
				"https://en.pingcap.com/tidb-cloud/",
				"https://en.pingcap.com/blog/",
				"https://en.pingcap.com/event/",
				"https://en.pingcap.com/customers/",
				"https://en.pingcap.com/community/",
				"https://en.pingcap.com/ebook-whitepaper/")
			dirs = append(dirs,
				"https://en.pingcap.com/wp-json/hellobar/get/*",
				"https://en.pingcap.com/blog/**",
				"https://en.pingcap.com/case-study/**",
				"https://en.pingcap.com/ebook-whitepaper/**",
				"https://en.pingcap.com/event/**")
		}

		go func() {
			taskid, err := createPurgeReqeust(urls, dirs)
			if err != nil {
				fmt.Println("create purge request error: ", err)
			}

			fmt.Println("create purge request success, taskid: ", taskid)

			retry := 6
			for i := 0; i < retry; i++ {
				time.Sleep(10 * time.Second)
				status, rate, files, dirs, finishTime, err := getPurgeRequestStatus(taskid)
				if err != nil {
					fmt.Println("get purge request status error: ", err)
					return
				}
				if status == "finished" && rate >= 99.99 {
					if err := sendSuccessMessage(conf, event, files, dirs, finishTime); err != nil {
						fmt.Println("send success message error: ", err)
					}
					return
				}
			}
			fmt.Printf("failed to get purge request status after %d retries\n", retry)
		}()
	default:
		return sendUsageMessageCard(conf, event)
	}
	return nil
}

func sendUsageMessageCard(conf *config.Config, event *im.MessageReceiveEvent) error {
	cardContent :=
		` {
			"config": {
			  "wide_screen_mode": true
			},
			"elements": [
			  {
				"extra": {
				  "alt": {
					"content": "",
					"tag": "plain_text"
				  },
				  "img_key": "img_v2_41d50954-cea3-41ec-b5c6-8155b1576aeg",
				  "tag": "img"
				},
				"tag": "div",
				"text": {
				  "content": "The PingCAP English website (https://en.pingcap.com/) applies dynamic CDN acceleration technology to enable visitors all over the world to have a similar experience. However, the CDN cache takes 24 hours by default to refresh to the latest content, this bot provides commands to help the content authors perform the operation of purging the cache immediately.\n\n**Usage: @cdn-helper &lt; command &gt; [args]**\n\n",
				  "tag": "lark_md"
				}
			  },
			  {
				"fields": [
				  {
					"is_short": false,
					"text": {
					  "content": "**🔹 Refresh the cache of most first-level pages, including the content of hello-bar**\n    @cdn-helper refresh",
					  "tag": "lark_md"
					}
				  },
				  {
					"is_short": false,
					"text": {
					  "content": "",
					  "tag": "lark_md"
					}
				  },
				  {
					"is_short": false,
					"text": {
					  "content": "**🔹 Refresh the content of the given page URLs**\n    @cdn-helper refresh &lt; page_urls... &gt\n    Example: @cdn-helper refresh https://en.pingcap.com/ebook-whitepaper/",
					  "tag": "lark_md"
					}
				  },
				  {
					"is_short": false,
					"text": {
					  "content": "",
					  "tag": "lark_md"
					}
				  },
				  {
					"is_short": false,
					"text": {
					  "content": "**🔹 Refresh a batch of URLs with a specified path prefix by wildcards**\n    Example:\n    @cdn-helper refresh-dir\n    @cdn-helper refresh-dir https://en.pingcap.com/blog/**",
					  "tag": "lark_md"
					}
				  },
				  {
					"is_short": false,
					"text": {
					  "content": "",
					  "tag": "lark_md"
					}
				  },
				  {
					"is_short": false,
					"text": {
					  "content": "**🔹 Show this help output**\n    @cdn-helper help",
					  "tag": "lark_md"
					}
				  }
				],
				"tag": "div"
			  },
			  {
				"tag": "hr"
			  },
			  {
				"elements": [
				  {
					"content": "❓ For more support please contact @Ken Liu",
					"tag": "lark_md"
				  }
				],
				"tag": "note"
			  }
			],
			"header": {
			  "title": {
				"content": "🤖 Hi - Welcome to use website CDN Helper",
				"tag": "plain_text"
			  }
			}
		  }`

	card := map[string]interface{}{}
	err := json.Unmarshal([]byte(cardContent), &card)
	if err != nil {
		return err
	}

	body := map[string]interface{}{
		"chat_id":  event.Event.Message.ChatId,
		"msg_type": "interactive",
		"card":     card,
	}
	ret := make(map[string]interface{})
	req := request.NewRequestWithNative("/open-apis/message/v4/send", "POST",
		request.AccessTokenTypeTenant, body, &ret,
	)
	ctx := core.WrapContext(context.Background())
	err = api.Send(ctx, conf, req)
	// fmt.Println(ctx.GetRequestID())
	// fmt.Println(ctx.GetHTTPStatusCode())
	if err != nil {
		fmt.Println(tools.Prettify(err))
		e := err.(*response.Error)
		fmt.Println(e.Code)
		fmt.Println(e.Msg)
		return err
	}

	// fmt.Println(tools.Prettify(ret))
	return nil
}

func sendSuccessMessage(conf *config.Config, event *im.MessageReceiveEvent, files []string, dirs []string, finishTime time.Time) error {
	cardTemplate :=
		`{
		"config": {
		  "wide_screen_mode": true
		},
		"elements": [
		  {
			"tag": "div",
			"fields": [
			  {
				"is_short": true,
				"text": {
				  "tag": "lark_md",
				  "content": "**Status：**\nFinished\n{{ .FinishTime}}"
				}
			  },
			  {
				"is_short": true,
				"text": {
				  "tag": "lark_md",
				  "content": "**Success Rate：**\n100.0 %"
				}
			  }
			]
		  },
		  {
			"fields": [
			  {
				"is_short": false,
				"text": {
				  "content": "**Purged page cache for URLs**\n{{ .FileURLs}}",
				  "tag": "lark_md"
				}
			  },
			  {
				"is_short": false,
				"text": {
				  "content": "",
				  "tag": "lark_md"
				}
			  },
			  {
				"is_short": false,
				"text": {
				  "content": "**Purged page cache for DIRs**\n{{ .DirURLs}}",
				  "tag": "lark_md"
				}
			  }
			],
			"tag": "div"
		  },
		  {
			"tag": "hr"
		  },
		  {
			"elements": [
			  {
				"content": "❓ For more support please contact @Ken Liu",
				"tag": "lark_md"
			  }
			],
			"tag": "note"
		  }
		],
		"header": {
		  "title": {
			"content": "🤖 CDN Helper - Refresh CDN cache successfully",
			"tag": "plain_text"
		  }
		}
	  }`
	t, err := template.New("msg_card").Parse(cardTemplate)
	if err != nil {
		return err
	}

	cardData := struct {
		FinishTime string
		FileURLs   string
		DirURLs    string
	}{
		FinishTime: finishTime.Format(time.RFC822),
		FileURLs:   strings.Join(files, "\\n"),
		DirURLs:    strings.Join(dirs, "\\n"),
	}

	cardContent := new(bytes.Buffer)
	if err := t.Execute(cardContent, cardData); err != nil {
		return err
	}

	card := map[string]interface{}{}
	if err := json.Unmarshal(cardContent.Bytes(), &card); err != nil {
		return err
	}

	body := map[string]interface{}{
		"chat_id":  event.Event.Message.ChatId,
		"msg_type": "interactive",
		"card":     card,
	}
	ret := make(map[string]interface{})
	req := request.NewRequestWithNative("/open-apis/message/v4/send", "POST",
		request.AccessTokenTypeTenant, body, &ret,
	)
	ctx := core.WrapContext(context.Background())
	err = api.Send(ctx, conf, req)
	// fmt.Println(ctx.GetRequestID())
	// fmt.Println(ctx.GetHTTPStatusCode())
	if err != nil {
		fmt.Println(tools.Prettify(err))
		e := err.(*response.Error)
		fmt.Println(e.Code)
		fmt.Println(e.Msg)
		return err
	}

	// fmt.Println(tools.Prettify(ret))
	return nil
}
