package feishubot

import (
	"bytes"
	"context"
	"encoding/json"
	"fmt"
	"io/ioutil"
	"log"
	"net/http"
	"strconv"
	"strings"
	"sync"
	"time"

	"github.com/golang/freetype/truetype"
	"github.com/larksuite/oapi-sdk-go/api/core/request"
	"github.com/larksuite/oapi-sdk-go/core"
	v1 "github.com/larksuite/oapi-sdk-go/service/im/v1"
	"github.com/spf13/cast"
	"github.com/wcharczuk/go-chart/v2"
	"github.com/wcharczuk/go-chart/v2/drawing"
	"gorm.io/gorm"

	"github.com/pingcap-inc/lark-bots/v2/hr"
	"github.com/pingcap-inc/lark-bots/v2/pkg/config"
	"github.com/pingcap-inc/lark-bots/v2/pkg/db"
	"github.com/pingcap-inc/lark-bots/v2/pkg/job"
	"github.com/pingcap-inc/lark-bots/v2/pkg/lark"
	"github.com/pingcap-inc/lark-bots/v2/pkg/util"
)

const (
	timeFormat = "2006-01-02 15:04:05"
)

type App struct {
	*gorm.DB
	Lark           *lark.Lark
	Questionnaires map[int]Questionnaire
	Lock           sync.Mutex
	Messages       map[string]QuestionnaireMessage
	Answers        map[string]map[string]string // openMessageId -> {question, answer}
	*http.Server
}

func (app *App) Run() {
	sheetToken := config.GetString("sheettoken")

	sheetId, err := app.Lark.GetSheetId(sheetToken, 1)
	if err != nil {
		log.Println("[error] GetSheetId err ", err)
		return
	}

	sheetData, err := app.Lark.GetSheetData(sheetToken, fmt.Sprintf("%s!A:H", sheetId))
	if err != nil {
		log.Println("[error] GetSheetData err ", err)
		return
	}
	const headerRowNum = 2

	if len(sheetData)-headerRowNum <= len(app.Questionnaires) {
		return
	}

	for i := headerRowNum + len(app.Questionnaires); i < len(sheetData); i++ {
		columns := sheetData[i].([]interface{})
		q := Questionnaire{ID: len(app.Questionnaires) + 1,
			Name:              cast.ToString(columns[0]),
			TargetList:        cast.ToString(columns[1]),
			BlackList:         cast.ToString(columns[2]),
			NotificationTimes: cast.ToString(columns[3]),
			QuestionCt:        cast.ToInt(columns[4]),
			ConfigStr:         cast.ToString(columns[7]),
		}

		switch columns[6].(type) {
		case string:
			q.CardContent = cast.ToString(columns[6])
		case []interface{}:
			ss := strings.Builder{}
			for _, row := range columns[6].([]interface{}) {
				ss.WriteString(row.(map[string]interface{})["text"].(string))
			}
			q.CardContent = ss.String()
		}

		q.CronTimes = strings.Split(q.NotificationTimes, ",")
		json.Unmarshal(util.StringToByteSlice(q.CardContent), &q.Card)
		json.Unmarshal(util.StringToByteSlice(q.ConfigStr), &q.Config)
		q.AppInst = app
		log.Println("Add new Questionnaire ", q)

		app.Lock.Lock()
		app.Questionnaires[q.ID] = q
		app.Lock.Unlock()

		app.DB.Save(q)

		q.Schd()
	}
}

func NewApp() *App {
	var err error
	app := App{}
	app.DB, err = db.InitDb(config.GetString("db"))
	if err != nil {
		log.Println("DB init failed ", err)
		return nil
	}

	app.Lark = lark.NewLark()

	app.Questionnaires = make(map[int]Questionnaire)
	app.Answers = make(map[string]map[string]string)
	app.Messages = make(map[string]QuestionnaireMessage)

	return &app
}

func (app *App) OnStarUp() error {
	job.Start()
	err := app.DB.AutoMigrate(&Questionnaire{}, &QuestionnaireAnswer{}, &QuestionnaireMessage{}, &hr.HREmployee{}, &hr.HRDepartment{}, &hr.HREmployeeDepartment{})
	if err != nil {
		return err
	}

	answers := []QuestionnaireAnswer{}
	_ = app.DB.Find(&answers).Error
	for _, answer := range answers {
		if _, ok := app.Answers[answer.OpenMessageID]; !ok {
			app.Answers[answer.OpenMessageID] = make(map[string]string)
		}
		app.Answers[answer.OpenMessageID][answer.Question] = answer.Answer
	}

	questionnaires := []Questionnaire{}
	_ = app.DB.Find(&questionnaires).Error

	for _, questionnaire := range questionnaires {
		questionnaire.CronTimes = strings.Split(questionnaire.NotificationTimes, ",")
		json.Unmarshal(util.StringToByteSlice(questionnaire.CardContent), &questionnaire.Card)
		json.Unmarshal(util.StringToByteSlice(questionnaire.ConfigStr), &questionnaire.Config)
		app.Questionnaires[questionnaire.ID] = questionnaire
	}

	// todo cjl test
	// for _, questionnaire := range app.Questionnaires {
	// 	questionnaire.AppInst = app
	// 	questionnaire.Schd()
	// }

	// job.AddJob("@every 5m", app)
	// job.AddJob("@every 12h", job.JobRunner(func() {
	// 	hr.LoadAndSaveEmployees(app.DB)
	// 	hr.FetchDepartments(app.DB)
	// }))

	// if config.GetString("runonece") == "true" {
	// 	hr.LoadAndSaveEmployees(app.DB)
	// }

	shsFont = getSourceHanSansFont()
	return nil
}

type Response struct {
	Code    int         `json:"code"`
	Message string      `json:"message"`
	Data    interface{} `json:"data"`
}

func (app *App) GeneratePNG(title, openId, email string, questionnaireId int) {
	time.Sleep(time.Second)
	res := []struct {
		Question string  `json:"question"`
		Hour     float64 `json:"hour"`
	}{}
	err := app.DB.Raw(`select questionnaire_answers.question, SUM(questionnaire_answers.answer) as hour from questionnaire_answers join questionnaire_messages 
on questionnaire_answers.open_message_id = questionnaire_messages.open_message_id
where questionnaire_messages.email = ? AND questionnaire_messages.questionnaire_id = ?
GROUP BY questionnaire_answers.question ORDER BY hour ASC;`, email, questionnaireId).Find(&res).Error
	if err != nil {
		log.Println("[error] query summary err", err)
		return
	}

	pie := chart.PieChart{
		Title:      title,
		TitleStyle: chart.StyleTextDefaults(),
		Background: chart.Style{
			Padding: chart.Box{
				Top: 0,
			},
		},
		Width:  512,
		Height: 582,
	}
	pie.TitleStyle.Padding.Bottom = 512
	pie.TitleStyle.Padding.Top = 0
	pie.TitleStyle.Font = shsFont
	pie.TitleStyle.FontSize = 20

	sum := 0.0
	for _, tmp := range res {
		sum += tmp.Hour
	}
	minDegree := 21.6
	usedDegree := 0.0

	for i, tmp := range res {
		if tmp.Hour == 0 {
			continue
		}

		degree := tmp.Hour / sum * 360
		if degree < minDegree {
			degree = minDegree
		}

		if i == len(res)-1 {
			degree = 360.0 - usedDegree
		}

		usedDegree += degree
		name := tmp.Question
		ok := true
		if name, ok = questionNameMap[tmp.Question]; !ok {
			name = tmp.Question
		}
		value := chart.Value{
			Label: fmt.Sprintf("%s %sh", name,
				strconv.FormatFloat(tmp.Hour, 'f', -1, 64)),
			Value: degree,
		}
		value.Style.FontSize = 18
		value.Style.Font = shsFont
		value.Style.FontColor = drawing.ColorWhite
		value.Style.FillColor = color[i]
		pie.Values = append(pie.Values, value)
	}

	imageBytes := bytes.NewBuffer([]byte{})
	pie.Render(chart.PNG, imageBytes)

	imageUpReq := app.Lark.LarkMessageService.Images.Create(core.WrapContext(context.Background()))
	imageUpReq.SetImageType("message")
	imageFile := request.File{}
	imageFile.SetName(title)
	imageFile.SetType("png")
	imageFile.SetContent(imageBytes.Bytes())
	imageUpReq.SetImage(&imageFile)

	image, err := imageUpReq.Do()
	if err != nil {
		log.Println(err)
	} else {
		imageBytes, _ := json.Marshal(image)
		req := app.Lark.LarkMessageService.Messages.Create(core.WrapContext(context.Background()),
			&v1.MessageCreateReqBody{ReceiveId: openId,
				Content: util.ByteSliceToString(imageBytes),
				MsgType: "image",
			})
		req.SetReceiveIdType("open_id")

		_, err := req.Do()
		if err != nil {
			log.Println("[error] ", err)
		}
	}
}

var shsFont *truetype.Font
var color []drawing.Color
var questionNameMap map[string]string

func getSourceHanSansFont() *truetype.Font {
	fontFile := "./SourceHanSans-Bold.ttf"

	// 读字体数据
	fontBytes, err := ioutil.ReadFile(fontFile)
	if err != nil {
		log.Println(err)
		return nil
	}
	font, err := truetype.Parse(fontBytes)
	if err != nil {
		log.Println(err)
		return nil
	}

	return font
}

func init() {
	questionNameMap = map[string]string{
		"meeting": "开会",
		"coding":  "编码",
		"support": "支持",
	}
	color = append(color,
		drawing.Color{145, 152, 252, 255},
		drawing.Color{158, 208, 72, 255},
		drawing.Color{255, 198, 75, 255},
		drawing.Color{149, 170, 169, 255},
		drawing.Color{253, 164, 175, 255},
		drawing.Color{250, 137, 84, 255},
		drawing.Color{216, 230, 152, 255},
	)
}
