package service

import (
	"bytes"
	"context"
	"encoding/csv"
	"encoding/json"
	"fmt"
	"io"
	"net/http"
	"os"
	"strconv"
	"strings"
	"time"

	"cloud.google.com/go/bigquery"
	"go.uber.org/zap"

	"finance-portal/internal/dataplatform/store"
	"finance-portal/internal/pkg/code"
	"finance-portal/model"
	"finance-portal/pkg/errors"
	"finance-portal/pkg/log"
)

const (
	LarkOpenAPIBase = "https://open.feishu.cn/open-apis"

	dataPlatformKey = "data-platform-"

	larkTok = "lark-token"
)

type commonService struct {
	store store.Factory
}

type ComSrv interface {
	GetDataByReqLarkAPI(ctx context.Context, m, url string, body io.Reader, params map[string]string, ttoken string) (json.RawMessage, error)
	ReadCSV(ctx context.Context, filePath string) ([][]string, error)
	WriteCSV(ctx context.Context, filePath string, records [][]string) error
}

var _ ComSrv = (*commonService)(nil)

func newCommon(store store.Factory) *commonService {
	return &commonService{
		store: store,
	}
}

func (cs *commonService) GetDataByReqLarkAPI(ctx context.Context, m, url string, body io.Reader, params map[string]string, ttoken string) (json.RawMessage, error) {
	log.L(ctx).Info("GetDataByReqLarkAPI Called.")

	tat := ttoken
	if tat == "" {
		rtat, err := tokenCache.GetToken(ctx, "lark")
		if err != nil {
			log.L(ctx).Error(err.Error())

			return nil, errors.Wrap(err, "Error when GetLarkToken")
		}
		tat = rtat
	}

	ctx, cancel := context.WithTimeout(context.Background(), 10*time.Second)
	defer cancel()
	req, _ := http.NewRequestWithContext(ctx, m, url, body)

	query := req.URL.Query()
	if params != nil || len(params) != 0 {
		for k, v := range params {
			query.Set(k, v)
		}
	}
	req.URL.RawQuery = query.Encode()
	req.Header.Set("Authorization", "Bearer "+tat)
	resp, err := (&http.Client{}).Do(req)
	if err != nil {
		log.L(ctx).Error(err.Error())
		err = fmt.Errorf("error when request lark api: %s %s, err: %w", m, url, err)

		return nil, err
	}
	defer func() {
		_ = resp.Body.Close()
	}()

	b, _ := io.ReadAll(resp.Body)
	var res model.LarkCommonRes
	if err := json.Unmarshal(b, &res); err != nil {
		log.L(ctx).Error(err.Error())
		err = fmt.Errorf("error when unmarshal lark api res: %s %s, err: %w", m, url, err)

		return nil, err
	}
	if res.Code != 0 {
		log.L(ctx).Errorf("Lark API error code: %d, msg: %s", res.Code, res.Msg)
		err = fmt.Errorf("lark Error Code: %d", res.Code)

		return nil, err
	}

	return res.Data, nil
}

func (cs *commonService) saveLarkOps(ctx context.Context, level int, errMsg string) {
	log.L(ctx).Info("start saveLarkOps")

	cs.store.Common().SaveError(model.ErrorLog{
		Severity:     level,
		ErrorMessage: errMsg,
	})

	tk, err := tokenCache.GetToken(ctx, "lark")

	if err != nil {
		log.L(ctx).Errorf("error GetLarkToken: %v", err)

		return
	}

	url := "/bitable/v1/apps/FrqGbdNEYaExAwslQp0cDXo1noK/tables/tblb4kYjTOL63dfx/records"

	body := map[string]interface{}{
		"fields": model.DataPlatformLog{
			Level: strconv.Itoa(level),
			Msg:   errMsg,
		},
	}

	marshalBody, _ := json.Marshal(body)

	_, err = comSrv.GetDataByReqLarkAPI(ctx, "POST", LarkOpenAPIBase+url, bytes.NewReader(marshalBody), nil, tk)
	if err != nil {
		log.L(ctx).Errorf("error GetDataByReqLarkAPI: %v", err)

		return
	}

	log.L(ctx).Info("end saveLarkOps")

	return
}

type MessageBody struct {
	ReceiveID string `json:"receive_id,omitempty"`
	MsgType   string `json:"msg_type"`
	Content   string `json:"content"`
	Uuid      string `json:"uuid"`
}

func SendLarkMsg(ctx context.Context, idType string, msgBody MessageBody) (*model.LarkReplyMsgResData, error) {
	reqb, err := json.Marshal(msgBody)
	if err != nil {
		log.L(ctx).Errorf("error when marshal reply msg request body, err: %s", err.Error())

		return nil, errors.WithCode(code.ErrEncodingJSON, "")
	}

	tok, _ := tokenCache.GetToken(ctx, "lark")

	jsonraw, err := comSrv.GetDataByReqLarkAPI(ctx, "POST", LarkOpenAPIBase+"/im/v1/messages?receive_id_type="+idType, bytes.NewReader(reqb), nil, tok)
	if err != nil {
		log.L(ctx).Errorf("error when call lark send msg api, err: %s", err.Error())

		return nil, errors.WithCode(code.ErrHttpRequest, "")
	}

	var larkm model.LarkReplyMsgResData
	if err = json.Unmarshal(jsonraw, &larkm); err != nil {
		log.L(ctx).Errorf("error when unmarshal lark msg, err: %s", err.Error())

		return nil, errors.WithCode(code.ErrDecodingJSON, "")
	}

	return &larkm, nil
}

func GetLastMonth() string {
	now := time.Now()
	// 计算上个月的时间
	lastMonth := now.AddDate(0, -1, 0)

	// 将时间格式化为 "YYYY-MM" 格式
	s := lastMonth.Format("2006-01")

	return s
}

func TrimSplit(s string) []string {
	// remove [
	s = strings.TrimPrefix(s, "[")
	// remove ]
	s = strings.TrimSuffix(s, "]")
	//  split by ,
	s = strings.ReplaceAll(s, "null", "")
	s = strings.ReplaceAll(s, "\"", "")
	return strings.Split(s, ",")
}

func TripleExpression(s, expect, re, unre string) string {
	if s == expect {
		return re
	}

	return unre
}

func DateToString(d bigquery.NullDate) string {
	if d.Valid {
		// Format the Date as a string in the form "yyyy-mm-dd".
		return fmt.Sprintf("%04d-%02d-%02d", d.Date.Year, d.Date.Month, d.Date.Day)
	} else {
		// If the Date is not valid, return an empty string or some other placeholder.
		return ""
	}
}

func (cs *commonService) ReadCSV(ctx context.Context, filePath string) ([][]string, error) {
	file, err := os.Open(filePath)
	if err != nil {
		log.L(ctx).Error("Failed to open CSV file", zap.String("filePath", filePath), zap.Error(err))

		return nil, errors.Wrap(err, "failed to open CSV file")
	}
	defer file.Close()

	reader := csv.NewReader(file)
	records, err := reader.ReadAll()
	if err != nil {
		log.L(ctx).Error("Failed to read CSV records", zap.Error(err))

		return nil, errors.Wrap(err, "failed to read CSV records")
	}

	return records, nil
}

func (cs *commonService) WriteCSV(ctx context.Context, filePath string, records [][]string) error {
	file, err := os.Create(filePath)
	if err != nil {
		log.L(ctx).Error("Failed to create CSV file", zap.String("filePath", filePath), zap.Error(err))

		return errors.Wrap(err, "failed to create CSV file")
	}
	defer file.Close()

	writer := csv.NewWriter(file)
	defer writer.Flush()

	if err = writer.WriteAll(records); err != nil {
		log.L(ctx).Error("Failed to write CSV records", zap.Error(err))

		return errors.Wrap(err, "failed to write CSV records")
	}

	return nil
}
