package main

import (
	"strconv"
	"time"

	"github.com/go-redis/redis/v7"
	"github.com/sirupsen/logrus"

	"lc/common/models"
	"lc/common/protocol"
	"lc/common/util"
)

// var LAMP_EVENT_ID string = "lamp_event_id"
// var LAMP_ALARM_ID string = "lamp_alarm_id"

type HlZigbeeLampController struct {
	tenant        string             //租户
	gid           string             //基础数据，网关ID
	did           string             //基础数据，集控器设备编码
	tid           uint16             //基础数据，集控器物模型ID
	lampDid       string             //灯编码
	errCnt        uint               //连续错误计数
	lampOn        uint8              //开灯状态,1开灯，0关灯，0xFF未知
	state         uint8              //实时数据，0在线,1离线
	lastStateTime time.Time          //实时数据，最新状态时间
	mapData       map[uint16]float64 //实时数据，最新数据
	lastDataTime  time.Time          //实时数据，最新数据时间
	nextHourTime  time.Time          //实时数据
	mapAlarm      map[string]int64   //告警开始时间->数据库记录ID
	// lampNumber    uint32             //灯编号,长和单灯末端编号,海蓝通信编码
}

func (o *HlZigbeeLampController) SetDID(did string, pdid string) {
	if o.lampDid == "" { //0xFF未知
		o.state = 0xFF
		o.lampOn = 0xFF
	}
	if o.mapAlarm == nil {
		o.mapAlarm = make(map[string]int64)
	}
	o.did = did      //集id
	o.lampDid = pdid //灯id

}

// HandleData 更新灯控数据
func (o *HlZigbeeLampController) HandleData(tenant, gid, pdid string, tid uint16, t time.Time, mapData *protocol.CHZB_LampData) {
	if o.lampDid == "" {
		logrus.Debugf("HlZigbeeLampController.HandleData:设备编码为空:lampDid=%s", pdid)
		return
	}
	//更新基础数据
	o.tenant = tenant
	o.gid = gid
	o.lampDid = pdid //灯
	o.tid = tid
	if o.nextHourTime.IsZero() { //下一个小时
		o.nextHourTime = protocol.ToBJTime(util.BeginningOfHour().Add(1 * time.Hour))
	}
	if o.mapData == nil {
		o.mapData = make(map[uint16]float64)
	}

	if len(mapData.Data) > 0 { //按什么顺序？
		if v, ok := mapData.Data[1]; ok {
			o.handleLampEvent(t, v)
		}
		for k, v := range mapData.Data {
			o.mapData[k] = v
		}

		//需要告警，则推入告警管理器
		//TODO:
		bv := BizValue{ID: pdid, Time: t, Tid: tid, Data: o.mapData} //sid,值
		GetBizAlarmMgr().PushData(&bv)

		o.lastDataTime = t
		cacheData(o.lampDid, t, mapData.Data)
		o.errCnt = 0
	} else {
		o.errCnt++
	}

	o.handleStateChange(t) //灯控在离线事件
	cacheState(o.lampDid, protocol.ToBJTime(t).Format("2006-01-02 15:04:05"), o.state)

	o.checkSaveData() //1 hour
}

// UpdateState 上层每隔一分钟调用,置状态为离线
func (o *HlZigbeeLampController) UpdateState() {
	if o.lampDid == "" {
		logrus.Debug("HlZigbeeLampController.handleStateChange:设备编码为空")
		return
	}
	if o.lastStateTime.IsZero() { //时间为空
		t0, s0, err := getState(o.lampDid)
		if err == nil {
			o.state = s0
			o.lastStateTime = t0
		} else {
			err := redisCltRawData.HGet(DeviceAlarmId, o.lampDid).Err()
			if err == redis.Nil {
				o.state = protocol.FAILED
				o.lastStateTime = util.MlNow()
				GetEventMgr().PushEvent(&EventObject{ID: o.lampDid, EventType: models.ET_OFFLINE, Time: o.lastStateTime})
			}
		}
	}

	if o.state == protocol.FAILED || //若之前是离线状态的，则不修改状态
		(!o.lastDataTime.IsZero() && util.MlNow().Sub(o.lastDataTime).Minutes() < OfflineInterval) {
		return
	}
	//如果之前一直是在线状态的，则置为离线；若之前是离线状态的，则不修改状态
	if o.state == protocol.SUCCESS {
		o.state = protocol.FAILED
		o.lastStateTime = util.MlNow()
		GetEventMgr().PushEvent(&EventObject{ID: o.lampDid, EventType: models.ET_OFFLINE, Time: o.lastStateTime})
		cacheState(o.lampDid, o.lastStateTime.Format("2006-01-02 15:04:05"), o.state)
		//检查是否要缓存数据
		o.checkSaveData() //1hour
	}
}

func (o *HlZigbeeLampController) SetOnOffLine(onoffline uint8) {
	if onoffline == protocol.FAILED { //使离线
		if o.state == protocol.FAILED || //若之前是离线状态的，则不修改状态
			(!o.lastDataTime.IsZero() && util.MlNow().Sub(o.lastDataTime).Minutes() < OfflineInterval) {
			return
		}
		//如果之前一直是在线状态的，则置为离线；若之前是离线状态的，则不修改状态
		if o.state == protocol.SUCCESS {
			o.state = protocol.FAILED
			o.lastStateTime = util.MlNow()
			GetEventMgr().PushEvent(&EventObject{ID: o.lampDid, EventType: models.ET_OFFLINE, Time: o.lastStateTime})
			cacheState(o.lampDid, o.lastStateTime.Format("2006-01-02 15:04:05"), o.state)
			//检查是否要缓存数据
			o.checkSaveData() //1hour
		}
	} else if onoffline == protocol.SUCCESS { //使在线
		//单灯收到不online
	}

}

func (o *HlZigbeeLampController) handleLampEvent(t time.Time, lampOn float64) {
	//开关灯状态
	if o.lampOn == 0xFF {
		if lampOn > 0 {
			o.lampOn = 1
		} else {
			o.lampOn = 0
		}
		return
	}
	if lampOn > 0 && o.lampOn == 0 { //熄灯状态到开灯状态
		GetEventMgr().PushEvent(&EventObject{ID: o.lampDid, EventType: models.ET_ONLAMP, Time: t, Value: lampOn})
	} else if lampOn == 0 && o.lampOn == 1 { //开灯状态到熄灯状态
		GetEventMgr().PushEvent(&EventObject{ID: o.lampDid, EventType: models.ET_OFFLAMP, Time: t, Value: lampOn})
	}
	//更新状态
	if lampOn > 0 {
		o.lampOn = 1
	} else {
		o.lampOn = 0
	}
}

//灯控状态改变事件
func (o *HlZigbeeLampController) handleStateChange(t time.Time) {
	if o.lampDid == "" {
		logrus.Debug("HlZigbeeLampController.handleStateChange:设备编码为空")
		return
	}
	//最新状态
	state := uint8(0)
	if o.errCnt >= 10 {
		state = 1
	} else if o.errCnt == 0 {
		state = 0
	} else {
		return
	}

	//状态处理
	if o.lastStateTime.IsZero() {
		t0, s0, err := getState(o.lampDid)
		if err != nil {
			o.state = state
			o.lastStateTime = t
			return
		}
		o.state = s0
		o.lastStateTime = t0
	}
	if o.state == 0 && state == 1 { //在线->离线
		GetEventMgr().PushEvent(&EventObject{ID: o.lampDid, EventType: models.ET_OFFLINE, Time: t})
	} else if o.state == 1 && state == 0 { //离线->在线
		GetEventMgr().PushEvent(&EventObject{ID: o.lampDid, EventType: models.ET_ONLINE, Time: t})
	}
	o.state = state
	o.lastStateTime = t
}

func HLConvertExcept(except uint16) string {
	//-1初始值 0正常，1异常亮灯，2异常熄灯，3亮度异常
	switch except {
	case protocol.LE_OK: //正常（正常开灯或熄灯状态）
		return "正常"
	case protocol.LE_ON: //亮灯异常（本该关灯状态）
		return "熄灯时段亮灯"
	case protocol.LE_OFF: //亮灯，但亮度异常（本该开灯状态，但开灯亮度不对）
		return "亮灯时段熄灯"
	case protocol.LE_ON_BRIGHTNESS:
		return "亮灯但亮度异常"
	default:
		return "状态未知"
	}
}

// HandleAlarm 告警开始,结束  终端->平台的报警
func (o *HlZigbeeLampController) HandleAlarm(alarm *protocol.LampAlarm) {
	if o.mapAlarm == nil {
		o.mapAlarm = make(map[string]int64)
	}
	key := o.lampDid + "@" + alarm.StartTime
	if alarm.EndTime != "" { //告警结束
		if t, err := util.MlParseTime(alarm.EndTime); err == nil {
			aid, ok := o.mapAlarm[key]
			if !ok {
				if str, err := redisCltRawData.HGet(DeviceAlarmId, key).Result(); err == nil {
					if id, err := strconv.Atoi(str); err == nil {
						aid = int64(id)
					}
				}
			}
			if aid > 0 {
				oo := models.DeviceAlarm{ID: aid, TEnd: t, EValue: float32(alarm.Brightness)}
				if err := oo.Update(); err != nil {
					logrus.Errorf("更新告警[%d]信息失败:%s", aid, err.Error())
				}
				delete(o.mapAlarm, key)
				if err := redisCltRawData.HDel(DeviceAlarmId, key).Err(); err != nil {
					logrus.Errorf("更新告警[%d]信息失败:%s", aid, err.Error())
				}
			}
		}
	} else { //告警开始
		t, err := util.MlParseTime(alarm.StartTime)
		if err != nil {
			return
		}
		oo := models.DeviceAlarm{
			DID:       alarm.DID,
			TStart:    t,
			Threshold: 0,
			SValue:    float32(alarm.AlarmBrightness),
			Content:   ConvertExcept(alarm.AlarmType),
			AlarmType: alarm.AlarmType,
			Level:     1,
		}
		if err := models.G_db.Create(&oo).Error; err != nil {
			logrus.Errorf("告警信息[%v]入库失败:%s", alarm, err.Error())
		} else {
			o.mapAlarm[key] = oo.ID
			if err := redisCltRawData.HSet(DeviceAlarmId, key, oo.ID).Err(); err != nil {
				logrus.Errorf("设备[%s]告警数据[%d]缓存失败:%s", o.lampDid, oo.ID, err.Error())
			}
		}
	}
}

//每小时入库一次设备数据，这里值不计顺序
func (o *HlZigbeeLampController) checkSaveData() {
	if o.nextHourTime.Before(o.lastDataTime) {
		//判断小时是否一样，不一样则以数据时间为准
		if !util.New(o.nextHourTime).BeginningOfHour().Equal(util.New(o.lastDataTime).BeginningOfHour()) {
			o.nextHourTime = util.New(o.lastDataTime).BeginningOfHour()
		}
		if len(o.mapData) > 0 {
			var datas []models.DeviceHourData
			for k, v := range o.mapData {
				o := models.DeviceHourData{ID: o.lampDid, Sid: k, Val: float32(v), Time: o.nextHourTime, CreatedAt: time.Now()}
				datas = append(datas, o)
			}
			if err := models.MultiInsertDeviceHourData(datas); err != nil {
				//TODO 是否考虑备份数据
				logrus.Errorf("设备[%s]小时[%s]数据插入数据库失败:%s", o.lampDid, o.nextHourTime.Format("2006-01-02 15:04:05"), err.Error())
			}
		}
		o.nextHourTime = o.nextHourTime.Add(time.Hour)
		o.mapData = make(map[uint16]float64)
	}
}
