package dogecloud

import (
	"crypto/hmac"
	"crypto/sha1"
	"encoding/hex"
	"encoding/json"
	"fmt"
	"io"
	"net/http"
	"net/url"
	"strings"
)

const dogeHost = "https://api.dogecloud.com"

type Client struct {
	accessKey string
	secretKey string
}

func NewClient(accessKey, secretKey string) *Client {
	return &Client{accessKey: accessKey, secretKey: secretKey}
}

func (c *Client) UploadCdnCert(note, cert, private string) (*UploadCdnCertResponse, error) {
	req := &UploadCdnCertRequest{
		Note:        note,
		Certificate: cert,
		PrivateKey:  private,
	}

	reqBts, err := json.Marshal(req)
	if err != nil {
		return nil, err
	}

	reqMap := make(map[string]interface{})
	err = json.Unmarshal(reqBts, &reqMap)
	if err != nil {
		return nil, err
	}

	respBts, err := c.sendReq(http.MethodPost, "cdn/cert/upload.json", reqMap, true)
	if err != nil {
		return nil, err
	}

	resp := &UploadCdnCertResponse{}
	err = json.Unmarshal(respBts, resp)
	if err != nil {
		return nil, err
	}
	if resp.Code != nil && *resp.Code != 0 && *resp.Code != 200 {
		return nil, fmt.Errorf("dogecloud api error, code: %d, msg: %s", *resp.Code, *resp.Message)
	}

	return resp, nil
}

func (c *Client) BindCdnCertWithDomain(certId int64, domain string) (*BindCdnCertResponse, error) {
	req := &BindCdnCertRequest{
		CertId: certId,
		Domain: &domain,
	}

	reqBts, err := json.Marshal(req)
	if err != nil {
		return nil, err
	}

	reqMap := make(map[string]interface{})
	err = json.Unmarshal(reqBts, &reqMap)
	if err != nil {
		return nil, err
	}

	respBts, err := c.sendReq(http.MethodPost, "cdn/cert/bind.json", reqMap, true)
	if err != nil {
		return nil, err
	}

	resp := &BindCdnCertResponse{}
	err = json.Unmarshal(respBts, resp)
	if err != nil {
		return nil, err
	}
	if resp.Code != nil && *resp.Code != 0 && *resp.Code != 200 {
		return nil, fmt.Errorf("dogecloud api error, code: %d, msg: %s", *resp.Code, *resp.Message)
	}

	return resp, nil
}

func (c *Client) BindCdnCertWithDomainId(certId int64, domainId int64) (*BindCdnCertResponse, error) {
	req := &BindCdnCertRequest{
		CertId:   certId,
		DomainId: &domainId,
	}

	reqBts, err := json.Marshal(req)
	if err != nil {
		return nil, err
	}

	reqMap := make(map[string]interface{})
	err = json.Unmarshal(reqBts, &reqMap)
	if err != nil {
		return nil, err
	}

	respBts, err := c.sendReq(http.MethodPost, "cdn/cert/bind.json", reqMap, true)
	if err != nil {
		return nil, err
	}

	resp := &BindCdnCertResponse{}
	err = json.Unmarshal(respBts, resp)
	if err != nil {
		return nil, err
	}
	if resp.Code != nil && *resp.Code != 0 && *resp.Code != 200 {
		return nil, fmt.Errorf("dogecloud api error, code: %d, msg: %s", *resp.Code, *resp.Message)
	}

	return resp, nil
}

// 调用多吉云的 API。
// https://docs.dogecloud.com/cdn/api-access-token?id=go
//
// 入参：
//   - method：GET 或 POST
//   - path：是调用的 API 接口地址，包含 URL 请求参数 QueryString，例如：/console/vfetch/add.json?url=xxx&a=1&b=2
//   - data：POST 的数据，对象，例如 {a: 1, b: 2}，传递此参数表示不是 GET 请求而是 POST 请求
//   - jsonMode：数据 data 是否以 JSON 格式请求，默认为 false 则使用表单形式（a=1&b=2）
func (c *Client) sendReq(method string, path string, data map[string]interface{}, jsonMode bool) ([]byte, error) {
	body := ""
	mime := ""
	if jsonMode {
		_body, err := json.Marshal(data)
		if err != nil {
			return nil, err
		}
		body = string(_body)
		mime = "application/json"
	} else {
		values := url.Values{}
		for k, v := range data {
			values.Set(k, v.(string))
		}
		body = values.Encode()
		mime = "application/x-www-form-urlencoded"
	}

	path = strings.TrimPrefix(path, "/")
	signStr := "/" + path + "\n" + body
	hmacObj := hmac.New(sha1.New, []byte(c.secretKey))
	hmacObj.Write([]byte(signStr))
	sign := hex.EncodeToString(hmacObj.Sum(nil))
	auth := fmt.Sprintf("TOKEN %s:%s", c.accessKey, sign)

	req, err := http.NewRequest(method, fmt.Sprintf("%s/%s", dogeHost, path), strings.NewReader(body))
	if err != nil {
		return nil, err
	}
	req.Header.Set("Content-Type", mime)
	req.Header.Set("Authorization", auth)

	client := http.Client{}
	resp, err := client.Do(req)
	if err != nil {
		return nil, err
	}
	defer resp.Body.Close()

	bytes, err := io.ReadAll(resp.Body)
	if err != nil {
		return nil, err
	}

	return bytes, nil
}
