package service

import (
	butil "enterprise/base/util"
	"enterprise/common/dao"
	"enterprise/common/model"
	"enterprise/common/registry"
	"github.com/robertkrimen/otto"
	log "github.com/sirupsen/logrus"
	"github.com/smbrave/goutil"
	"github.com/spf13/cast"
	"math"
	"time"
)

type StaffSalary struct {
	user *model.StaffUser
}

func NewStaffSalary(user *model.StaffUser) *StaffSalary {
	return &StaffSalary{
		user: user,
	}
}

func (s *StaffSalary) CalcSalary(salary *model.StaffSalary, month string) (*model.StaffSalary, error) {
	corp, err := dao.NewCorpDao().Get(s.user.CorpId)
	if err != nil {
		return nil, err
	}
	userSalary := s.user.GetSalary()
	userConfig := s.user.GetConfig()
	corpConfig := corp.GetConfig()
	if userSalary.Base == "" && userSalary.Target == "" {
		return nil, nil
	}
	if salary == nil {
		salary = new(model.StaffSalary)
		salary.Month = month
		salary.CorpId = s.user.CorpId
		salary.UserId = s.user.Id
		salary.Username = s.user.Username
	}

	entryTime, _ := time.ParseInLocation("2006-01-02", s.user.EntryDate, time.Local)
	leaveTime, _ := time.ParseInLocation("2006-01-02", s.user.LeaveDate, time.Local)
	isEntryMonth := goutil.If(cast.ToInt(entryTime.Format("200601")) == cast.ToInt(month), true, false)
	isLeaveMonth := goutil.If(cast.ToInt(leaveTime.Format("200601")) == cast.ToInt(month), true, false)

	//社保和公积金
	salary.SocialDeduct = 0
	salary.HouseDeduct = 0
	if isEntryMonth { //入职月不买社保
		salary.SocialDeduct = 0
		salary.HouseDeduct = 0
	}

	salary.ShouldDay = int(s.getTotalWorkDay(s.user.CorpId, s.user.Username, month))
	// 入职月或离职月以大多数人的出勤天数为准
	if isEntryMonth || isLeaveMonth {
		salary.ShouldDay = int(s.getTotalWorkDayMax(s.user.CorpId, month))
	}

	holiday, surplusHoliday := s.getRealVacationDay(month)
	realWorkDays := s.getRealWorkDay(month)
	//approvalCheckinDay := s.getApprovalCheckinDay(s.user.CorpId, s.user.Username, month)

	salary.HolidayDay = holiday
	salary.AttendDay = realWorkDays + surplusHoliday
	salary.Salary = cast.ToFloat64(userSalary.Base) + cast.ToFloat64(userSalary.Target)
	salary.SocialDeduct = goutil.If(userConfig.SocialDeduct != "", cast.ToFloat64(userConfig.SocialDeduct), cast.ToFloat64(corpConfig.SocialDeduct))
	salary.HouseDeduct = goutil.If(userConfig.HouseDeduct != "", cast.ToFloat64(userConfig.HouseDeduct), cast.ToFloat64(corpConfig.HouseDeduct))

	//计算工资

	s.formatFloat(salary)
	return salary, nil
}

func (s *StaffSalary) calculate(corp *model.Corp, salary *model.StaffSalary) {

	//获取基础数据
	data := s.getCalcData(salary)

	//获取业务数据
	dataFactory := registry.NewSalaryCalculator(corp, s.user)
	if dataFactory != nil {
		data["biz"] = dataFactory.Calculate(salary)
	}
	salary.Extra = goutil.EncodeJSONIndent(data)

	//获取计算器表达式
	calculator, _ := dao.NewSalaryCalculatorDao().Get(cast.ToInt64(s.user.GetSalary().Calculator))
	if calculator == nil {
		log.Errorf("calculator[%d] is nil", s.user.GetSalary().Calculator)
		return
	}

	//执行表达式
	jsrun := otto.New()
	jsrun.Set("data", data)
	jsrun.Run(calculator.Expression)

	//出勤工资
	attendSalary, err := jsrun.Get("attend_salary")
	if err != nil {
		log.Errorf("attend_salary error :%s", err.Error())
	} else {
		salary.AttendSalary, err = attendSalary.ToFloat()
		if err != nil {
			log.Errorf("attendSalary foloat is error:%s", err.Error())
		}
	}

	//绩效工资
	targetSalary, err := jsrun.Get("target_salary")
	if err != nil {
		log.Errorf("attend_salary error :%s", err.Error())
	} else {
		salary.TargetSalary, err = targetSalary.ToFloat()
		if err != nil {
			log.Errorf("targetSalary foloat is error:%s", err.Error())
		}
	}

	//奖金
	awardSalary, err := jsrun.Get("award_salary")
	if err != nil {
		log.Errorf("attend_salary error :%s", err.Error())
	} else {
		salary.AwardSalary, err = awardSalary.ToFloat()
		if err != nil {
			log.Errorf("awardSalary foloat is error:%s", err.Error())
		}
	}
}

func (s *StaffSalary) getCalcData(mSalary *model.StaffSalary) map[string]interface{} {

	userSalary := s.user.GetSalary()
	userConfig := s.user.GetConfig()
	data := make(map[string]interface{})

	user := make(map[string]interface{})
	salary := make(map[string]interface{})

	user["username"] = s.user.Username        //账户名称
	user["status"] = s.user.Status            //状态
	user["entryDate"] = s.user.EntryDate      //入职时间
	user["officalDate"] = s.user.OfficialDate //转正时间
	user["leaveDate"] = s.user.LeaveDate      //离职时间
	user["baseSalary"] = userSalary.Base      //基本工资
	user["targetSalary"] = userSalary.Target  //绩效工资
	user["target"] = userConfig.PerftTarget   //绩效目标
	salary["month"] = mSalary.Month           //工资月份
	salary["shouldDay"] = mSalary.ShouldDay   //应出勤天数
	salary["attendDay"] = mSalary.AttendDay   //实际出勤天数
	salary["holidayDay"] = mSalary.HolidayDay //休假天数

	data["user"] = user
	data["salary"] = salary

	return data
}

func (s *StaffSalary) formatFloat(salary *model.StaffSalary) {
	salary.AwardSalary = butil.FloatCut(salary.AwardSalary)
	salary.AttendSalary = butil.FloatCut(salary.AttendSalary)
	salary.TargetSalary = butil.FloatCut(salary.TargetSalary)
	salary.OtherSalary = butil.FloatCut(salary.OtherSalary)
	salary.AttendDay = butil.FloatCut(salary.AttendDay)
	salary.HolidayDay = butil.FloatCut(salary.HolidayDay)
}

func (s *StaffSalary) getRealVacationDay(month string) (float64, float64) {
	// 休假申请
	approveVacations, err := dao.NewApprovalVacationDao().GetByUsername(s.user.CorpId, s.user.Username, month, "")
	if err != nil {
		log.Errorf("db error :%s", err.Error())
		return 0, 0
	}

	holiday := float64(0)
	surplusHoliday := float64(0)
	for _, vac := range approveVacations {
		startTime, _ := time.ParseInLocation("2006-01-02 15:04:05", vac.VacationStartTime, time.Local)
		endTime, _ := time.ParseInLocation("2006-01-02 15:04:05", vac.VacationEndTime, time.Local)

		//同一天请假时长大于8小时算一天
		if startTime.Format("2006-01-02") == endTime.Format("2006-01-02") && vac.VacationDuration > 1 {
			holiday += 1
		} else {
			holiday += vac.VacationDuration
			//不是整天数,把剩余的算上
			span := vac.VacationDuration - math.Floor(vac.VacationDuration)
			surplusHoliday += goutil.If(math.Abs(span) < 0.000001, 0, 1-span)
		}
	}
	return holiday, surplusHoliday
}

func (s *StaffSalary) getApprovalCheckinDay(corpId int64, username, month string) int {
	approvalDay := int(0)
	userCheckins, err := dao.NewCheckinDao().Query(corpId, username, month, false)
	if err != nil {
		log.Errorf("db error :%s", err.Error())
		return approvalDay
	}
	for _, checkin := range userCheckins {
		approvalCheckin, _ := dao.NewApprovalCheckinDao().GetByUsernameDay(corpId, username, checkin.Day)
		if approvalCheckin != nil {
			approvalDay += 1
		}

	}
	return approvalDay
}

func (s *StaffSalary) getRealWorkDay(month string) float64 {
	realWorkdays := float64(0)
	userCheckins, err := dao.NewCheckinDao().Query(s.user.CorpId, s.user.Username, month, false)
	if err != nil {
		log.Errorf("db error :%s", err.Error())
		return realWorkdays
	}
	approvalCheckinDay := 0

	//入职当天算工作日
	entryTime, _ := time.ParseInLocation("2006-01-02", s.user.EntryDate, time.Local)
	if entryTime.Format("200601") == month {
		realWorkdays += 1
	}

	//离职当天算工作日
	leaveTime, _ := time.ParseInLocation("2006-01-02", s.user.LeaveDate, time.Local)
	if leaveTime.Format("200601") == month {
		realWorkdays += 1
	}

	corp, _ := dao.NewCorpDao().Get(s.user.CorpId)
	for _, checkin := range userCheckins {
		//入职离职当天已经算过了
		if entryTime.Format("2006-01-02") == checkin.Day || leaveTime.Format("2006-01-02") == checkin.Day {
			continue
		}

		//有请假申请的不算出勤
		approvalVacation, _ := dao.NewApprovalVacationDao().GetByUsernameDay(s.user.CorpId, s.user.Username, checkin.Day)
		if approvalVacation != nil {
			continue
		}

		if checkin.Exception == "" {
			realWorkdays += 1
			continue
		}

		//有补卡申请就直接算出勤
		approvalCheckin, _ := dao.NewApprovalCheckinDao().GetByUsernameDay(s.user.CorpId, s.user.Username, checkin.Day)
		if approvalCheckin != nil {
			realWorkdays += 1
			approvalCheckinDay += 1
			continue
		}

		//其他按工作时长结算
		if checkin.EndTime > 0 && checkin.StartTime > 0 {
			duration := float64(checkin.EndTime-checkin.StartTime) / cast.ToFloat64(corp.GetConfig().WorkerHouer)
			realWorkdays += goutil.If(duration > 1, 1, duration)
		}
	}
	return realWorkdays
}

func (s *StaffSalary) getTotalWorkDay(corpId int64, username, month string) int64 {
	checkins, _ := dao.NewCheckinDao().Query(corpId, username, month, false)
	return int64(len(checkins))
}

func (s *StaffSalary) getTotalWorkDayMax(corpId int64, month string) int64 {
	// 最多人数的应出勤天数 为真正的出勤天数
	userCounts, err := dao.NewCheckinDao().CountUsername(corpId, month)
	if err != nil {
		log.Errorf("db error :%s", err.Error())
		return 0
	}
	mp := make(map[int64]int)
	totalDays := int64(0)
	for _, uc := range userCounts {
		mp[uc.Count] += 1
	}
	minCount := 0
	for k, v := range mp {
		if v > minCount {
			minCount = v
			totalDays = k
		}
	}
	return totalDays

}