package com.yeejoin.equipmanage.service.impl;

import com.alibaba.fastjson.JSON;
import com.alibaba.fastjson.JSONArray;
import com.alibaba.fastjson.JSONObject;
import com.baomidou.mybatisplus.core.conditions.query.QueryWrapper;
import com.baomidou.mybatisplus.extension.plugins.pagination.Page;
import com.baomidou.mybatisplus.extension.service.impl.ServiceImpl;
import com.yeejoin.equipmanage.common.entity.Car;
import com.yeejoin.equipmanage.common.entity.WlCarMileage;
import com.yeejoin.equipmanage.common.utils.CoordinateUtil;
import com.yeejoin.equipmanage.common.utils.DateUtils;
import com.yeejoin.equipmanage.common.utils.HttpUtil;
import com.yeejoin.equipmanage.controller.Coordinate;
import com.yeejoin.equipmanage.fegin.IotFeign;
import com.yeejoin.equipmanage.listener.CarIotNewListener;
import com.yeejoin.equipmanage.mapper.WlCarMileageMapper;
import com.yeejoin.equipmanage.service.ICarService;
import com.yeejoin.equipmanage.service.IWlCarMileageService;
import com.yeejoin.equipmanage.thread.ThreadCarMileageTreatment;
import org.apache.commons.lang3.ObjectUtils;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.beans.factory.annotation.Value;
import org.springframework.scheduling.annotation.Async;
import org.springframework.scheduling.annotation.EnableAsync;
import org.springframework.scheduling.annotation.EnableScheduling;
import org.springframework.scheduling.annotation.Scheduled;
import org.springframework.stereotype.Service;
import org.typroject.tyboot.component.emq.EmqKeeper;
import org.typroject.tyboot.core.restful.utils.ResponseModel;

import javax.annotation.Resource;
import java.io.BufferedReader;
import java.io.IOException;
import java.io.InputStreamReader;
import java.math.BigDecimal;
import java.net.HttpURLConnection;
import java.net.URL;
import java.security.KeyManagementException;
import java.security.KeyStoreException;
import java.security.NoSuchAlgorithmException;
import java.text.DateFormat;
import java.text.ParseException;
import java.text.SimpleDateFormat;
import java.time.LocalDateTime;
import java.util.*;
import java.util.stream.Collectors;

/**
 * 车量里程表 服务实现类
 *
 * @author duanwei
 * @date 2023-02-01
 */

@Service
@EnableScheduling
@EnableAsync
public class WlCarMileageServiceImpl extends ServiceImpl<WlCarMileageMapper, WlCarMileage>
		implements IWlCarMileageService {
	@Value("${car.max.speed:100}")
	private Integer CAR_MAX_SPEED;
	@Autowired
	IotFeign iotFeign;

	@Autowired
	ICarService iCarService;

	@Value("${mileage.parameter}")
	private Double mileageParameter;

	@Resource
	private EmqKeeper emqKeeper;

	private static final Logger log = LoggerFactory.getLogger(HttpUtil.class);

	private final String GUIDE_KEY = "813684495d9a3981dd2c7694916fe404";

	private final String GUIDE_URL = "https://restapi.amap.com/v4/grasproad/driving?";

	private final String GUIDE_ADDRESS_URL = "https://restapi.amap.com/v3/geocode/regeo?";

	@Override
	public Page<WlCarMileage> page(Page<WlCarMileage> page, WlCarMileage wlCarMileage) {
		return this.baseMapper.page(page, wlCarMileage, mileageParameter);
	}

	@Override
	public Double totalMileage(String iotCode) {
		return this.baseMapper.totalMileage(iotCode);
	}

	@Override
	public List<Coordinate> getCoordinateList(long id) {
		double speed = 0;
		WlCarMileage wlCarMileage = this.getById(id);
		String iotCode = wlCarMileage.getIotCode();
		String measurement = iotCode.substring(0,8);
		String deviceName = iotCode.replace(measurement, "");
		// 由于iot存在毫秒故结束时间要+1秒 iot+1秒有bug还是查不到 +2秒
		ResponseModel<List<Object>> result = iotFeign.getLiveData(measurement, deviceName, wlCarMileage.getStartTime(),
				new Date(wlCarMileage.getEndTime().getTime()));
		List<Object> list = result.getResult();
		List<Coordinate> coordinateList = new ArrayList<Coordinate>();
		if (list != null) {
			DateFormat format1 = new SimpleDateFormat("yyyy-MM-dd'T'HH:mm:ss.SSS'Z'");
			DateFormat format2 = new SimpleDateFormat("yyyy-MM-dd'T'HH:mm:ss'Z'");
			for (Object object : list) {
				JSONObject jsonObject = JSONObject.parseObject(JSONObject.toJSONString(object));
				if (jsonObject.get("FireCar_Longitude") != null && jsonObject.get("FireCar_Latitude") != null) {
					Coordinate coordinate = new Coordinate();
					List<Double> lnglat = new ArrayList<Double>();
					lnglat.add(jsonObject.getDoubleValue("FireCar_Longitude"));
					lnglat.add(jsonObject.getDoubleValue("FireCar_Latitude"));
					coordinate.setLnglat(lnglat);
					coordinate.setSpeed(jsonObject.getDoubleValue("FireCar_Speed"));
					speed = speed + jsonObject.getDoubleValue("FireCar_Speed");
					String time = jsonObject.getString("time");
					if (time.length() > 20) {
						try {
							coordinate.setTime(format1.parse(jsonObject.getString("time")).getTime());
						} catch (ParseException e) {
							e.printStackTrace();
						}
					} else {
						try {
							coordinate.setTime(format2.parse(jsonObject.getString("time")).getTime());
						} catch (ParseException e) {
							e.printStackTrace();
						}
					}
					double direction = jsonObject.getDoubleValue("direction");
					if (!ObjectUtils.isEmpty(direction)){
						coordinate.setDirection(jsonObject.getDoubleValue("direction"));
					}else {
						coordinate.setDirection(0);
					}
					coordinateList.add(coordinate);
				}
			}
		}
		// 倒序坐标变为正序
		Collections.reverse(coordinateList);

		// 坐标轨迹纠偏
		double avgSpeed = speed / coordinateList.size();
		double count = Double.valueOf(coordinateList.size()) / 500;
		int ceil = (int) Math.ceil(count);
		ArrayList<Coordinate> resultList = new ArrayList<>();
		for (int i = 1; i <= ceil; i++) {
			if (i == ceil) {
				List<Coordinate> coordinates = coordinateList.subList(500 * (i - 1), coordinateList.size());
				List<Coordinate> check = check(coordinates, avgSpeed);
				resultList.addAll(check);
			} else {
				List<Coordinate> coordinates = coordinateList.subList(500 * (i - 1), 500 + (i - 1) * 500);
				List<Coordinate> check = check(coordinates, avgSpeed);
				resultList.addAll(check);
			}

		}

		return resultList;
	}

	private List<Coordinate> check(List<Coordinate> list, double avgSpeed) {
		ArrayList<Coordinate> coordinates = new ArrayList<>();
		JSONArray objects = new JSONArray();
		int count = 0;
		for (Coordinate coordinate : list) {
			JSONObject jsonObject = new JSONObject();
			// 经度
			jsonObject.put("x", coordinate.getLnglat().get(0));
			// 纬度
			jsonObject.put("y", coordinate.getLnglat().get(1));
			// 角度
			jsonObject.put("ag", coordinate.getDirection());
			// 速度
			jsonObject.put("sp", coordinate.getSpeed());
			// 时间
			if (count == 0) {
				jsonObject.put("tm", coordinate.getTime() / 1000);
			} else {
				jsonObject.put("tm", 3 * count);
			}
			count += 1;
			objects.add(jsonObject);
		}
		String s = objects.toJSONString();
		StringBuilder api = new StringBuilder(GUIDE_URL);
		api.append("key=").append(GUIDE_KEY);
		String result = null;
		try {
			result = HttpUtil.post(api.toString(), s);
		} catch (IOException | NoSuchAlgorithmException | KeyStoreException | KeyManagementException e) {
			e.printStackTrace();
		}
		if (result != null) {
			Map<String, Object> jsonObject = (Map<String, Object>) JSONObject.parseObject(result);
			//判断是否坐标不满足高德地图纠偏需求
			if(jsonObject.containsKey("errcode")&&jsonObject.get("errcode").toString().equals("30001")){
				return list;
			}
			if (jsonObject.containsKey("data")) {
				JSONObject data3 = JSONObject.parseObject(jsonObject.get("data").toString());
				JSONArray points1 = JSONArray.parseArray(data3.get("points").toString());
				points1.forEach(item -> {
					JSONObject jsonObject1 = JSONObject.parseObject(item.toString());
					List<Double> doubles = new ArrayList<>();
					Coordinate coordinate = new Coordinate();
					doubles.add(Double.valueOf(jsonObject1.get("x").toString()));
					doubles.add(Double.valueOf(jsonObject1.get("y").toString()));
					coordinate.setLnglat(doubles);
					coordinate.setSpeed(avgSpeed);
					coordinates.add(coordinate);
				});
			}
		}

		return coordinates;
	}

	@Override
	public Map<String, Boolean> getCalender(long id, Date date) {
		List<String> daylist = new ArrayList<String>();
		Map<String, Boolean> map = new HashMap<>();
		Car car = iCarService.getById(id);
		if (car == null || car.getIotCode() == null || date == null) {
			return map;
		}
		Calendar c = Calendar.getInstance();
		// 获取上一个月
		c.setTime(date);
		c.add(Calendar.MONTH, -1);
		daylist.addAll(getDayByMonth(c.getTime()));

		// 获取当月
		c.setTime(date);
		daylist.addAll(getDayByMonth(c.getTime()));

		// 获取下一个月
		c.setTime(date);
		c.add(Calendar.MONTH, 1);
		daylist.addAll(getDayByMonth(c.getTime()));
//		List<Map<String, Object>> hasList = this.listMaps(new QueryWrapper<WlCarMileage>()
//				.select("COUNT(1) AS count,date").lambda().eq(WlCarMileage::getIotCode, car.getIotCode())
//				.between(WlCarMileage::getDate, daylist.get(0), daylist.get(daylist.size() - 1))
//				.isNotNull(WlCarMileage::getEndTime)
//				.ge(WlCarMileage::getTravel, 0.5)
//				.groupBy(WlCarMileage::getDate));
		Map<String, Object> hasMap = new HashMap<>();
		List<WlCarMileage> wlCarMileageList = this.list(new QueryWrapper<WlCarMileage>().eq("iot_code", car.getIotCode())
				.between("date", daylist.get(0), daylist.get(daylist.size() - 1))
				.isNotNull("end_time")
				.ge("travel", 0.5)
				.groupBy("date"));
		List<WlCarMileage> wlCarMileageListActiveData = wlCarMileageList.stream().filter(wlCarMileage -> (!ObjectUtils.isEmpty(wlCarMileage.getTakeTime()) && (!ObjectUtils.isEmpty(wlCarMileage.getTravel()) && (wlCarMileage.getTravel() / (wlCarMileage.getTakeTime() / 3600000)) < CAR_MAX_SPEED))).collect(Collectors.toList());
        List<String> activeDateList = wlCarMileageListActiveData.stream().map(wlCarMileage -> String.valueOf(DateUtils.convertDateToString(wlCarMileage.getDate(),DateUtils.DATE_PATTERN))).collect(Collectors.toList());
		for (String day : daylist) {
			boolean has = false;
			if (activeDateList.contains(day)) {
				has = true;
			}
			map.put(day, has);
		}
		return map;
	}

	public static List<String> getDayByMonth(Date date) {
		List<String> data = new ArrayList<>();
		try {
			Calendar c = Calendar.getInstance();
			c.setTime(date);
			// 获取当前的年份
			int year = c.get(Calendar.YEAR);
			// 获取当前的月份（需要加1才是现在的月份）
			int month = c.get(Calendar.MONTH) + 1;
			// 获取本月的总天数
			int dayCount = c.getActualMaximum(Calendar.DAY_OF_MONTH);
			// 定义时间格式
			SimpleDateFormat sdf = new SimpleDateFormat("yyyy-MM-dd");
			// 开始日期为当前年月拼接1号
			Date startDate = sdf.parse(year + "-" + month + "-01");
			// 结束日期为当前年月拼接该月最大天数
			Date endDate = sdf.parse(year + "-" + month + "-" + dayCount);
			// 设置calendar的开始日期
			c.setTime(startDate);
			// 当前时间小于等于设定的结束时间
			while (c.getTime().compareTo(endDate) <= 0) {
				String time = sdf.format(c.getTime());
				data.add(time);
				// 当前日期加1
				c.add(Calendar.DATE, 1);
			}
		} catch (ParseException e) {
			e.printStackTrace();
		}
		return data;
	}

	@Override
	@Scheduled(cron = "${mileage.segmentation.cron}")
	@Async
	public void mileageSegmentation() {
		log.info("轨迹切分定时任务开始执行时间.............{}", LocalDateTime.now());
		Calendar cal = Calendar.getInstance();
		cal.setTime(new Date());
		cal.add(Calendar.DATE, -1);
		String nowDate = new SimpleDateFormat("yyyy-MM-dd").format(cal.getTime());
		log.info("轨迹切分定时任务数据过滤时间.............{}", nowDate);
		List<WlCarMileage> list = this.baseMapper.list(nowDate);
		log.info("需要切分数据, {}", list);
		//销毁所有线程和所有上次坐标
		//销毁所有线程
		log.info("销毁所有有效线程开始");
		CarIotNewListener.deviceInfo.keySet().forEach(s->{
			try {
				CarIotNewListener.deviceInfo.get(s).interrupt();
			}catch (Exception e){
				log.info("销毁有效线程失败"+e.getMessage());
			}
		});
		CarIotNewListener.deviceInfo.clear();
		log.info("销毁所有有效线程成功");
		//销毁所有的坐标信息
		log.info("销毁所有坐标信息开始");
		CarIotNewListener.deviceLastInfo.clear();
		log.info("销毁所有坐标信息成功");
		log.info("------------------开始切分里程-------------------------------");
		list.forEach(item -> {
			Calendar calendar = Calendar.getInstance();
			calendar.setTime(item.getDate());
			calendar.add(Calendar.DATE, 1);
			Date date = calendar.getTime();
			//iotcode有效
			if (!ObjectUtils.isEmpty(item.getIotCode()) && item.getIotCode().length() > 8) {
				// 查询车辆上报信息
				ResponseModel<List<Object>> result = iotFeign.getLiveData(item.getIotCode().substring(0, 8), item.getIotCode().substring(8),
						item.getStartTime(), new Date(new Date().getTime() + 2000));
				List<Object> list1 = result.getResult();
				// 获取最后一个有坐标的数据
				JSONObject lastObj = null;
				// 过滤空坐标
				List<Object> filterList = new ArrayList<Object>();
				for (int i = 0; i < list1.size(); i++) {
					JSONObject Obj = JSONObject.parseObject(JSONObject.toJSONString(list1.get(i)));
					if (Obj.get("FireCar_Longitude") != null && Obj.get("FireCar_Latitude") != null
							&& Obj.getDoubleValue("FireCar_Longitude") != 0
							&& Obj.getDoubleValue("FireCar_Latitude") != 0) {
						filterList.add(list1.get(i));
						// 获取第一个不为空的坐标
						if (lastObj == null) {
							lastObj = Obj;
						}
					}
				}
				if (lastObj == null) {
					lastObj = new JSONObject();
					lastObj.put("FireCar_Longitude", 0.0);
					lastObj.put("FireCar_Latitude", 0.0);
					lastObj.put("time", 0);
					lastObj.put("FireCar_Speed", 0);
				}
				// 当前坐标
				double startLongitude = lastObj.getDoubleValue("FireCar_Longitude");
				double startLatitude = lastObj.getDoubleValue("FireCar_Latitude");
				// 当前速度
				Double v = Double.parseDouble(String.valueOf(lastObj.get("FireCar_Speed")));
				item.setStartSpeed(v.intValue());
				double travel = 0.0;
				// 获取里程
				for (int i = 0; i < filterList.size() - 1; i++) {
					JSONObject start = JSONObject.parseObject(JSONObject.toJSONString(filterList.get(i)));
					JSONObject end = JSONObject.parseObject(JSONObject.toJSONString(filterList.get(i + 1)));
					travel += CoordinateUtil.distance(start.getDoubleValue("FireCar_Latitude"),
							start.getDoubleValue("FireCar_Longitude"), end.getDoubleValue("FireCar_Latitude"),
							end.getDoubleValue("FireCar_Longitude"));
				}
				// 查询车辆最新位置
				String address = getAddress(startLongitude, startLatitude);
				// 里程耗时
				long takeTime = (date.getTime()) - (item.getStartTime().getTime());

				// 修改0点未结束里程记录
				item.setEndSpeed(v.intValue());
				item.setEndTime(date);
				item.setEndLatitude(startLatitude);
				item.setEndLongitude(startLongitude);
				item.setEndName(address);
				item.setTravel(new BigDecimal(travel / 1000).setScale(1, BigDecimal.ROUND_HALF_UP).doubleValue());
				item.setTakeTime(takeTime);
				this.getBaseMapper().updateById(item);
				log.info("-----------结束里程成功：：："+JSONObject.toJSONString(item)+"-----------------");
//				// 从0点开启新里程
//				item.setStartName(address);
//				item.setDate(date);
//				item.setId(null);
//				item.setEndSpeed(null);
//				item.setEndTime(null);
//				item.setEndLatitude(null);
//				item.setEndLongitude(null);
//				item.setEndName(null);
//				item.setTravel(null);
//				item.setTakeTime(null);
//				item.setStartSpeed(v.intValue());
//				item.setStartTime(item.getDate());
//				item.setStartLongitude(startLongitude);
//				item.setStartLatitude(startLatitude);
//				this.baseMapper.insert(item);
//				HashMap<String,String> messageMap = new HashMap<>();
//				messageMap.put("FireCar_Latitude", String.valueOf(startLatitude));
//				messageMap.put("FireCar_Longitude", String.valueOf(startLongitude));
//                messageMap.put("FireCar_Speed",String.valueOf(v.intValue()));
//                messageMap.put("time",String.valueOf(item.getDate().getTime()));
//                messageMap.put("name","轨迹切分消息！！！");
//				log.info("-----------新增开始里程成功：：："+JSONObject.toJSONString(item)+"-----------------");
//				try {
//					emqKeeper.getMqttClient().publish(item.getIotCode().substring(0, 8)+"/"+item.getIotCode().substring(8)+"/property",JSON.toJSON(messageMap).toString().getBytes("UTF-8"),1,false);
//				} catch (Exception e) {
//				}
				//根据iotcode获取车辆并且同步经纬度到车辆
				Car car =  iCarService.getOne(new QueryWrapper<Car>().eq("iot_code", item.getIotCode()));
				car.setLongitude(startLongitude);
				car.setLatitude(startLatitude);
				iCarService.saveOrUpdate(car);
				log.info("-----------更新车辆坐标成功：：："+JSONObject.toJSONString(item)+"-----------------");
			}
		});
		log.info("轨迹切分任务执行完成..............");
//		log.info("-------------------对于切割完成的数据进行倒计时操作----------------------------------");
//		ThreadCarMileageTreatment threadCarMileageTreatment = new ThreadCarMileageTreatment(this,iCarService , iotFeign);
//		log.info("-------------------对于切割完成的数据进行倒计时开始----------------------------------");
//		threadCarMileageTreatment.start();
//		log.info("-------------------对于切割完成的数据进行倒计时结束----------------------------------");
	}

	@Override
	public Boolean getUncompleteMileagByIotCode(String iotCode) {
		Integer integer = this.count(new QueryWrapper<WlCarMileage>().select("1").lambda().eq(WlCarMileage::getIotCode,iotCode).isNull(WlCarMileage::getEndTime));
		return integer <= 0 ;
	}

	private String getAddress(double longitude, double lantitude) {
		StringBuilder api = new StringBuilder(GUIDE_ADDRESS_URL);
		api.append("key=").append(GUIDE_KEY).append("&location=").append(longitude).append(",").append(lantitude)
				.append("&radius=1000").append("&batch=false").append("&extensions=base").append("&roadlevel=0")
				.append("&batch=false");
		StringBuilder res = new StringBuilder();
		BufferedReader in = null;
		try {
			System.out.println(api.toString());
			URL url = new URL(api.toString());
			HttpURLConnection conn = (HttpURLConnection) url.openConnection();
			conn.setDoOutput(true);
			conn.setRequestMethod("POST");
			in = new BufferedReader(new InputStreamReader(conn.getInputStream(), "UTF-8"));
			String line;
			while ((line = in.readLine()) != null) {
				res.append(line).append("\n");
			}
			JSONObject object = JSONObject.parseObject(res.toString());
			JSONObject regeocode = object.getJSONObject("regeocode");
			String address = regeocode.getString("formatted_address");
			if ("[]".equals(address)) {
				address = "无效坐标";
			}
			res = new StringBuilder(address);
		} catch (Exception e) {
			e.printStackTrace();
		} finally {
			try {
				if (in != null) {
					in.close();
				}
			} catch (IOException e) {
				e.printStackTrace();
			}
		}
		return res.toString();
	}
	@Override
	public Double getTotalTravelByIotCodeAndDate(String date, String iotCode) {
		return this.baseMapper.getTotalTravelByIotCodeAndDate(date,iotCode);
	}

	@Override
	public Double getTotalTravelByIotCodeAndDateProd(String startDate, String endDate, String iotCode) {
		return this.baseMapper.getTotalTravelByIotCodeAndDateProd(startDate,endDate,iotCode);
	}
}
