package feishu

import (
	"bytes"
	"encoding/json"
	"fmt"
	"io"
	"io/ioutil"
	"net/http"

	"github.com/pingcap/log"
	"go.uber.org/zap"
)

type HttpTransport struct {
	HttpClient    HttpClient
	Authenticator HttpAuthenticator
}

func (c *HttpTransport) callGET(apiPath string, output interface{}) error {
	return c.callWithNoPayload(apiPath, "GET", output)
}

func (c *HttpTransport) callPUT(apiPath string, payload interface{}, output interface{}) error {
	return c.callWithPayload(apiPath, "PUT", payload, output)
}

func (c *HttpTransport) callPOST(apiPath string, payload interface{}, output interface{}) error {
	return c.callWithPayload(apiPath, "POST", payload, output)
}

func (c *HttpTransport) callDELETE(apiPath string, output interface{}) error {
	return c.callWithNoPayload(apiPath, "DELETE", output)
}

func (c *HttpTransport) callWithPayload(apiPath, method string, payload interface{}, output interface{}) error {
	body, err := json.Marshal(payload)
	if err != nil {
		return err
	}
	respBody, err := c.Submit(apiPath, method, bytes.NewReader(body))
	if err != nil {
		return err
	}
	return json.Unmarshal(respBody, output)
}

func (c *HttpTransport) callWithNoPayload(apiPath, method string, output interface{}) error {
	body, err := c.Submit(apiPath, method, nil)
	if err != nil {
		return err
	}
	return json.Unmarshal(body, output)
}

func (c *HttpTransport) Submit(apiPath, method string, payload io.Reader) ([]byte, error) {
	log.Info("call feishu api", zap.String("method", method), zap.String("path", apiPath))
	req, err := http.NewRequest(method, apiPath, payload)
	if err != nil {
		return nil, err
	}
	if err := c.Authenticator.Auth(req); err != nil {
		return nil, err
	}
	// #nosec
	resp, err := c.HttpClient.Do(req)
	if err != nil {
		return nil, err
	}
	// #nosec
	defer deferClose(resp.Body)
	bodyContent, err := ioutil.ReadAll(resp.Body)
	if err != nil {
		return nil, err
	}
	if resp.StatusCode != 200 {
		return nil, fmt.Errorf("call API failed with status code %d, msg:%s", resp.StatusCode, string(bodyContent))
	}
	return bodyContent, nil
}

func deferClose(c io.Closer) {
	if err := c.Close(); err != nil {
		log.Error("failed to close", zap.Error(err))
	}
}
