package cn.hmsoft.weixin.request;

import java.io.ByteArrayOutputStream;
import java.io.DataInputStream;
import java.io.File;
import java.io.FileInputStream;
import java.io.InputStream;
import java.io.OutputStream;
import java.net.HttpURLConnection;
import java.net.URL;
import java.util.Map;

import cn.hmsoft.frame.exception.BusinessException;
import cn.hmsoft.frame.util.FrameAssertUtil;
import cn.hmsoft.helper.ClassHelper;
import cn.hmsoft.helper.JsonHelper;
import cn.hmsoft.helper.StringHelper;
import cn.hmsoft.log.LogHelper;
import cn.hmsoft.weixin.data.model.WxDefine;
import cn.hmsoft.weixin.entity.response.WxResponseJson;
import cn.hmsoft.weixin.util.WeiXinParamUtil;
import cn.hmsoft.weixin.util.WeiXinUtil;

/**********************************
 * @note 处理微信请求基类
 * @author revisit@126.com
 * @Company: www.hmsoft.cn
 * @create 2017-05-08 23:23:08
 * @version 4.0.0
 */
public abstract class AbstractRequest<T extends WxResponseJson, K, V> {

	/**********************
	 * 返回的结果转换成的json串
	 */
	protected T jsonEntity;
	/**********************
	 * 所有业务处理完成之后返回的实体类
	 */
	protected K returnEntity;
	/**********************
	 * 请求的实体类
	 */
	protected V requestEntity;
	protected String method = "get";
	/*************
	 * 请求次数
	 */
	protected int requestTotal = 1;

	/***************************
	 * 请求的地址
	 */
	protected abstract String makeUrl();

	/********************
	 * 完成请求后逻辑处理
	 */
	@SuppressWarnings("unchecked")
	public void processBusiness() {
		this.returnEntity = (K) this.jsonEntity;

	}

	protected HttpURLConnection http = null;

	/**********************
	 * 微信公众号
	 */
	protected WxDefine wxDefine;

	/************************
	 * 获取微信公众号
	 */
	public WxDefine getWxDefine() {
		return wxDefine;
	}

	/**********************
	 * 设置微信公众号
	 */
	public void setWxDefine(String app_id) {
		this.wxDefine = WeiXinUtil.getWxDefine(app_id);
	}

	/**********************
	 * 设置微信公众号
	 */
	public void setWxDefine(int wx_id) {
		this.wxDefine = WeiXinUtil.getWxDefine(wx_id);
	}

	/**********************
	 * 设置微信公众号
	 */
	public void setWxDefine(WxDefine wx) {
		this.wxDefine = wx;
	}

	@SuppressWarnings("unchecked")
	public K request() {
		FrameAssertUtil.isNotNull(wxDefine, "未设置微信公众号！");
		Class<?> clazz = ClassHelper.getGenericsClass(this.getClass());
		/************************
		 * 如果不是取toket的线程，如果没有tokent超时时间，说明无法启动
		 */
		if (!clazz.getName().endsWith("TokenResponseJson") && this.wxDefine.getApp_token_expired() == null)
			return null;
		// 获取实际应该去请求的url
		String ul = this.makeUrl();
		// 如果没有设置url，报错
		FrameAssertUtil.isNotEmpty(ul, "请求的类[" + this.getClass() + "]没有实现url路径，请检查");
		try {
			URL url = new URL(ul);
			http = (HttpURLConnection) url.openConnection();
			// get还是post方式
			http.setRequestMethod(this.method.equalsIgnoreCase("post") ? "POST" : "GET");
			// 连接超时时间
			http.setConnectTimeout(WeiXinParamUtil.HttpConnectTimeoutSeconds * 1000);
			// 读取数据超时时间
			http.setReadTimeout(WeiXinParamUtil.HttpConnectTimeoutSeconds * 1000);
			// 允许输出
			http.setDoOutput(true);
			// 允许传入
			http.setDoInput(true);
			http.setRequestProperty("Connection", "Keep-Alive");
			http.setRequestProperty("Charset", "UTF-8");
			// System.out.println(entity.getRequestObject().toString());
			// 连接
		} catch (Exception e) {
			throw new BusinessException("HTTP无法连接，请检查URL地址[" + ul + "]");
		}
		this.httpRequest();
		// 返回消息
		String message = receiveMessage();
		if (StringHelper.isNotEmpty(message)) {
			jsonEntity = (T) JsonHelper.toObject(message, clazz);
			// jsonEntity.setWeixin_json(message);
		}

		if (jsonEntity != null) {
			// token问题
			if (jsonEntity.getErrcode() == 40001 || jsonEntity.getErrcode() == 40014 || jsonEntity.getErrcode() == 40029
					|| jsonEntity.getErrcode() == 42001) {
				if (this.requestTotal < 5) {
					this.wxDefine.getTokenThread().refreshToken();
					this.requestTotal++;
					this.request();
				} else {
					//添加错误日志显示
					LogHelper.info("error_code:" + jsonEntity.getErrcode() +";err_message:" + jsonEntity.getErrmsg() 
					+ ";weixin_json:" + jsonEntity.getWeixin_json());
					throw new BusinessException("微信令牌失效，请过5分钟重试！");
				}
				// this.request();
			}
			if (jsonEntity.getErrcode() == 40163) {
				throw new BusinessException("系统繁忙，请稍后！");
			}

			if (jsonEntity.getErrcode() != 0) {
				// 40164
				if (this.getClass().getSimpleName().equals("TokenRequest") && jsonEntity.getErrcode() == 40164) {
					// 白名单失败
					this.processBusiness();
					return returnEntity;
				} else
					throw new BusinessException("[" + this.getClass().getName() + "]微信服务器业务请求失败，失败代码["
							+ jsonEntity.getErrcode() + "]，失败原因[" + WeiXinUtil.getErrorMsg(jsonEntity) + "]");
			}
			this.processBusiness();
		}
		return returnEntity;
	}

	/********************************
	 * http发出个简单的请求，发送json串
	 */
	protected void httpRequest() {
		try {
			http.connect();
			http.setConnectTimeout(15000);
			http.setReadTimeout(15000);
		} catch (Exception e) {
		}
		if (this.requestEntity != null) {
			try {
				OutputStream os = http.getOutputStream();
				os.write(transateRequestJsonCode().getBytes("UTF-8"));
				os.flush();
				os.close();
			} catch (Exception e) {
			}
		}

	}

	/**************************************
	 * 将请求的类转换成json串，同时保留特殊字符
	 */
	protected String transateRequestJsonCode() {
		LogHelper.info("最终请求json--- >" + JsonHelper.fromObjectWithChar(this.requestEntity));
		return JsonHelper.fromObjectWithChar(this.requestEntity);

	}

	/**************************
	 * 获取结果，下载一个json
	 */

	protected String receiveMessage() {
		String message = null;
		try {
			InputStream is = http.getInputStream();
			byte[] bcache = new byte[2048];
			int readSize = 0;// 每次读取的字节长度
			// int totalSize = 0;// 总字节长度
			ByteArrayOutputStream infoStream = new ByteArrayOutputStream();
			while ((readSize = is.read(bcache)) > 0) { // totalSize += readSize;
				infoStream.write(bcache, 0, readSize);
			}
			message = infoStream.toString("UTF-8");
			is.close();
		} catch (Exception e) {

		} finally {
			try {
				http.disconnect();
				http = null;
			} catch (Exception e) {

			}
		}
		return message;
	}

	protected String getDownloadFileName() {
		String file_name = http.getHeaderField("Content-disposition");
		if (StringHelper.isEmpty(file_name))
			return null;
		return file_name.substring(file_name.indexOf("filename") + 10, file_name.length() - 1);
	}

	/**************************
	 * 获取结果，下载一个文件
	 */

	protected int receiveAsFile(OutputStream out) {
		try {

			// file_name.substring(file_name.indexOf("filename")+10,file_name.length()-1);
			// Map<String, List<String>> map = http.getHeaderFields();
			// for (String key : map.keySet()) {
			// for (String v : map.get(key))
			// System.out.println(key + ":" + v);
			// }
			InputStream is = http.getInputStream();
			int size = is.available();
			byte[] b = new byte[1024];
			int len;
			while ((len = is.read(b)) > 0)
				out.write(b, 0, len);
			is.close();
			out.close();
			return size;
		} catch (Exception e) {
			e.printStackTrace();
			return 0;
		} finally {
			try {
				http.disconnect();
				http = null;
			} catch (Exception e) {

			}
		}
	}

	/**********************************************
	 * 通过http上传一个文件，map里为同时上传的参数
	 */
	protected void httpRequest(File file, Map<String, String> map) throws Exception {
		String BOUNDARY = "----------" + System.currentTimeMillis();

		http.setUseCaches(false);
		http.setRequestMethod("POST");
		this.http.setRequestProperty("Content-Type", "multipart/form-data; boundary=" + BOUNDARY);
		// 请求正文信息
		// 头
		String boundary = BOUNDARY;
		// 传输内容
		StringBuffer contentBody = new StringBuffer("--" + BOUNDARY);
		// 尾
		String endBoundary = "\r\n--" + boundary + "--\r\n";
		OutputStream out = http.getOutputStream();
		// 1. 处理文字形式的POST请求

		for (String key : map.keySet()) {
			contentBody.append("\r\n").append("Content-Disposition: form-data; name=\"").append(key + "\"")
					.append("\r\n").append("\r\n").append(map.get(key)).append("\r\n").append("--").append(boundary);
		}
		String boundaryMessage1 = contentBody.toString();
		out.write(boundaryMessage1.getBytes("utf-8"));

		this.httpSendFile(out, file, "media", BOUNDARY);

		// 3. 写结尾
		out.write(endBoundary.getBytes("utf-8"));

		out.flush();

		out.close();

	}

	/**********************************************
	 * 通过http上传一个文件
	 */
	private void httpSendFile(OutputStream out, File file, String field_name, String BOUNDARY) throws Exception {
		StringBuilder sb = new StringBuilder();
		sb.append("--"); // 必须多两道线
		sb.append(BOUNDARY);
		sb.append("\r\n");
		sb.append("Content-Disposition: form-data;name=\"media\";filelength=\"" + file.length() + "\";filename=\""
				+ file.getName() + "\"\r\n");
		sb.append("Content-Type:application/octet-stream\r\n\r\n");
		byte[] head = sb.toString().getBytes("utf-8");
		// 获得输出流
		// OutputStream out = new DataOutputStream(http.getOutputStream());
		// 输出表头
		out.write(head);
		// 开始真正向服务器写文件
		DataInputStream in = new DataInputStream(new FileInputStream(file));
		int bytes = 0;
		byte[] bufferOut = new byte[1024];
		while ((bytes = in.read(bufferOut)) != -1) {
			out.write(bufferOut, 0, bytes);
		}
		in.close();
		byte[] foot = ("\r\n--" + BOUNDARY + "--\r\n").getBytes("utf-8");// 定义最后数据分隔线
		out.write(foot);
		out.flush();
	}
}
