package main

import (
	"bytes"
	"encoding/json"
	"fmt"
	"net/http"
	"net/url"
	"os"
	"path/filepath"
	"time"

	"github.com/huaweicloud/huaweicloud-sdk-go-v3/core/auth/basic"
	elb "github.com/huaweicloud/huaweicloud-sdk-go-v3/services/elb/v3"
	elbModel "github.com/huaweicloud/huaweicloud-sdk-go-v3/services/elb/v3/model"
	elbRegion "github.com/huaweicloud/huaweicloud-sdk-go-v3/services/elb/v3/region"
	"github.com/spf13/cobra"
	"github.com/spf13/viper"
)

var (
	cfgFile string
	action  string
	region  string
	id      string
	msg     string
	notice  bool = false
)

var rootCmd = &cobra.Command{
	Use:   "tlsupdate",
	Short: "check and update tls",
	Run: func(cmd *cobra.Command, args []string) {
		// 如果配置文件存在,则读取配置文件
		if cfgFile != "" {
			viper.SetConfigFile(cfgFile)
			if err := viper.ReadInConfig(); err != nil {
				fmt.Println("Error reading config file:", err)
				os.Exit(1)
			}
		}

		// 获取配置
		ak := viper.GetString("huawei.ak")
		sk := viper.GetString("huawei.sk")
		notify := viper.GetString("notify")
		webhook := viper.GetString("webhook")
		tlsConfig := viper.Get("tls").([]interface{})

		// 获取证书id列表
		var ids []string
		for _, tls := range tlsConfig {
			tlsConfigMap := tls.(map[string]interface{})
			ids = append(ids, tlsConfigMap["id"].(string))
		}

		// 如果配置文件中提供飞书链接,则通知用户
		if notify != "" {
			// 是否通知
			notice = true
		}
		//发送用户格式,默认是text文本
		post := false
		// 富文本中的链接
		postUrl := ""

		// 遍历配置中tls列表,如果id匹配,即更新或查看证书状态
		for _, t := range tlsConfig {
			tlsConfigmap := t.(map[string]interface{})
			if id == "" {
				// id为空不能更新,不支持批量更新所有证书
				if action == "update" {
					msg = "请求出错,只能指定id更新tls"
					os.Exit(1)
				}
				// id为空,即获取所有证书的有效期
				if action == "status" {
					for _, i := range ids {
						if tlsConfigmap["id"].(string) == i {
							region = tlsConfigmap["region"].(string)
							// 如果未配置region,默认为上海-1
							if region == "" {
								region = "cn-east-3"
							}
							// 获取状态信息
							post, msg = genStatusMsg(region, ak, sk, i)
							// 构造post请求链接
							postUrl = generateUrl(webhook, "", map[string]interface{}{
								"action": "update",
								"id":     id,
							})
						}
					}
				}
			}
			// id不为空,根据id更新或查看证书有效期
			if tlsConfigmap["id"].(string) == id {
				key, err := readFile(tlsConfigmap["private_key"].(string))
				if err != nil {
					msg = fmt.Sprintf("读取key文件出错:%s", err)
				}
				cert, err := readFile(tlsConfigmap["certificate"].(string))
				if err != nil {
					msg = fmt.Sprintf("读取cert文件出错:%s", err)
				}
				region = tlsConfigmap["region"].(string)
				if region == "" {
					region = "cn-east-3"
				}
				if action == "status" {
					post, msg = genStatusMsg(region, ak, sk, id)
				}
				if action == "update" {
					msg = genUpdateMsg(region, ak, sk, id, key, cert)
				}
				postUrl = generateUrl(webhook, "", map[string]interface{}{
					"action": "update",
					"id":     id,
				})
			}
		}

		// 如果配置有通知链接,则发送通知信息
		if notice {
			// 如果有通知信息,则发送通知
			if msg != "" {
				var data interface{}
				// 如果post为true,即发送飞书富文本信息
				if post {
					// 构造富文本信息
					data = genPostAMsg("华为云TLS", msg, "点击续签TLS证书", postUrl)
				} else {
					// 发送text文本信息
					// 构造文本信息
					data = FeishuTextMessage{
						MsgType: "text",
						Content: TextContent{
							Text: msg,
						},
					}
				}
				// 发送通知
				if err := postRequest(notify, data); err != nil {
					fmt.Println("Error sending message:", err)
					os.Exit(1)
				}
			}
		}
	},
}

func main() {
	cobra.OnInitialize(initConfig)
	rootCmd.Flags().StringVarP(&cfgFile, "config", "c", "", "Config file (default is $HOME/config.yaml, ./config.yaml, or BINARY/config.yaml)")
	rootCmd.Flags().StringVarP(&action, "action", "a", "status", "update or status")
	rootCmd.Flags().StringVarP(&id, "id", "i", "", "tls id")

	if err := rootCmd.Execute(); err != nil {
		fmt.Println(err)
		os.Exit(1)
	}
}

// 初始化配置文件
func initConfig() {
	if cfgFile != "" {
		viper.SetConfigFile(cfgFile)
	} else {
		home, err := os.UserHomeDir()
		if err != nil {
			fmt.Println(err)
			os.Exit(1)
		}
		exec, err := os.Executable()
		if err != nil {
			fmt.Println(err)
			os.Exit(1)
		}
		exPath := filepath.Dir(exec)
		viper.AddConfigPath(".")
		viper.AddConfigPath(home)
		viper.AddConfigPath(exPath)
		viper.SetConfigName("config")
	}

	viper.AutomaticEnv() // 从环境变量中读取配置
	if err := viper.ReadInConfig(); err == nil {
		fmt.Println("Using config file:", viper.ConfigFileUsed())
	}
}

// 构建url
func generateUrl(baseUrl, source string, parameters map[string]interface{}) string {
	base, err := url.Parse(baseUrl)
	if err != nil {
		return ""
	}
	base.Path += "/" + source
	query := base.Query()
	for param, value := range parameters {
		query.Add(param, fmt.Sprintf("%v", value))
	}
	base.RawQuery = query.Encode()

	return base.String()
}

// post请求
func postRequest(url string, data interface{}) error {
	jsonData, err := json.Marshal(data)
	if err != nil {
		return err
	}
	resp, err := http.Post(url, "application/json", bytes.NewBuffer(jsonData))
	if err != nil {
		return err
	}
	defer resp.Body.Close()
	if resp.StatusCode != http.StatusOK {
		return fmt.Errorf("HTTP status code: %d", resp.StatusCode)
	}
	return nil
}

// 读取文件
func readFile(path string) (string, error) {
	data, err := os.ReadFile(path)
	if err != nil {
		return "", err
	}
	return string(data), nil
}

// 华为云ak,sk登录校验
func authenticate(region, ak, sk string) *elb.ElbClient {
	auth, err := basic.NewCredentialsBuilder().
		WithAk(ak).
		WithSk(sk).
		SafeBuild()
	if err != nil {
		fmt.Println("Error creating credentials:", err)
		os.Exit(1)
	}
	r, err := elbRegion.SafeValueOf(region)
	if err != nil {
		fmt.Println("Error creating credentials:", err)
		os.Exit(1)
	}

	elbClient, err := elb.ElbClientBuilder().WithRegion(r).WithCredential(auth).SafeBuild()
	if err != nil {
		fmt.Println("Error creating credentials:", err)
		os.Exit(1)
	}
	client := elb.NewElbClient(elbClient)
	return client
}

// tls有效期
func tlsValidity(region, ak, sk, id string) ([]string, string, error) {
	client := authenticate(region, ak, sk)
	request := &elbModel.ShowCertificateRequest{
		CertificateId: id,
	}
	response, err := client.ShowCertificate(request)
	if err != nil {
		fmt.Println("Error creating credentials:", err)
		return nil, "", err
	}
	return *response.Certificate.SubjectAlternativeNames, response.Certificate.ExpireTime, nil
}

// 时间差
func timeDiff(expire string) (bool, error) {

	// 解析时间字符串
	t, err := time.Parse(time.RFC3339, expire)
	if err != nil {
		fmt.Println("解析证书过期时间错误:", err)
		return false, err
	}

	// 获取当前时间
	now := time.Now()

	// 计算时间差
	duration := now.Sub(t)

	// 检查时间差是否小于1天
	if duration > -7*24*time.Hour {
		return true, nil
	}
	return false, nil

}

// 更新tls
func updateTLS(region, ak, sk, id, key, cert string) ([]string, error) {
	client := authenticate(region, ak, sk)
	request := &elbModel.UpdateCertificateRequest{
		CertificateId: id,
	}
	certificatebody := &elbModel.UpdateCertificateOption{

		PrivateKey:  &key,
		Certificate: &cert,
	}
	request.Body = &elbModel.UpdateCertificateRequestBody{
		Certificate: certificatebody,
	}
	response, err := client.UpdateCertificate(request)
	if err != nil {
		fmt.Println("Error creating credentials:", err)
		return []string{}, err
	}
	return *response.Certificate.SubjectAlternativeNames, nil
}

// 构造状态通知信息
func genStatusMsg(region, ak, sk, id string) (bool, string) {
	var message string
	domain, expire, err := tlsValidity(region, ak, sk, id)
	if err != nil {
		message = fmt.Sprintf("获取证书信息失败,请检查配置文件或代码,错误信息:%v", err)
		return false, message
	}
	diff, err := timeDiff(expire)
	if err != nil {
		message = fmt.Sprintf("解析证书过期时间错误,请检查:%v", err)
		return false, message
	}

	if diff {
		message = fmt.Sprintf("域名:%v\n\n证书有效期不足一周,请及时更新:", domain)
		return true, message
	}
	return false, message
}

// 构造更新通知信息
func genUpdateMsg(region, ak, sk, id, key, cert string) string {
	var message string
	domain, err := updateTLS(region, ak, sk, id, key, cert)
	if err != nil {
		message = fmt.Sprintf("Error updating TLS: %v", err)
		return message
	}
	message = fmt.Sprintf("域名:%v\nhttps证书更新成功", domain)
	return message
}

func genPostAMsg(title, message, text, url string) *FeishuPostMessage {
	return &FeishuPostMessage{
		MsgType: "post",
		Content: PostContent{
			Post: Post{
				ZhCn: Message{
					Title: title,
					Content: [][]ContentItem{
						{
							{
								Tag:  "text",
								Text: message,
							},
							{
								Tag:  "a",
								Text: text,
								Href: url,
							},
						},
					},
				},
			},
		},
	}
}