package db

import (
	"io/ioutil"
	"olympus/oncall/backend/pkg/log"

	"github.com/spf13/viper"
	"gopkg.in/yaml.v2"
)

type Round struct {
	Shifts []*Shift `yaml:"shifts"`
}

type Shift struct {
	Category int     `yaml:"category"`
	Robin    [][]int `yaml:"robin"`
}

func GenerateNextRoundByConfig(categoryId int) error {
	content, err := ioutil.ReadFile(viper.GetViper().GetString("round-path"))
	if err != nil {
		log.Error("msg", "fail to load round file")
	}
	round := &Round{}

	err = yaml.Unmarshal(content, round)
	if err != nil {
		log.Error("msg", "fail to unmarshal round file")
	}
	var shift Shift
	for _, v := range round.Shifts {
		if v.Category == categoryId {
			shift = *v
			break
		}
	}
	if shift.Category == 0 {
		log.Error("msg", "cannot find category robin information", "category id", categoryId)
		return nil
	}
	// 1. calculate the total weeks(total_week) of next round. round-length * period
	categoryInf, err := GetCategoryInfo(categoryId)
	if err != nil {
		return err
	}
	nextRoundStartWeek := categoryInf.NextRound
	log.Info("msg", "preparing generating next round schedule", "category id", categoryInf.CategoryId, "category name", categoryInf.Name)
	totalWeek := len(shift.Robin) * categoryInf.Period
	const DELETE_BEYOND_NEXT_ROUND = `
		delete from
			shift
		where
			week_id > ?
			and category_id = ?`
	// 2. delete all the entries whose week id is greater than next_round + total_week
	GetDB().Exec(DELETE_BEYOND_NEXT_ROUND, nextRoundStartWeek+float32(totalWeek)-1, categoryId)

	// 3. get the manual map.
	manualMap, err := getManualMap(categoryId, nextRoundStartWeek)
	if err != nil {
		log.Error("msg", "get manual map", "error info", err)
	}

	// 4. fill the next round week by week
	// generate new ones.
	res := make(map[float32][]*OneInShift)
	for i := int(nextRoundStartWeek); i < int(nextRoundStartWeek)+totalWeek; i++ {
		res[float32(i)] = make([]*OneInShift, 0)
	}

	// append the manual map to the result.
	for k, v := range manualMap {
		for _, v2 := range v {
			res[k] = append(res[k], &OneInShift{v2.EngineerId, v2.StartWeek, 1})
		}
	}

	// the length of auto list should be the same as total weeks
	autoList := make([][]int, 0)
	for i := 0; i < len(shift.Robin); i++ {
		for j := 0; j < categoryInf.Period; j++ {
			autoList = append(autoList, shift.Robin[i])
		}
	}
	// fill the result by auto map
	for i := 0; i < totalWeek; i++ {
		manualLen := len(res[nextRoundStartWeek+float32(i)])
		for j := 0; j < categoryInf.EngineerNum-manualLen; j++ {
			if len(autoList[i]) >= j+1 {
				res[float32(i)+nextRoundStartWeek] = append(res[float32(i)+nextRoundStartWeek], &OneInShift{EngineerId: autoList[i][j], StartWeek: float32(i) + nextRoundStartWeek, Manual: 0})
			}
		}
	}

	const DELETE_NEXT_ROUND = `
		delete from
			shift
		where
			category_id = ?
			and week_id >= ?`
	GetDB().Exec(DELETE_NEXT_ROUND, categoryId, nextRoundStartWeek)

	const INSERT_SHIFT_LIST = `
		insert into
			shift (
				category_id,
				engineer_id,
				week_id,
				manual_flag,
				start_week
			)
		values
			(?, ?, ?, ?, ?)`
	for k, v := range res {
		for _, e_id := range v {
			GetDB().Exec(INSERT_SHIFT_LIST, categoryId, e_id.EngineerId, k, e_id.Manual, e_id.StartWeek)
		}
	}

	return nil
}
