package service

import (
	"bytes"
	"database/sql"
	"encoding/json"
	"errors"
	"fmt"
	"io/ioutil"
	"log"
	"net/http"
	"strconv"
	"strings"
	"time"
	"xiaoniaokuaiyan.com/xiaoniao/config"
	"xiaoniaokuaiyan.com/xiaoniao/pay/wx"

	"gopkg.in/guregu/null.v3"

	"xiaoniaokuaiyan.com/xiaoniao/dal"
	"xiaoniaokuaiyan.com/xiaoniao/entity"
	"xiaoniaokuaiyan.com/xiaoniao/util"
)

type ActivityService struct {
	*WeixinService
}

func (srv *ActivityService) AddReporter(reporter *entity.ActReporter) (interface{}, error) {
	if !util.IsMobile(reporter.Mobile) {
		return nil, errors.New("wrong mobile phone number")
	}
	db := util.GetWriteSqlDB()
	reporter.CreatedAt = time.Now().Format("2006-01-02 15:04:05")
	strSql, mkv := util.GenerateInsertSqlFromStruct("t_activity_reporter", reporter)
	result, err := db.NamedExec(strSql, mkv)
	if err != nil {
		return nil, err
	}
	if ra, _ := result.RowsAffected(); ra <= 0 {
		return nil, errors.New("add failed")
	}
	return true, nil
}

func (srv *ActivityService) AddActInfo(item *entity.ActInfo) (interface{}, error) {

	db := util.GetWriteSqlDB()
	if strings.HasPrefix(item.Source, "-") {
		item.Source = item.Source[1:]
		strSql := "select count(*) from t_activity_info where mobile = ? and source = ?;"
		var tc int
		derr := db.Get(&tc, strSql, item.Mobile.String, item.Source)
		if derr != nil {
			return nil, derr
		}
		if tc > 0 {
			return nil, errors.New("1::already submit")
		}
	}
	var params = map[string]string{}
	if item.Extra.Valid && item.Extra.String != "" {
		err := json.Unmarshal([]byte(item.Extra.String), &params)
		if err != nil {
			return nil, err
		}
	}
	if item.Source == "medicine" || item.Source == "SendBack" || item.Source == "directbind" {
		smsSvc := &SMSService{
			ISMSCode: dal.DefaultSMSCodeDal,
		}
		_, err := smsSvc.ValidateCode(item.Mobile.String, params["vcode"], 8)
		if err != nil {
			return nil, errors.New("2::wrong validate code")
		}
		var bindTimes int
		var todayStr = time.Now().Format("2006-01-02")
		db.Get(&bindTimes, "SELECT count(distinct ex_code) from t_activity_info where mobile = ? and source =? and (created_at between ? and ?);", item.Mobile.String, item.Source, todayStr, todayStr+" 23:59:59")
		//20211127 从1 改成5
		if bindTimes >= 5 {
			return nil, errors.New("3::beyond binding times today")
		}
	}

	item.CreatedAt = time.Now().Format("2006-01-02 15:04:05")
	strSql, mkv := util.GenerateInsertSqlFromStruct("t_activity_info", item)
	result, err := db.NamedExec(strSql, mkv)
	if err != nil {
		return nil, err
	}
	if ra, _ := result.RowsAffected(); ra <= 0 {
		return nil, errors.New("add failed")
	}
	id, _ := result.LastInsertId()
	item.Id = int(id)
	//保存订单信息
	if item.Source == "medicine" || item.Source == "SendBack" {
		if !item.ExCode.Valid || item.ExCode.String == "" {
			//20220623 如果ExCode 是空,直接跳转结尾并且通知redis
			goto JUMP
		}
		var upItem = struct {
			CustomName    string `db:"custom_name"`
			Mobile        string `db:"mobile"`
			Age           int    `db:"age"`
			Birthday      string `db:"birthday"`
			Gender        string `db:"gender"`
			ExpressStatus string `db:"expressstatus"`
		}{}
		err = db.Get(&upItem, "select name as custom_name, gender, age, mobile,birthday, expressstatus from t_order t1 left join t_order_extra t2 on t1.id = t2.order_id where id = ?;", item.ExCode.String)
		if err != nil {
			//return nil, err
			//20220623 去除返回报错逻辑,直接跳转结尾并且通知redis
			goto JUMP
		}
		//20220520 修改生日逻辑
		var sqlResult sql.Result
		if _, ok := params["birthday"]; ok {
			btime, _ := time.Parse("2006-01-02", params["birthday"])
			age := time.Now().Year() - btime.Year()
			sqlResult, err = db.Exec("update t_order set name =?, gender =?, age =?, mobile =?, birthday =? where id = ?", item.CustomName.String, item.Gender.String, age, item.Mobile.String, params["birthday"], item.ExCode.String)
			if err != nil {

			} else if ra, _ := sqlResult.RowsAffected(); ra > 0 {
				go util.InsertMongo(item.ExCode.String, "回寄/活动", 1, map[string]interface{}{"name": item.CustomName.String, "gender": item.Gender.String, "age": age, "mobile": item.Mobile.String, "birthday": params["birthday"], "url": "/act/addinfo"})
			}
		} else {
			sqlResult, err = db.Exec("update t_order set name =?,  mobile =? where id = ?", item.CustomName.String, item.Mobile.String, item.ExCode.String)
			if err != nil {

			} else if ra, _ := sqlResult.RowsAffected(); ra > 0 {
				go util.InsertMongo(item.ExCode.String, "回寄/活动", 1, map[string]interface{}{"name": item.CustomName.String, "mobile": item.Mobile.String, "url": "/act/addinfo"})
			}
		}
		//20220704 熠保项目 修改 blood_code
		sqlResult, _ = db.Exec("update t_order set blood_codes =? where id = ? and (trim(blood_codes)='' or blood_codes is null)", item.Address.String, item.ExCode.String)
		if ra, _ := sqlResult.RowsAffected(); ra > 0 {
			go util.InsertMongo(item.ExCode.String, "回寄/活动", 1, map[string]interface{}{"blood_codes": item.Address.String, "url": "/act/addinfo"})
			db.Exec("insert into t_activity_info(custom_name,age,gender,mobile,address,ex_code,source,created_at) values(?,?,?,?,?,?,'order_update',?);", upItem.CustomName, upItem.Age, upItem.Gender, upItem.Mobile, upItem.Birthday, item.ExCode.String, item.CreatedAt)
		}

	}
JUMP:
	//推送消息
	msg := map[string]string{
		"user":   "system",
		"oper":   "add",
		"source": item.Source,
	}
	buf, _ := json.Marshal(msg)
	client := util.GetRedis()
	client.Publish("act-change", string(buf))
	return item, nil
}


func (srv *ActivityService) GetActInfo(mobile string, source string) (interface{}, error) {
	var strSql = "select * from t_activity_info where mobile = ? and source = ?;"
	if source == "BCODE" {
		return _getActInfoByBloodcode(mobile)
	} else if source == "fapiao" || source == "daneng" || source == "renbao" {
		return _getActInfoByExCode(mobile)
	}
	var infos = []entity.ActInfo{}
	db := util.GetSqlDB()
	err := db.Select(&infos, strSql, mobile, source)
	if err != nil {
		return nil, fmt.Errorf("failed to query actinfo: %v", err)
	}
	return infos, nil
}


func _getActInfoByBloodcode(bcode string) (interface{}, error) {
	strSql := "select * from t_activity_info where address = ? order by id desc;"
	var infos = []entity.ActInfo{}
	db := util.GetSqlDB()
	err := db.Select(&infos, strSql, bcode)
	if err != nil {
		return nil, fmt.Errorf("failed to query actinfo: %v", err)
	}
	return infos, nil

}

// qz add 添加按照excode 查询,20200810
// qz add 添加人保 按照excode 查询 20210629
func _getActInfoByExCode(exCode string) (interface{}, error) {
	strSql := "select * from t_activity_info where ex_code = ? order by id desc;"
	var infos = []entity.ActInfo{}
	db := util.GetSqlDB()
	err := db.Select(&infos, strSql, exCode)
	if err != nil {
		return nil, fmt.Errorf("failed to query actinfo: %v", err)
	}
	return infos, nil
}

func (src *ActivityService) UpdateActInfo(item *entity.ActInfo) (interface{}, error) {

	var params = map[string]string{}
	if item.Extra.Valid && item.Extra.String != "" {
		err := json.Unmarshal([]byte(item.Extra.String), &params)
		if err != nil {
			return nil, err
		}
	}
	if item.Source == "fapiao" || item.Source == "daneng" {
		smsSvc := &SMSService{
			ISMSCode: dal.DefaultSMSCodeDal,
		}
		_, err := smsSvc.ValidateCode(item.Mobile.String, params["vcode"], 9)
		if err != nil {
			return nil, errors.New("2::wrong validate code")
		}
	}

	var source = item.Source
	item.Source = ""
	strSql, mkv := util.GenerateUpdateSqlFromStruct("t_activity_info", item, fmt.Sprintf(" where id =%d", item.Id))
	db := util.GetWriteSqlDB()
	_, err := db.NamedExec(strSql, mkv)
	if err != nil {
		return nil, err
	}
	if source == "SendBack" && item.ExCode.Valid {
		return _updateSendBack(item)
	}
	return false, nil
}

//func _updateSendBack(item *entity.ActInfo) (interface{}, error) {
//	var params = map[string]string{}
//	if item.Extra.Valid && item.Extra.String != "" {
//		err := json.Unmarshal([]byte(item.Extra.String), &params)
//		if err != nil {
//			return nil, err
//		}
//	}
//	var expressStatus string
//	db := util.GetWriteSqlDB()
//	err := db.Get(&expressStatus, "select expressstatus from t_order_extra where order_id = ?;", item.ExCode.String)
//	if err != nil {
//		return nil, err
//	}
//	//20220518 外面source 给洗掉了,而且外面判断sendback了,所以这里去掉sendback判断
//	//if item.Source == "SendBack" && (expressStatus == "200" || expressStatus == "100") {
//	if expressStatus == "200" || expressStatus == "100" {
//		db.Exec("update t_order_extra set expressstatus='300', express_user=?, expressnumber_user=? where order_id = ?;", params["express"], params["expressno"], item.ExCode.String)
//	}
//	return true, nil
//}

func _updateSendBack(item *entity.ActInfo) (interface{}, error) {
	var params = map[string]string{}
	if item.Extra.Valid && item.Extra.String != "" {
		err := json.Unmarshal([]byte(item.Extra.String), &params)
		if err != nil {
			return nil, err
		}
	}
	var data struct {
		ExpressStatus string      `db:"expressstatus"`
		ExpressTime   null.String `db:"express_time"`
	}
	db := util.GetWriteSqlDB()
	err := db.Get(&data, "select expressstatus,express_time from t_order_extra where order_id = ?;", item.ExCode.String)
	if err != nil {
		return nil, err
	}
	//20220518 外面source 给洗掉了,而且外面判断sendback了,所以这里去掉sendback判断
	if data.ExpressStatus == "200" || data.ExpressStatus == "100" {
		//20220519 修改原来改成状态300 换成改为210 同时判断extra 里面是否有sendStartTime 更新到express_time 字段中
		db.Exec("update t_order_extra set expressstatus='210', express_user=?, expressnumber_user=? where order_id = ?;", params["express"], params["expressno"], item.ExCode.String)
		sst, ok := params["sendStartTime"]
		if !ok {
			sst = time.Now().Format("2006-01-02 15:04:05")
		}
		if !data.ExpressTime.Valid || data.ExpressTime.String == "" {
			db.Exec("update t_order_extra set express_time = ? where order_id = ?;", fmt.Sprintf("{\"setStartTime\": \"%s\"}", sst), item.ExCode.String)
		} else {
			_, err := db.Exec("update t_order_extra set express_time = JSON_SET(express_time,'$.setStartTime','"+sst+"') where order_id = ?;", item.ExCode.String)
			fmt.Println(err)
		}

	}
	return true, nil
}



func (srv *ActivityService) WithdrawActInfo(item *entity.ActInfo) (interface{}, error) {
	//根据id 获取信息
	var strSql = "select * from t_activity_info where id=? and source in ('fapiao','daneng') and status='500'"

	var infos = entity.ActInfo{}
	db := util.GetSqlDB()
	err := db.Get(&infos, strSql, item.Id)
	if err != nil {
		return nil, fmt.Errorf("1::failed to query actinfo: %v", err)
	}
	var params = map[string]string{}
	if infos.Extra.Valid && infos.Extra.String != "" {
		err := json.Unmarshal([]byte(infos.Extra.String), &params)
		if err != nil {
			return nil, err
		}
	}
	if params["openid"] == "" || params["price"] == "" {
		return nil, fmt.Errorf("2::none openid [%s], or price: [%s]", params["openid"], params["price"])
	}
	_, err = strconv.ParseInt(params["price"], 10, 64)
	if err != nil {
		return nil, fmt.Errorf("3::price: [%s] not real int ", params["price"])
	}
	strSql = "update t_activity_info set status = '600'  where id = ? "
	tx := db.MustBegin()
	sqlResult, err := tx.Exec(strSql, item.Id)
	if err != nil {
		return false, err
	}
	if la, err := sqlResult.RowsAffected(); la <= 0 {
		return false, err
	}
	wxJsSdk := wx.NewJsSdk()
	weixin := wx.NewWeixin(wx.DefaultConfig["appid"], wx.DefaultConfig["secret"])
	nonceStr := wxJsSdk.GenerateNoncestr(32)
	billno := fmt.Sprintf("HB%s%s", time.Now().Format("20060102150405"), util.RandNumString(6))
	reqItem := map[string]string{
		"mch_id":       wx.DefaultConfig["mch_id"],
		"nonce_str":    nonceStr,
		"mch_billno":   billno,
		"wxappid":      wx.DefaultConfig["appid"],
		"send_name":    "小鸟快验",
		"re_openid":    params["openid"],
		"total_amount": params["price"],
		"total_num":    "1",
		"wishing":      "报销费用",
		"client_ip":    "127.0.0.1",
		"act_name":     "红包提现",
		"remark":       "readpack",
		"scene_id":     "PRODUCT_4",
	}
	sign := wxJsSdk.ComputePaySignature(reqItem, wx.DefaultConfig["apikey"])
	reqItem["sign"] = sign
	rel, err := weixin.SendRedpack(reqItem, wx.DefaultConfig["cert_file"], wx.DefaultConfig["key_file"])
	log.Println(rel)
	str := ""
	if err != nil {
		log.Println(err)
		str = err.Error()
	} else {
		arrayByte, _ := json.Marshal(rel)
		str = string(arrayByte)
	}
	if rel["return_code"] == "SUCCESS" && rel["result_code"] == "SUCCESS" {
		tx.Exec("insert into t_activity_withdraw_record(billno, money, activity_id,openid,result,is_success) values(?,?,?,?,?,1);", billno, params["price"], item.Id, params["openid"], str)
		//tx.Exec("insert into t_deliver_msg(deliver_user_id, title, content,created_at) values(?,?,?,?);", duId, "余额提取成功", fmt.Sprintf("您于%s成提取%d元,系统以微信红包的形式发送至您的微信账号,注意查收。", time.Now().Format("2006-01-02"), money/100), time.Now().Format("2006-01-02 15:04:05"))
		tx.Commit()
	} else {
		tx.Tx.Rollback()
		db.Exec("insert into t_activity_withdraw_record(billno, money, activity_id,openid,result,is_success) values(?,?,?,?,?,0);", billno, params["price"], item.Id, params["openid"], str)

		errCode := 0
		var errMsg string
		switch rel["err_code"] {
		case "NO_AUTH":
			errCode = 10
			errMsg = "发放失败,此请求可能存在风险,已被微信拦截"
		case "SENDNUM_LIMIT":
			errCode = 11
			errMsg = "该用户今日领取红包个数超过限制"
		case "ILLEGAL_APPID":
			errCode = 13
			errMsg = "非法appid,请确认是否为公众号的appid,不能为APP的appid"
		case "MONEY_LIMIT":
			errCode = 14
			errMsg = "红包金额发放限制"
		case "SEND_FAILED":
			errCode = 15
			errMsg = "红包发放失败,请更换单号再重试"
		case "FATAL_ERROR":
			errCode = 16
			errMsg = "openid和原始单参数不一致"
		case "CA_ERROR":
			errCode = 17
			errMsg = "商户API证书校验出错"
		case "SIGN_ERROR":
			errCode = 18
			errMsg = "签名错误"
		case "SYSTEMERROR":
			errCode = 19
			errMsg = "请求已受理,请稍后使用原单号查询发放结果"
		case "XML_ERROR":
			errCode = 20
			errMsg = "输入xml参数格式错误"
		case "FREQ_LIMIT":
			errCode = 21
			errMsg = "超过频率限制,请稍后再试"
		case "API_METHOD_CLOSED":
			errCode = 22
			errMsg = "你的商户号API发放方式已关闭,请联系管理员在商户平台开启"
		case "NOTENOUGH":
			errCode = 23
			errMsg = "帐号余额不足,请到商户平台充值后再重试"
		case "OPENID_ERROR":
			errCode = 24
			errMsg = "openid和appid不匹配"
		case "MSGAPPID_ERROR":
			errCode = 25
			errMsg = "触达消息给用户appid有误"
		case "ACCEPTMODE_ERROR":
			errCode = 26
			errMsg = "主、子商户号关系校验失败"
		case "PROCESSING":
			errCode = 27
			errMsg = "请求已受理,请稍后使用原单号查询发放结果"
		case "PARAM_ERROR":
			errCode = 28
			errMsg = "参数错误"
		case "SENDAMOUNT_LIMIT":
			errCode = 29
			errMsg = "您的商户号今日发放金额超过限制,如有需要请登录微信支付商户平台更改API安全配置"
		case "RCVDAMOUNT_LIMIT":
			errCode = 30
			errMsg = "该用户今日领取金额超过限制,如有需要请登录微信支付商户平台更改API安全配置"
		default:
			errCode = 4
			errMsg = fmt.Sprintf("failure withdraw , check db for more information with billno [%s]", billno)
		}

		return nil, fmt.Errorf("%d::[%s]", errCode, errMsg)
		//return false,nil
	}
	return true, nil
}

// 20220505 开票
func (srv *ActivityService) AddInvoice(item *entity.ActInfo) (interface{}, error) {
	//todo 查询是否有开票item.excode
	count, err := GetInvoiceCount(item.ExCode.String)
	if err != nil || count > 0 {
		return nil, fmt.Errorf("1::already fapiao record")
	}
	url := config.IniConf.Section("server").Key("fapiaoapi").String()
	param := map[string]string{}
	err = json.Unmarshal([]byte(item.Extra.String), &param)
	if !item.Extra.Valid || err != nil {
		return nil, fmt.Errorf("2::data extra error")
	}
	param["order_id"] = item.ExCode.String

	db := util.GetSqlDB()
	var price int
	db.Get(&price, "select actual_payment from t_order where id = ?", item.ExCode.String)
	if price == 0 {
		return nil, fmt.Errorf("3::payment is 0")
	}
	param["price"] = strconv.FormatFloat(float64(price)/100, 'f', -1, 64)
	path := "/fp/send"
	b, err := json.Marshal(param)
	reader := bytes.NewReader(b)
	resp, err := http.Post(url+path, "application/json", reader)
	if err != nil {
		return nil, fmt.Errorf("4::send response error %s", err.Error())
	}
	r, err := ioutil.ReadAll(resp.Body)
	if err != nil {
		return nil, fmt.Errorf("5::response read error %s", err.Error())
	}
	defer resp.Body.Close()
	result := struct {
		Code int    `json:"code"`
		Msg  string `json:"msg"`
	}{}
	err = json.Unmarshal(r, &result)
	if err != nil || result.Code != 0 {
		return nil, fmt.Errorf("6::response Unmarshal error %s", string(r))
	}
	return "success", nil
}

// 20220505 获取开票
func GetInvoiceCount(oid string) (int, error) {
	db := util.GetSqlDB()
	var count int
	//err:=db.Get(&count,"select count(1) from t_fapiao_trd where order_id = ? and status !=4",oid)

	//这里不做状态判断,如果提交过,不论成功失败,不可重复提交,后续问题交由财务或开发处理
	err := db.Get(&count, "select count(1) from t_fapiao_trd where order_id = ? ", oid)
	return count, err
}

func (srv *ActivityService) GetInvoice(oid string) ([]entity.ActInfo, error) {
	db := util.GetSqlDB()
	data := struct {
		Type  string `json:"type"`
		Title string `json:"title"`
		Num   string `json:"num"`
		Email string `json:"email"`
	}{}

	//20220524 防止之前开票过,这次又开票
	var strSql = "select * from t_activity_info where ex_code = ? and source = ?;"
	var infos = []entity.ActInfo{}
	db.Select(&infos, strSql, oid, "invoice")

	if len(infos) > 0 {
		return infos, nil
	}

	err := db.Get(&data, "select type,title,tax_num as num,email from t_fapiao_trd where order_id = ? order by created_at desc limit 1", oid)
	if err != nil {
		return nil, fmt.Errorf("failed to query invoice: %v", err)
	}
	b, _ := json.Marshal(data)
	return []entity.ActInfo{{
		ExCode: null.StringFrom(oid),
		Extra:  null.StringFrom(string(b)),
	}}, nil
}