package cn.quantgroup.xyqb.service.auth.impl;

import cn.quantgroup.xyqb.Constants;
import cn.quantgroup.xyqb.exception.IdCardException;
import cn.quantgroup.xyqb.model.Gender;
import cn.quantgroup.xyqb.model.IdCardInfo;
import cn.quantgroup.xyqb.service.auth.IIdCardService;
import lombok.extern.slf4j.Slf4j;
import org.apache.commons.lang.StringUtils;
import org.springframework.stereotype.Service;

import javax.annotation.PostConstruct;
import java.text.ParseException;
import java.text.SimpleDateFormat;
import java.util.*;
import java.util.regex.Matcher;
import java.util.regex.Pattern;

/**
 * Created by Miraculous on 15/7/10.
 */
@Slf4j
@Service
public class IdCardServiceImpl implements IIdCardService {

    private String[] validCodes = {"1", "0", "x", "9", "8", "7", "6", "5", "4", "3", "2"};

    private Map<String, String> areaCodes;

    private int[] wi = {7, 9, 10, 5, 8, 4, 2, 1, 6, 3, 7, 9, 10, 5, 8, 4, 2};

    private Pattern datePattern = Pattern.compile("^((\\d{2}(([02468][048])|([13579][26]))[\\-\\/\\s]?((((0?[13578])|(1[02]))" +
            "[\\-/\\s]?((0?[1-9])|([1-2][0-9])|(3[01])))|(((0?[469])|(11))[\\-/\\s]?((0?[1-9])|([1-2][0-9])|(30)))|(0?2[\\-/\\s]?" +
            "((0?[1-9])|([1-2][0-9])))))|(\\d{2}(([02468][1235679])|([13579][01345789]))[\\-/\\s]?((((0?[13578])|(1[02]))[\\-/\\s]?" +
            "((0?[1-9])|([1-2][0-9])|(3[01])))|(((0?[469])|(11))[\\-/\\s]?((0?[1-9])|([1-2][0-9])|(30)))|(0?2[\\-/\\s]?((0?[1-9])|(1[0-9])|(2[0-8]))))))" +
            "(\\s(((0?[0-9])|([1-2][0-3]))\\:([0-5]?[0-9])((\\s)|(:([0-5]?[0-9])))))?$");

    @PostConstruct
    private void init() {
        areaCodes = new HashMap<>();
        areaCodes.put("11", "北京");
        areaCodes.put("12", "天津");
        areaCodes.put("13", "河北");
        areaCodes.put("14", "山西");
        areaCodes.put("15", "内蒙古");
        areaCodes.put("21", "辽宁");
        areaCodes.put("22", "吉林");
        areaCodes.put("23", "黑龙江");
        areaCodes.put("31", "上海");
        areaCodes.put("32", "江苏");
        areaCodes.put("33", "浙江");
        areaCodes.put("34", "安徽");
        areaCodes.put("35", "福建");
        areaCodes.put("36", "江西");
        areaCodes.put("37", "山东");
        areaCodes.put("41", "河南");
        areaCodes.put("42", "湖北");
        areaCodes.put("43", "湖南");
        areaCodes.put("44", "广东");
        areaCodes.put("45", "广西");
        areaCodes.put("46", "海南");
        areaCodes.put("50", "重庆");
        areaCodes.put("51", "四川");
        areaCodes.put("52", "贵州");
        areaCodes.put("53", "云南");
        areaCodes.put("54", "西藏");
        areaCodes.put("61", "陕西");
        areaCodes.put("62", "甘肃");
        areaCodes.put("63", "青海");
        areaCodes.put("64", "宁夏");
        areaCodes.put("65", "新疆");
        areaCodes.put("71", "台湾");
        areaCodes.put("81", "香港");
        areaCodes.put("82", "澳门");
        areaCodes.put("91", "国外");
    }

    @Override
    public boolean isIdCardValid(String idCardStr) throws ParseException {
        if (idCardStr == null) {
            return false;
        }
        String actualId;
        if (idCardStr.length() == Constants.ID_NO_STANDARD_LENGTH) {
            actualId = idCardStr.substring(0, Constants.ID_NO_CHECK_LENGTH);
        } else if (idCardStr.length() == Constants.ID_NO_OLD_LENGTH) {
            actualId = idCardStr.substring(0, 6) + "19" + idCardStr.substring(6, Constants.ID_NO_OLD_LENGTH);
        } else {
            return false;
        }
        if (!StringUtils.isNumeric(actualId)) {
            return false;
        }

        String yearStr = actualId.substring(6, 10);
        String monthStr = actualId.substring(10, 12);
        String dayStr = actualId.substring(12, 14);
        int year = Integer.parseInt(yearStr);
        int month = Integer.parseInt(monthStr);
        int day = Integer.parseInt(dayStr);
        String dateStr = yearStr + "-" + monthStr + "-" + dayStr;
        Matcher matcher = datePattern.matcher(dateStr);
        if (!matcher.matches()) {
            return false;
        }
        GregorianCalendar gc = new GregorianCalendar();
        Date date = new SimpleDateFormat("yyyy-MM-dd").parse(dateStr);
        if (gc.get(Calendar.YEAR) - year > Constants.AGE_MAX || gc.getTime().getTime() - date.getTime() < 0) {
            return false;
        }
        if (month < Constants.MONTH_NO_MIN || month > Constants.MONTH_NO_MAX) {
            return false;
        }
        if (day < Constants.DAY_NO_MIN || day > Constants.DAY_NO_MAX) {
            return false;
        }
        String areaCode = actualId.substring(0, Constants.ID_NO_AREA_CODE_LENGTH);
        if (!areaCodes.containsKey(areaCode)) {
            return false;
        }
        // 校验码
        int acurateCode = 0;
        for (int i = 0; i < Constants.ID_NO_CHECK_LENGTH; ++i) {
            acurateCode += ((actualId.charAt(i) - '0') * wi[i]);
        }
        actualId += validCodes[acurateCode % Constants.ID_NO_CHECK_MASK];
        return idCardStr.length() != Constants.ID_NO_STANDARD_LENGTH || actualId.equalsIgnoreCase(idCardStr);
    }

    @Override
    public IdCardInfo getIdCardInfo(String idCardStr){
        IdCardInfo cardInfo = new IdCardInfo();
        cardInfo.setIsValid(false);
        if (idCardStr == null) {
            return cardInfo;
        }
        String actualId;
        String lastChar;
        if (idCardStr.length() == Constants.ID_NO_STANDARD_LENGTH) {
            actualId = idCardStr.substring(0, Constants.ID_NO_CHECK_LENGTH);
            lastChar = idCardStr.substring(16, Constants.ID_NO_CHECK_LENGTH).toLowerCase();
        } else if (idCardStr.length() == Constants.ID_NO_OLD_LENGTH) {
            actualId = idCardStr.substring(0, 6) + "19" + idCardStr.substring(6, Constants.ID_NO_OLD_LENGTH);
            lastChar = idCardStr.substring(13, 14).toLowerCase();
        } else {
            return cardInfo;
        }
        if (!StringUtils.isNumeric(actualId)) {
            return cardInfo;
        }
        String yearStr = actualId.substring(6, 10);
        String monthStr = actualId.substring(10, 12);
        String dayStr = actualId.substring(12, 14);
        int year = Integer.parseInt(yearStr);
        int month = Integer.parseInt(monthStr);
        int day = Integer.parseInt(dayStr);
        String dateStr = yearStr + "-" + monthStr + "-" + dayStr;
        Matcher matcher = datePattern.matcher(dateStr);
        if (!matcher.matches()) {
            return cardInfo;
        }
        GregorianCalendar gc = new GregorianCalendar();
        Date date;
        try {
            date = new SimpleDateFormat("yyyy-MM-dd").parse(dateStr);
        } catch (ParseException e) {
            //这里根本不会出错好么.万一哪个脑抽改了上面的, 就出错了
            log.error("解析日期异常...", e);
            return null;
        }

        if (gc.get(Calendar.YEAR) - year > Constants.AGE_MAX || gc.getTime().getTime() - date.getTime() < 0) {
            return cardInfo;
        }
        if (month < Constants.MONTH_NO_MIN || month > Constants.MONTH_NO_MAX) {
            return cardInfo;
        }
        if (day < Constants.DAY_NO_MIN || day > Constants.DAY_NO_MAX) {
            return cardInfo;
        }
        String areaCode = actualId.substring(0, Constants.ID_NO_AREA_CODE_LENGTH);
        if (!areaCodes.containsKey(areaCode)) {
            return cardInfo;
        }

        // 校验码
        int checkCode = 0;
        for (int i = 0; i < Constants.ID_NO_CHECK_LENGTH; ++i) {
            checkCode += ((actualId.charAt(i) - '0') * wi[i]);
        }
        actualId += validCodes[checkCode % Constants.ID_NO_CHECK_MASK];

        if (idCardStr.length() == Constants.ID_NO_STANDARD_LENGTH) {
            if (!actualId.equalsIgnoreCase(idCardStr)) {
                return cardInfo;
            }
        }

        cardInfo.setIsValid(true);
        // 判断男女
        if (Integer.parseInt(lastChar) % Constants.GENDER_MASK == 0) {
            cardInfo.setGender(Gender.FEMALE);
        } else {
            cardInfo.setGender(Gender.MALE);
        }
        cardInfo.setIdNo(idCardStr);
        cardInfo.setProvince(areaCodes.get(areaCode));
        cardInfo.setBirthDate(date);
        return cardInfo;
    }

    @Override
    public IdCardInfo getIdCardInfoWithExceptions(String idCardStr) throws ParseException {
        if (idCardStr == null) {
            throw new IdCardException("身份证号码不能为空");
        }
        String actualId;
        String lastValue;
        if (idCardStr.length() == Constants.ID_NO_STANDARD_LENGTH) {
            actualId = idCardStr.substring(0, Constants.ID_NO_CHECK_LENGTH);
            lastValue = idCardStr.substring(16, Constants.ID_NO_CHECK_LENGTH).toLowerCase();
        } else if (idCardStr.length() == Constants.ID_NO_OLD_LENGTH) {
            actualId = idCardStr.substring(0, 6) + "19" + idCardStr.substring(6, Constants.ID_NO_OLD_LENGTH);
            lastValue = idCardStr.substring(13, 14).toLowerCase();
        } else {
            throw new IdCardException("身份证号码必须为18位或15位");
        }
        if (!StringUtils.isNumeric(actualId)) {
            throw new IdCardException("身份证格式不正确");
        }
        String yearStr = actualId.substring(6, 10);
        String monthStr = actualId.substring(10, Constants.MONTH_NO_MAX);
        String dayStr = actualId.substring(12, 14);
        int year = Integer.parseInt(yearStr);
        int month = Integer.parseInt(monthStr);
        int day = Integer.parseInt(dayStr);
        String dateStr = yearStr + "-" + monthStr + "-" + dayStr;
        Matcher matcher = datePattern.matcher(dateStr);
        if (!matcher.matches()) {
            throw new IdCardException("身份证出生日期不正确");
        }
        GregorianCalendar gc = new GregorianCalendar();
        Date date = new SimpleDateFormat("yyyy-MM-dd").parse(dateStr);
        if (gc.get(Calendar.YEAR) - year > Constants.AGE_MAX || gc.getTime().getTime() - date.getTime() < 0) {
            throw new IdCardException("身份证出生年份不正确");
        }
        if (month < Constants.MONTH_NO_MIN || month > Constants.MONTH_NO_MAX) {
            throw new IdCardException("身份证出生月份不正确");
        }
        if (day < Constants.DAY_NO_MIN || day > Constants.DAY_NO_MAX) {
            throw new IdCardException("身份证出生日期不正确");
        }
        String areaCode = actualId.substring(0, Constants.ID_NO_AREA_CODE_LENGTH);
        if (!areaCodes.containsKey(areaCode)) {
            throw new IdCardException("身份证省份不正确");
        }

        // 校验码
        int checkCode = 0;
        for (int i = 0; i < Constants.ID_NO_CHECK_LENGTH; ++i) {
            checkCode += ((actualId.charAt(i) - '0') * wi[i]);
        }
        actualId += validCodes[checkCode % Constants.ID_NO_CHECK_MASK];

        if (idCardStr.length() == Constants.ID_NO_STANDARD_LENGTH) {
            if (!actualId.equalsIgnoreCase(idCardStr)) {
                throw new IdCardException("身份证校验不正确");
            }
        }

        IdCardInfo cardInfo = new IdCardInfo();
        cardInfo.setIsValid(true);
        // 判断男女
        if (Integer.parseInt(lastValue) % Constants.GENDER_MASK == 0) {
            cardInfo.setGender(Gender.FEMALE);
        } else {
            cardInfo.setGender(Gender.MALE);
        }
        cardInfo.setIdNo(idCardStr);
        cardInfo.setProvince(areaCodes.get(areaCode));
        cardInfo.setBirthDate(date);
        return cardInfo;
    }
}
