package com.iamberry.wechat.tools;

import java.io.BufferedReader;
import java.io.InputStream;
import java.io.InputStreamReader;
import java.io.OutputStream;
import java.net.ConnectException;
import java.net.URL;
import java.net.URLEncoder;
import java.util.Date;
import java.util.logging.Logger;

import javax.net.ssl.HttpsURLConnection;
import javax.net.ssl.SSLContext;
import javax.net.ssl.SSLSocketFactory;
import javax.net.ssl.TrustManager;

import net.sf.json.JSONObject;

import org.slf4j.LoggerFactory;

import com.iamberry.cache.LocalCache;
import com.iamberry.wechat.core.entity.wx.AccessToken;
import com.iamberry.wechat.core.entity.wx.ITTempLate;
import com.iamberry.wechat.core.entity.wx.Menu;
import com.iamberry.wechat.core.entity.wx.QRCJson;
import com.iamberry.wechat.core.entity.wx.Ticket;
import com.iamberry.wechat.core.entity.wx.Token;

/**
 * 公众平台通用接口工具类
 *
 * @author fzh
 *
 */
public class WeixinUtil {


	// 日志
	private  static org.slf4j.Logger logger = LoggerFactory.getLogger(WeixinUtil.class);

	// 发送短信模板的URL
	private static String sendTemplate = "https://api.weixin.qq.com/cgi-bin/message/template/send?access_token=";

	// token缓存
	private static LocalCache<String, Token> cache = new LocalCache<>(2);

	/**
	 * 网页授权获取access_token
	 *
	 * @param code
	 * @return
	 */
	public static JSONObject getParamByOauth2(String code) {
		JSONObject jsonObject = null;
		try {
			// 得到access_token和openid
			jsonObject = httpRequest(NameUtils.oauth2_getToken_url.replaceAll("CODE", code).replaceAll("APPID", NameUtils.getConfig("appId")).replaceAll("SECRET", NameUtils.getConfig("appSecret")), "GET", null);
			if (null != jsonObject) {
				return jsonObject;
			}
		} catch (Exception e) {
			// 获取token失败
			logger.error("获取token失败 errcode:{} errmsg:{}" + jsonObject.getInt("errcode") + jsonObject.getString("errmsg"));
		}
		return null;
	}

	/**
	 * 发起https请求并获取结果
	 *
	 * @param requestUrl
	 *            请求地址
	 * @param requestMethod
	 *            请求方式(GET/POST)
	 * @param outputStr
	 *            提交的数据
	 * @return JSONObject(通过JSONObject.get(key)的方式获取json对象的属性值)
	 *         java.lang.ClassCastException:
	 *         sun.net.www.protocol.http.HttpURLConnection cannot be cast to
	 *         javax.net.ssl.HttpsURLConnection
	 */
	public static JSONObject httpRequest(String requestUrl,
										 String requestMethod, String outputStr) {
		JSONObject jsonObject = null;
		StringBuffer buffer = new StringBuffer();
		try {
			// 创建SSLContext对象，并使用我们指定的信任管理器初始化
			TrustManager[] tm = { new MyX509TrustManager() };
			SSLContext sslContext = SSLContext.getInstance("SSL", "SunJSSE");
			sslContext.init(null, tm, new java.security.SecureRandom());
			// 从上述SSLContext对象中得到SSLSocketFactory对象
			SSLSocketFactory ssf = sslContext.getSocketFactory();

			URL url = new URL(requestUrl);
			HttpsURLConnection httpUrlConn = (HttpsURLConnection) url
					.openConnection();
			httpUrlConn.setSSLSocketFactory(ssf);

			httpUrlConn.setDoOutput(true);
			httpUrlConn.setDoInput(true);
			httpUrlConn.setUseCaches(false);

			// 设置请求方式(GET/POST)
			httpUrlConn.setRequestMethod(requestMethod);
			if ("GET".equalsIgnoreCase(requestMethod))
				httpUrlConn.connect();

			// 当有数据需要提交时
			if (null != outputStr) {
				OutputStream outputStream = httpUrlConn.getOutputStream();
				// 注意编码格式,防止中文乱码
				outputStream.write(outputStr.getBytes("UTF-8"));
				outputStream.close();
			}

			// 将返回的输入流转换成字符串
			InputStream inputStream = httpUrlConn.getInputStream();
			InputStreamReader inputStreamReader = new InputStreamReader(inputStream, "utf-8");
			BufferedReader bufferedReader = new BufferedReader(inputStreamReader);

			String str = null;
			while ((str = bufferedReader.readLine()) != null) {
				buffer.append(str);
			}
			bufferedReader.close();
			inputStreamReader.close();
			// 释放资源
			inputStream.close();
			inputStream = null;
			httpUrlConn.disconnect();
			jsonObject = JSONObject.fromObject(buffer.toString());
		} catch (ConnectException ce) {
			Logger.getAnonymousLogger().info("Weixin server connection timed out.");
		} catch (Exception e) {
			Logger.getAnonymousLogger().info("信任管理器请求时..." + e);
			Logger.getAnonymousLogger().info("jsonObject..." + jsonObject);
		}
		return jsonObject;
	}

	/**
	 * 获取access_token
	 *
	 * @param appid
	 *            凭证
	 * @param appsecret
	 *            密钥
	 * @return
	 */
	public static AccessToken getAccessToken(String appid, String appsecret) {

		// 根据public No查询access_token
		Token token = cache.get(NameUtils.getConfig("pubNo"));

		AccessToken accessToken = null;
		//判断数据库中是否存在token
		if (token != null) {
			//判断token是否失效
			Date date = new Date();
			if (date.getTime() < token.getEndDate().getTime()) {
				accessToken = new AccessToken();
				accessToken.setExpiresIn(7200);
				accessToken.setToken(token.getToken());
				return accessToken;
			} else {
				String requestUrl = NameUtils.access_token_url.replace("APPID", NameUtils.getConfig("appId")).replace("APPSECRET", NameUtils.getConfig("appSecret"));
				JSONObject jsonObject = httpRequest(requestUrl, "GET", null);
				// 如果请求成功
				if (null != jsonObject) {
					try {
						accessToken = new AccessToken();
						accessToken.setToken(jsonObject.getString("access_token"));
						accessToken.setExpiresIn(jsonObject.getInt("expires_in"));
						token.setToken(accessToken.getToken());
						token.setEndDate(new Date(date.getTime() + 6500000));
						token.setAppid(NameUtils.getConfig("pubNo"));
						try {
							cache.put(NameUtils.getConfig("pubNo"), token);
						} catch (Exception e) {
							logger.error(e.getMessage());
						}
						return accessToken;
					} catch (Exception e) {
						accessToken = null;
						// 获取token失败
						logger.error("获取token失败 errcode:{} errmsg:{}" + jsonObject.getInt("errcode") + jsonObject.getString("errmsg"));
					}
				}
			}
		} else {
			String requestUrl = NameUtils.access_token_url.replace("APPID", appid).replace("APPSECRET", appsecret);
			JSONObject jsonObject = httpRequest(requestUrl, "GET", null);
			// 如果请求成功
			if (null != jsonObject) {
				try {
					accessToken = new AccessToken();
					accessToken.setToken(jsonObject.getString("access_token"));
					accessToken.setExpiresIn(jsonObject.getInt("expires_in"));

					token = new Token();
					token.setToken(accessToken.getToken());
					token.setEndDate(new Date(new Date().getTime() + 6500000));
					token.setAppid(NameUtils.getConfig("pubNo"));

					try {
						cache.put(NameUtils.getConfig("pubNo"), token);
					} catch (Exception e) {
						// 更新失败
						logger.error(e.getMessage());
					}
					return accessToken;
				} catch (Exception e) {
					accessToken = null;
					// 获取token失败
					logger.error("获取token失败 errcode:{} errmsg:{}" + jsonObject.getInt("errcode") + jsonObject.getString("errmsg"));
				}
			}
		}
		return accessToken;
	}

	/**
	 * 获取jsapi_ticket（jsapi_ticket的有效期为7200秒）
	 *
	 * @param token
	 * @return
	 */
	public static Ticket getTicket(String token) {
		Ticket ticket = null;
		String requestUrl = NameUtils.jsapi_ticket_url.replace("ACCESS_TOKEN", token);
		JSONObject jsonObject = httpRequest(requestUrl, "GET", null);
		if (null == jsonObject) {
			// 网络异常
			return null;
		}

		try {
			ticket = new Ticket();
			ticket.setTicket(jsonObject.getString("ticket"));
			ticket.setExpiresIn(jsonObject.getInt("expires_in"));
		} catch (Exception e) {
			// 获取token失败
			logger.error("获取ticket失败 errcode:{} errmsg:{}" + jsonObject.getInt("errcode") + jsonObject.getString("errmsg"));

			// 判断错误信息是否为 access_token 失效的问题，只有access失效，我们才重新获取，否则每次获取可能导致access_token资源耗尽
			if (jsonObject.getInt("errcode") != 40001)
				return null;

			// 强制刷新access_token，存在多线程安全隐患!
			requestUrl = NameUtils.access_token_url.replace("APPID", NameUtils.getConfig("appId")).replace("APPSECRET", NameUtils.getConfig("appSecret"));
			JSONObject accessTokenJsonObject = httpRequest(requestUrl, "GET", null);

			// 报错access_token信息，不管报错与否
			Token wechatToken = new Token();
			wechatToken.setEndDate(new Date(new Date().getTime() + 6500000));
			wechatToken.setAppid(NameUtils.getConfig("pubNo"));
			wechatToken.setToken(accessTokenJsonObject.getString("access_token"));
			cache.put(NameUtils.getConfig("pubNo"), wechatToken);

			// 重新获取一次
			requestUrl = NameUtils.jsapi_ticket_url.replace("ACCESS_TOKEN", wechatToken.getToken());
			jsonObject = httpRequest(requestUrl, "GET", null);
			ticket = new Ticket();
			ticket.setTicket(jsonObject.getString("ticket"));
			ticket.setExpiresIn(jsonObject.getInt("expires_in"));
		}

		return ticket;
	}

	/**
	 * 创建菜单
	 *
	 * @param menu
	 *            菜单实例
	 * @param accessToken
	 *            有效的access_token
	 * @return 0表示成功，其他值表示失败
	 */
	public static int createMenu(Menu menu, String accessToken) {
		int result = 0;
		// 拼装创建菜单的url
		String url = NameUtils.menu_create_url.replace("ACCESS_TOKEN", accessToken);
		// 将菜单对象转换成json字符串
		String jsonMenu = JSONObject.fromObject(menu).toString();
		// 调用接口创建菜单
		JSONObject jsonObject = httpRequest(url, "POST", jsonMenu);
		if (null != jsonObject) {
			if (0 != jsonObject.getInt("errcode")) {
				result = jsonObject.getInt("errcode");
				Logger.getAnonymousLogger().info("创建菜单失败 errcode:{} errmsg:{}" + jsonObject.getInt("errcode") + jsonObject.getString("errmsg"));
			}
		}
		return result;
	}

	/**
	 * 创建菜单
	 *
	 * @param jsonMenu
	 * @param accessToken
	 */
	public static int createMenu(String jsonMenu, String accessToken) {
		int result = 0;
		// 拼装创建菜单的url
		String url = NameUtils.menu_create_url.replace("ACCESS_TOKEN", accessToken);
		// 调用接口创建菜单
		JSONObject jsonObject = httpRequest(url, "POST", jsonMenu);
		if (null != jsonObject) {
			if (0 != jsonObject.getInt("errcode")) {
				result = jsonObject.getInt("errcode");
				Logger.getAnonymousLogger().info("创建菜单失败 errcode:{} errmsg:{} " + jsonObject.getInt("errcode") + jsonObject.getString("errmsg"));
			}
		}
		return result;
	}

	/**
	 * 创建二维码
	 *
	 * @param json
	 * @return
	 */
	public static QRCJson createQrcode(String json) {
		QRCJson qrcJson = new QRCJson();
		try {
			AccessToken at = getAccessToken(NameUtils.getConfig("appId"), NameUtils.getConfig("appSecret"));
			if (at != null) {
				String token = at.getToken();
				JSONObject jsonObject = httpRequest(NameUtils.get_token_url.replaceAll("TOKEN", token), "POST", json);
				if (jsonObject != null) {
					String ticket = URLEncoder.encode(jsonObject.getString("ticket"), "UTF-8");
					qrcJson.setTicket(ticket);
					qrcJson.setUrl(NameUtils.show_qrcode_url + ticket);
					if (json.contains("expire_seconds")) {// 临时二维码才有过期时间
						qrcJson.setExpire_seconds(jsonObject.getString("expire_seconds"));
					}
				}
			}
		} catch (Exception e) {
			e.printStackTrace();
		}
		return qrcJson;
	}

	/**
	 * 生成网页授权链接
	 *
	 * @param type
	 * @param redirectURI
	 */
	public static String getOauth2Url(String type, String redirectURI) {
		String url = "";
		try {
			redirectURI = URLEncoder.encode(redirectURI, "utf-8");
			if ("snsapi_base".equals(type)) {
				url = NameUtils.oauth2_url.replace("SCOPE", "snsapi_base").replace("APPID", NameUtils.getConfig("appId")).replace("REDIRECT_URI", redirectURI);
			} else {
				url = NameUtils.oauth2_url.replace("SCOPE", "snsapi_userinfo").replace("APPID", NameUtils.getConfig("appId")).replace("REDIRECT_URI", redirectURI);
			}
			return url;
		} catch (Exception e) {
			e.printStackTrace();
		}
		return null;
	}

	/**
	 * （网页授权）拉取用户信息
	 *
	 * @param code
	 * @param type
	 * @return
	 */
	public static JSONObject getUserInfoByOauth2(String code, String type) {
		JSONObject jsonObject = null;
		String openid = "", access_token = "";
		try {
			// 得到access_token和openid
			jsonObject = httpRequest(NameUtils.oauth2_getToken_url.replaceAll("CODE", code).replaceAll("APPID", NameUtils.getConfig("appId")).replaceAll("SECRET", NameUtils.getConfig("appSecret")), "GET", null);
			if (null != jsonObject) {
				openid = jsonObject.getString("openid");
				access_token = jsonObject.getString("access_token");
				// 获取用户信息
				if ("info".equals(type) && !"".equals(openid) && !"".equals(access_token)) {
					jsonObject = null;
					jsonObject = httpRequest(NameUtils.oauth2_getUserInfo_url.replaceAll("ACCESS_TOKEN", access_token).replaceAll("OPENID", openid), "GET", null);
					if (jsonObject != null)
						return jsonObject;
				}
				return jsonObject;
			}
		} catch (Exception e) {
			access_token = null;
			// 获取token失败
			logger.error("获取token失败 errcode:{} errmsg:{}" + jsonObject.getInt("errcode") + jsonObject.getString("errmsg"));
		}
		return null;
	}

	/**
	 * 拉取用户信息
	 *
	 * @param code
	 * @param appid
	 * @param secret
	 */
	public  static String[] getOpenId(String code, String appid, String secret) {
		String[] strs = new String[2];
		String url = "", openid = "", access_token = "";
		Logger.getAnonymousLogger().info("code = " + code);
		JSONObject jsonObject = null;
		// 得到access_token和openid
		url = "https://api.weixin.qq.com/sns/oauth2/access_token?appid=APPID&secret=SECRET&code=CODE&grant_type=authorization_code";
		jsonObject = httpRequest(url.replaceAll("CODE", code).replaceAll("APPID", appid).replaceAll("SECRET", secret), "GET", null);
		if (null != jsonObject) {
			try {
				openid = jsonObject.getString("openid");
				access_token = jsonObject.getString("access_token");
				strs[0] = openid;
				strs[1] = access_token;
			} catch (Exception e) {
				access_token = null;
				// 获取token失败
				Logger.getAnonymousLogger().info("获取token失败 errcode:{} errmsg:{}" + jsonObject.getInt("errcode") + jsonObject.getString("errmsg"));
			}
		}
		return strs;
	}

	/**
	 * 根据openid拉取用户信息
	 *
	 * @param openid
	 * @return
	 */
	public  static JSONObject getUserInfo(String openid) {
		AccessToken token = getAccessToken(NameUtils.getConfig("appId"), NameUtils.getConfig("appSecret"));
		if (token == null) {
			// 此处如果出错，只能说明程序逻辑改变，存在多线程安全隐患
			return null;
		}

		// 获取信息
		JSONObject jsonObject = httpRequest(NameUtils.userinfo_url.replaceAll("ACCESS_TOKEN", token.getToken()).replaceAll("OPENID", openid), "GET", null);
		if (null == jsonObject) {
			// 网络错误
			return null;
		}

		// 对数据进行get（防止出现问题，能够让程序及时补救）, 如果抛出异常，表示access_token可能失效，强制获取一次。
		try {
			jsonObject.getString("sex");
		} catch (Exception e) {

			// 记录日志
			logger.error("getUserInfo method throw exception:" + e.getMessage() + "[" + jsonObject + "]");

			// 判断错误信息是否为 access_token 失效的问题，只有access失效，我们才重新获取，否则每次获取可能导致access_token资源耗尽
			if (jsonObject.getInt("errcode") != 40001)
				return jsonObject;

			// 强制刷新access_token，存在多线程安全隐患!
			String requestUrl = NameUtils.access_token_url.replace("APPID", NameUtils.getConfig("appId")).replace("APPSECRET", NameUtils.getConfig("appSecret"));
			JSONObject accessTokenJsonObject = httpRequest(requestUrl, "GET", null);

			// 报错access_token信息，不管报错与否
			Token wechatToken = new Token();
			wechatToken.setEndDate(new Date(new Date().getTime() + 6500000));
			wechatToken.setAppid(NameUtils.getConfig("pubNo"));
			wechatToken.setToken(accessTokenJsonObject.getString("access_token"));
			cache.put(NameUtils.getConfig("pubNo"), wechatToken);
			// 再次获取用户信息
			return httpRequest(NameUtils.userinfo_url.replaceAll("ACCESS_TOKEN", wechatToken.getToken()).replaceAll("OPENID", openid), "GET", null);
		}
		return jsonObject;
	}

	/**
	 * 发送模板消息 appId 公众账号的唯一标识 appSecret 公众账号的密钥 openId 用户标识
	 */
	public static void sendTemplateMessage(String appId, String appSecret,
										   String openId, String template_id, Object data, String redirectUrl) {

		// 准备数据
		AccessToken token = getAccessToken(appId, appSecret);
		String access_token = token.getToken();
		String url = sendTemplate  + access_token;

		// 准备模版
		ITTempLate temp = new ITTempLate();
		temp.setTouser(openId);
		temp.setTemplate_id(template_id);
		temp.setUrl(redirectUrl);
		temp.setData(JSONObject.fromObject(data));

		// format
		String jsonString = JSONObject.fromObject(temp).toString();

		// send
		JSONObject jsonObject = httpRequest(url, "POST", jsonString);
		if (null != jsonObject) {
			if (0 != jsonObject.getInt("errcode")) {
				logger.info("错误 errcode:{} errmsg:{}" + jsonObject.getInt("errcode") + jsonObject.getString("errmsg"));
			}
		}
	}
}