package db

import (
	"olympus/oncall/backend/model"
	"olympus/oncall/backend/pkg/log"
)

// Generate next round's schedule for a category.
// func GenerateNextRoundSchedule(categoryId int) error {
// 	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)

// 	// determine how many people per week
// 	numberPerWeek := categoryInf.RoleNum
// 	// determine how many weeks per person.
// 	period := categoryInf.Period
// 	// find all the available roles in this category
// 	availableOnes, err := GetAvailableRolesInCategory(categoryId)
// 	if err != nil {
// 		return err
// 	}
// 	//scheduleMap holds the number of weeks that one should have in the next round.
// 	scheduleMap := make(map[int]int)
// 	for i := 0; i < len(availableOnes); i++ {
// 		scheduleMap[availableOnes[i].EngineerId] = period
// 	}

// 	var weekTotal = len(availableOnes) * period / numberPerWeek
// 	slices := model.AvailableSlice(availableOnes)
// 	// first sort by contribution and names.
// 	sort.Sort(slices)

// 	// delete the original schedule for next round in the shift table.
// 	const DELETE_NEXT_ROUND_WITHOUT_MANUAL = `
// 		delete from
// 			shift
// 		where
// 			category_id = ?
// 			and week_id >=?
// 			and manual_flag = 0`
// 	GetDB().Exec(DELETE_NEXT_ROUND_WITHOUT_MANUAL, categoryId, nextRoundStartWeek)

// 	// get the manual map that are not deleted.
// 	manualMap, err := getManualMap(categoryId, nextRoundStartWeek)
// 	if err != nil {
// 		return err
// 	}

// 	// generate new ones.
// 	res := make(map[float32][]*OneInShift)
// 	for i := int(nextRoundStartWeek); i < int(nextRoundStartWeek)+weekTotal; 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})
// 		}
// 	}

// 	// then delete the manual map from database.
// 	const DELETE_NEXT_ROUND = `
// 		delete from
// 			shift
// 		where
// 			category_id = ?
// 			and week_id >= ?`
// 	GetDB().Exec(DELETE_NEXT_ROUND, categoryId, nextRoundStartWeek)

// 	previousOne, err := getLastOne(nextRoundStartWeek, categoryId)
// 	if err != nil {
// 		return err
// 	}
// 	// schedule week by week.
// WeekLoop:
// 	for i := int(nextRoundStartWeek); i < int(nextRoundStartWeek)+weekTotal; i++ {
// 		// exclude the manual ones.
// 		// autoNum is the number of roles to be automatically generated.
// 		autoNum := numberPerWeek - len(res[float32(i)])

// 		// newPreviousOne record the shift list of temp week, to be used as previousOne in next week.
// 		newPreviousOne := make([]*OneInShift, 0)
// 		// append the manual ones first.
// 		for _, v := range res[float32(i)] {
// 			newPreviousOne = append(newPreviousOne, &OneInShift{v.EngineerId, v.StartWeek, v.Manual})
// 		}
// 		// if manual ones have fullfil the number , continue.
// 		if autoNum == 0 {
// 			previousOne = make([]*OneInShift, 0)
// 			previousOne = append(previousOne, newPreviousOne...)
// 			continue WeekLoop
// 		}

// 		// first, find the previous week's people who should continue this week.
// 		for _, value := range previousOne {
// 			if int(value.StartWeek)+period-1 >= i && inTheCategory(value.EngineerId, availableOnes) && scheduleMap[value.EngineerId] > 0 {
// 				// not finish, add it.
// 				res[float32(i)] = append(res[float32(i)], &OneInShift{value.EngineerId, value.StartWeek, 0})
// 				newPreviousOne = append(newPreviousOne, &OneInShift{value.EngineerId, value.StartWeek, 0})
// 				scheduleMap[value.EngineerId] -= 1
// 				autoNum--
// 				log.Info("msg", "schedule one", "engineer", value.EngineerId, "week id", i)
// 				if autoNum == 0 {
// 					previousOne = make([]*OneInShift, 0)
// 					previousOne = append(previousOne, newPreviousOne...)
// 				}
// 			}
// 		}

// 		// find a experienced person if more than one people.
// 		if numberPerWeek > 1 && allNew(newPreviousOne, slices) && numberPerWeek-len(newPreviousOne) > 0 {
// 			for l := len(slices) - 1; l >= 0; l-- {
// 				if scheduleMap[slices[l].EngineerId] > 0 && !isContained(slices[l].EngineerId, newPreviousOne) {
// 					// the person ends last week. Then he cannot participate this week.
// 					if isContained(slices[l].EngineerId, previousOne) && int(findViaEngineerId(previousOne, slices[l].EngineerId).StartWeek)+period == i {
// 						continue
// 					}
// 					res[float32(i)] = append(res[float32(i)], &OneInShift{slices[l].EngineerId, float32(i), 0})
// 					newPreviousOne = append(newPreviousOne, &OneInShift{slices[l].EngineerId, float32(i), 0})
// 					scheduleMap[slices[l].EngineerId] -= 1
// 					autoNum--
// 					if autoNum == 0 {
// 						previousOne = make([]*OneInShift, 0)
// 						previousOne = append(previousOne, newPreviousOne...)
// 						continue WeekLoop
// 					} else {
// 						break
// 					}

// 				}
// 			}

// 		}

// 		length := len(newPreviousOne)
// 		// generate the rest padding.
// 		j := 0
// 		for j < numberPerWeek-length {
// 			for _, v := range slices {
// 				// have available points, NOT（距离结束不止period 并且 他points小于period）
// 				if scheduleMap[v.EngineerId] > 0 && !(((int(nextRoundStartWeek)+weekTotal-i >= period) && (scheduleMap[v.EngineerId] < period)) && allNotEnough(period, scheduleMap)) {
// 					if isContained(v.EngineerId, previousOne) && int(findViaEngineerId(previousOne, v.EngineerId).StartWeek)+period == i {
// 						continue
// 					}
// 					res[float32(i)] = append(res[float32(i)], &OneInShift{v.EngineerId, float32(i), 0})
// 					newPreviousOne = append(newPreviousOne, &OneInShift{v.EngineerId, float32(i), 0})
// 					scheduleMap[v.EngineerId] -= 1
// 					j++
// 					autoNum--
// 					if autoNum == 0 {
// 						previousOne = make([]*OneInShift, 0)
// 						previousOne = append(previousOne, newPreviousOne...)
// 						continue WeekLoop
// 					}
// 				}

// 			}

// 		}
// 	}

// 	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
// }

// get all the available roles in one category.
func GetAvailableRolesInCategory(categoryId int) ([]*model.AvailablePerson, error) {
	const GET_AVAILABLE_ONES_IN_CATEGORY = `
		select
			team_name,
			engineer_id,
			engineer_name,
			contribution,
			engineer_en_name,
			experience
		from
			teams as t
			left join engineers as e on t.team_id = e.team_id
		where
			category_id = ?
			and available = 1`

	availableList := make([]*model.AvailablePerson, 0)
	res := GetDB().Raw(GET_AVAILABLE_ONES_IN_CATEGORY, categoryId).Scan(&availableList)
	if res.Error != nil {
		log.Error("msg", "error: get availables", "category id", categoryId, "error info", res.Error)
	}

	return availableList, nil
}

// get the manual input information for the schedule.
func getManualMap(categoryId int, startWeek float32) (map[float32][]*OneInShift, error) {
	const GET_MANUAL_MAP = `
		select
			week_id,
			start_week,
			engineer_id,
			manual_flag
		from
			shift
		where
			category_id = ?
			and week_id >= ?
			and manual_flag = 1`
	manuals := make([]ManualMap, 0)
	resSql := GetDB().Raw(GET_MANUAL_MAP, categoryId, startWeek).Scan(&manuals)
	if resSql.Error != nil {
		log.Error("msg", "error: getting manual map", "category id", categoryId, "start week", startWeek, "error info", resSql.Error)
		return nil, resSql.Error
	}

	res := make(map[float32][]*OneInShift)
	for _, v := range manuals {
		if _, exist := res[v.WeekId]; exist {
			res[v.WeekId] = append(res[v.WeekId], &OneInShift{EngineerId: v.EngineerId, StartWeek: v.StartWeek, Manual: 1})
		} else {
			res[v.WeekId] = make([]*OneInShift, 0)
			res[v.WeekId] = append(res[v.WeekId], &OneInShift{EngineerId: v.EngineerId, StartWeek: v.StartWeek, Manual: 1})
		}
	}
	return res, nil

}

// // get the previous week's schedule to find if there is some one who should continue.
func getLastOne(nextStart float64, categoryId int) ([]*OneInShift, error) {
	previousWeekId := nextStart - 1
	const GET_PREVIOUS_SHIFT_ONE = `
		select
			engineer_id,
			start_week
		from
			shift
		where
			category_id = ?
			and week_id = ?`
	res := make([]*OneInShift, 0)
	resSql := GetDB().Raw(GET_PREVIOUS_SHIFT_ONE, categoryId, previousWeekId).Scan(&res)
	if resSql.Error != nil {
		log.Error("msg", "error: get the previous shift", "category id", categoryId, "week id", nextStart, "error info", resSql.Error)
		return nil, resSql.Error
	}
	return res, nil
}

// func inTheCategory(engineerId int, availables []*model.AvailablePerson) bool {
// 	for _, v := range availables {
// 		if v.EngineerId == engineerId {
// 			return true
// 		}
// 	}
// 	return false
// }

// // Input: l: a list of people.
// // Output: Determine if all the people in this list are newbie.
// func allNew(l []*OneInShift, slice model.AvailableSlice) bool {
// 	for _, v := range l {
// 		person := findPersonById(v.EngineerId, slice)
// 		if person == nil {
// 			continue
// 		}
// 		if person.Experience > 4 {
// 			return false
// 		}
// 	}
// 	return true
// }

// // Below are Help functions.
// func findPersonById(engineer_id int, availables model.AvailableSlice) *model.AvailablePerson {
// 	for _, v := range availables {
// 		if v.EngineerId == engineer_id {
// 			return v
// 		}
// 	}
// 	return nil
// }

// func isContained(engineer_id int, l []*OneInShift) bool {
// 	for _, v := range l {
// 		if v.EngineerId == engineer_id {
// 			return true
// 		}
// 	}
// 	return false
// }

func findViaEngineerId(l []*OneInShift, e_id int) *OneInShift {
	for _, v := range l {
		if v.EngineerId == e_id {
			return v
		}
	}
	return nil
}

// // all the people in the map do not have enough points to support next peroid.
// func allNotEnough(period int, schedule map[int]int) bool {
// 	for _, v := range schedule {
// 		if v >= period {
// 			return true
// 		}
// 	}
// 	return false
// }
