package login

import (
	"encoding/json"
	"errors"
	"fmt"
	"git.listensoft.net/tool/jspkit/common"
	"git.listensoft.net/tool/jspkit/common/lxhttp"
	"git.listensoft.net/tool/jspkit/common/lxrod"
	"git.listensoft.net/tool/jspkit/common/models"
	"git.listensoft.net/tool/jspkit/common/variable"
	"git.listensoft.net/tool/jspkit/logger"
	"git.listensoft.net/tool/jspkit/taxerr"
	"github.com/go-rod/rod"
	"github.com/go-rod/rod/lib/utils"
	"github.com/makiuchi-d/gozxing"
	mqrcode "github.com/makiuchi-d/gozxing/multi/qrcode"
	"github.com/makiuchi-d/gozxing/qrcode"
	"github.com/tidwall/gjson"
	"go.uber.org/zap"
	"golang.org/x/image/draw"
	"golang.org/x/image/math/f64"
	"image"
	"net/url"
	"os"
	"strings"
	"time"
)

const Server = `https://keepalive.listensoft.net`

type LoginInfo struct {
	ComName  string `json:"comName,omitempty"`  //公司名
	District string `json:"district,omitempty"` //地区 shandong
	Password string `json:"password,omitempty"` //密码
	TaxNo    string `json:"taxNo,omitempty"`    //纳税人识别号
	Tel      string `json:"tel,omitempty"`      //手机号
	SmsID    string `json:"sms_id,omitempty"`   //验证码id 后台返回
	Code     string `json:"code,omitempty"`     //输入验证码
	Qrcode   string `json:"qrcode"`
}

func qrLogin(p *rod.Page, info *models.CompanyInfo, lr *lxrod.Lxrod) error {
	info.IsNewApp = true
	if err := checkInfo(*info); err != nil {
		return err
	}
	L, err := ApiLogin(info, info.Tel)
	if err != nil {
		if strings.Contains(err.Error(), "手机号码[") && strings.Contains(err.Error(), "]格式不对") {
			return taxerr.NewUser("[错误]：" + err.Error())
		}
		// 密码错误 未绑定 去截个图
		if errors.Is(err, taxerr.PasswdError) ||
			errors.Is(err, taxerr.UnRegisteredError) ||
			errors.Is(err, taxerr.ComIrrelevantRelationError) ||
			errors.Is(err, taxerr.PasswdLockError) ||
			errors.Is(err, taxerr.TaxNoError) ||
			errors.Is(err, taxerr.UnRegisteredError) ||
			errors.Is(err, taxerr.TelNotFundInTax) ||
			errors.Is(err, taxerr.TaxNotMatch) {
			if path := ErrImage(p, info); path != "" {
				lr.LoginErrImg = path
			}
		}
		return err
	}
	if !L {
		return taxerr.NewWebStuckTitle(common.InfoCscsts)
	}
	// 打开网页 获取二维码
	gotoQr(p, info)
	code, err := GetQrCode(p, "", info.Cscsts)
	if err != nil {
		return err
	}
	// 扫码
	err = ApiScanCode(info, info.Tel, code)
	if err == nil {
		if info.Area == string(variable.Guangxi) {
			utils.Sleep(10)
		}
		tSessionKey := fmt.Sprintf("%s_%s_tpass", info.Tel, info.Area)
		bys, _ := json.Marshal(info)
		sesInfo := []models.SessionInfo{{
			ApiParam: string(bys),
		}}
		common.SaveCookies(tSessionKey, sesInfo, int(time.Now().Unix()), tSessionKey)
	}
	return err
}

// 去登录截一下错误图片
func ErrImage(page *rod.Page, info *models.CompanyInfo) (path string) {
	_ = rod.Try(func() {
		gotoQr(page, info)
		page.Timeout(time.Second * 10).MustElement(`[placeholder="统一社会信用代码/纳税人识别号"]`).MustClick().MustInput(info.TaxNo)
		page.Timeout(common.ClickTimeOut).MustElement(`[placeholder="居民身份证号码/手机号码/用户名"]`).MustClick().MustInput(info.Tel)
		page.Timeout(common.ClickTimeOut).MustElement(`[placeholder="个人用户密码"]`).MustClick().MustInput(info.Zzrmm)
		// 滑块
		{
			box := page.MustElement(`#app > div > div.loginCls > div.mainCls > div > div.login_box > div.password_ddd > div.formContentE > div > div:nth-child(1) > div:nth-child(1) > div > form > div:nth-child(4) > div > div > div > div > div.handler.animate`).MustShape().Box()
			mouse1 := page.Mouse
			mouse := lxrod.Mouse{mouse1}
			mouse.MustMove(box.X, box.Y)
			utils.Sleep(0.2)
			mouse.MustDown("left")
			utils.Sleep(0.2)
			_ = mouse.MustMove(box.X+400, box.Y)
			utils.Sleep(1)
			mouse.MustUp("left")
		}
		page.Timeout(common.ClickTimeOut).MustElementX(`//button/span[text()="登录"]`).MustClick()
		// 确定
		for range ".........." {
			if page.MustHas(`[class="el-message__content"]`) {
				path = common.GeneratePath("png")
				page.MustScreenshot(path)
				path = common.PostSbjt(info.TaxNo, info.Period, path)
				break
			}
		}
	})
	return
}

func ApiLogin(info *models.CompanyInfo, tel string) (Login bool, err error) {
	u := Server + "/api/v1/login"
	data := map[string]string{
		"ComName":  info.ComName,
		"District": info.Area,
		"Password": info.Zzrmm,
		"TaxNo":    info.TaxNo,
		"Tel":      tel,
		"Qrcode":   "",
	}
	C := lxhttp.NewHttpClient(120)
	var response []byte
	for range "..." {
		logger.Info(u)
		a, _ := json.Marshal(data)
		logger.Info(string(a))
		response, err = lxhttp.POSTJson(C, u, data, nil)
		if err != nil {
			// todo 这个接口如果超时了，算成功等待1分钟后 直接去扫码，有问题提示失败重试 加地区
			if (errors.Is(err, taxerr.KeepTimeOut) || strings.Contains(err.Error(), "context deadline exceeded (Client.Timeout exceeded while awaiting headers)")) && taxerr.KeepTimeOutArea[info.Area] ||
				strings.Contains(string(response), "令牌无效") {
				logger.Info("接口超时等待一分钟直接扫码")
				utils.Sleep(60)
				return true, nil
			}
			logger.Info(err)
			utils.Sleep(10)
			continue
		}
		logger.Info(string(response))
		if strings.Contains(string(response), `nginx`) || strings.Contains(string(response), `税局服务异常`) {
			logger.Info("nginx或税局服务异常重试")
			utils.Sleep(10)
			continue
		}
		break
	}
	if err != nil {
		return false, taxerr.NewSystemV3("电子税局网页卡顿", "系统将于30分钟后重试")
	}
	if strings.Contains(string(response), `nginx`) {
		return false, taxerr.TaxAbnormalError(info.Cscsts)
	}
	if gjson.GetBytes(response, `bsqx`).Bool() {
		info.NewTaxBsQx = true
	}
	if gjson.GetBytes(response, "code").Int() == 1 && gjson.GetBytes(response, "message").String() == `success` {
		return true, nil
	}
	msg := gjson.Get(gjson.GetBytes(response, "message").String(), "msg").String()
	if msg != "" {
		return false, taxerr.FormatLoginError(msg)
	}
	if gjson.GetBytes(response, "code").Int() != 1 {
		msg := gjson.GetBytes(response, "message").String()
		if strings.Contains(msg, "手机号正在执行登录任务中，请30s后重试") {
			return false, taxerr.NewWebStuckTitleForErr(msg, info.Cscsts)
		}
		return false, taxerr.FormatLoginError(msg)
	}
	return false, err
}

func gotoQr(p *rod.Page, info *models.CompanyInfo) {
	name := fmt.Sprintf("%s.pro", info.Area)
	qrIndex := areaURLmap[name]
	rod.Try(func() {
		logger.Info("MustNavigate loading")
		w := p.Timeout(common.ClickTimeOut * 3).MustWaitNavigation()
		p.MustNavigate(qrIndex)
		w()
	})
	if info.Area == "xiamen" {
		p.MustWaitDOMStable()
		p.MustReload()
	}
}

// GetQrCode
func GetQrCode(p *rod.Page, qrcodeSelector string, Cscsts bool) (qrCode string, err error) {
	qrcodepath := "qrcode.png"
	if qrcodeSelector == "" {
		qrcodeSelector = "#qrcodeDiv > img"
	}
	HebeiShowKxLoginBox(p)
	utils.Sleep(1)
	rod.Try(func() {
		p.Timeout(common.ClickTimeOut).MustElement(qrcodeSelector)
	})
	HebeiShowKxLoginBox(p)
	HenanShowLoginIndx(p)
	logger.Info("get qrcodeSelector")
	rod.Try(func() {
		logger.Info(p.MustHas(qrcodeSelector))
		HebeiShowKxLoginBox(p)
		utils.OutputFile(qrcodepath, p.Timeout(common.ClickTimeOut*60).MustElement(qrcodeSelector).MustResource()) //
	})
	logger.Info("start QrcodeScanner")
	qrCode, err = QrcodeScanner(qrcodepath)
	if err != nil {
		logger.Info("扫码失败", zap.Error(err))
		newpath := "qrcode_" + common.GetTimestamp(13) + ".png"
		logger.Info("修改图片名称：", newpath)
		os.Rename(qrcodepath, newpath)
		//return "", taxerr.NewSystemV3("获取登录信息出错", "请稍后重试！")
		return "", taxerr.NewWebStuckTitle(Cscsts)
	}
	logger.Info("end QrcodeScanner")
	return
}

func ApiScanCode(info *models.CompanyInfo, tel string, code string) (err error) {
	u := Server + "/api/v1/scanQrcode"
	data := map[string]string{
		"ComName":  info.ComName,
		"District": info.Area,
		"Password": info.Zzrmm,
		"TaxNo":    info.TaxNo,
		"Tel":      tel,
		"Qrcode":   url.QueryEscape(code),
	}
	a, _ := json.Marshal(data)
	logger.Info(string(a))
	C := lxhttp.NewHttpClient(120)
	var response []byte
	for range "..." {
		response, err = lxhttp.POSTJson(C, u, data, nil)
		if err != nil {
			logger.Info(err)
			continue
		}
		if strings.Contains(string(response), `nginx`) || strings.Contains(string(response), `Please try again later`) ||
			strings.Contains(string(response), `应用令牌不能为空`) || strings.Contains(string(response), "授权失败~") {
			logger.Info(string(response))
			utils.Sleep(5)
			continue
		}
		break
	}
	if err != nil {
		return taxerr.NewSystemV3("电子税局网页卡顿", "系统将于30分钟后重试")
	}
	logger.Info(string(response))
	if strings.Contains(string(response), `nginx`) {
		return taxerr.TaxAbnormalError(info.Cscsts)
	}
	if strings.Contains(string(response), "请重新登录") || strings.Contains(string(response), "二维码已失效") {
		return taxerr.NewWebStuckTitle(info.Cscsts)
	}
	if gjson.GetBytes(response, "code").Int() == 1 && gjson.GetBytes(response, "message").String() == `success` {
		return nil
	}
	if message := gjson.GetBytes(response, "data.message"); message.Exists() && message.String() != "" {
		return taxerr.FormatLoginError(message.String())
	}
	return err
}

func HebeiShowKxLoginBox(page *rod.Page) {
	if !strings.Contains(page.MustInfo().URL, "hebei") {
		return
	}
	for range ".........." {
		if page.MustHas(`[placeholder="居民身份证号码/手机号码/用户名"]`) {
			break
		}
		if page.MustHas(`span.loginBtn > span`) {
			logger.Info("HebeiShowKxLoginBox:", page.MustInfo().URL)
			_ = rod.Try(func() {
				page.Timeout(time.Second).MustElement(`span.loginBtn > span`).MustClick()
			})
			//page.MustNavigate(areaURLmap["hebei.pro"])
			utils.Sleep(5)
		}
		utils.Sleep(2)
		logger.Info("重试:", page.MustInfo().URL)
		_ = page.WaitDOMStable(time.Second*5, 0)
	}
}
func HenanShowLoginIndx(p *rod.Page) {
	if !strings.Contains(p.MustInfo().URL, "henan") {
		return
	}
	rod.Try(func() {
		u := p.MustInfo().URL
		logger.Info("当前URL：", u)
		if u == "https://etax.henan.chinatax.gov.cn:8443" || u == "https://etax.henan.chinatax.gov.cn:8443/" {
			p.Timeout(common.ClickTimeOut).MustElement(`//span[text()='登录']`).MustClick()
			logger.Info("河南再次点击登录")
			utils.Sleep(5)
		}
		if u == "https://etax.henan.chinatax.gov.cn:8443" || u == "https://etax.henan.chinatax.gov.cn:8443/" {
			p.MustNavigate(areaURLmap["henan.pro"])
			logger.Info("河南再次navi")
			utils.Sleep(5)
		}
		if !p.MustHas("#qrcodeDiv > img") {
			p.MustNavigate(areaURLmap["henan.pro"])
			logger.Info("河南再次navi2")
			utils.Sleep(5)
		}
	})
}

func QrcodeScanner(imgpath string) (string, error) {
	// open and decode image file
	file, err := os.Open(imgpath)
	if err != nil {
		return "", err
	}
	img, _, err := image.Decode(file)
	if err != nil {
		return "", err
	}
	// prepare BinaryBitmap
	bmp, err := gozxing.NewBinaryBitmapFromImage(img)
	if err != nil {
		return "", err
	}
	// decode image
	qrReader := qrcode.NewQRCodeReader()
	result, err := qrReader.Decode(bmp, nil)
	if err != nil {
		var notFoundException gozxing.NotFoundException
		ok := errors.As(err, &notFoundException)
		if ok {
			logger.Info("Not found exception")
			w, h := img.Bounds().Max.X, img.Bounds().Max.Y
			aff := f64.Aff3{0.8 * 0.5, -0.866, float64(w), 0.866, 1.5 * 0.5, 0}
			img2 := image.NewRGBA(image.Rect(0, 0, w*2, h*2))
			draw.NearestNeighbor.Transform(img2, aff, img, img.Bounds(), draw.Over, nil)
			bmp, _ := gozxing.NewBinaryBitmapFromImage(img2)
			qrReader := qrcode.NewQRCodeReader()
			result, err := qrReader.Decode(bmp, nil)
			if err != nil {
				logger.Info("err:", err.Error())
				return "", err
			}
			return result.String(), nil
		}

		var formatException gozxing.FormatException
		formatIsOK := errors.As(err, &formatException)
		if formatIsOK {
			fmt.Println("Format Exception")
			qrReader := mqrcode.NewQRCodeMultiReader()
			result, err := qrReader.DecodeMultiple(bmp, nil)
			if err != nil {
				logger.Info("err:", err.Error())
				return "", err
			}
			if len(result) >= 1 {
				return result[0].String(), nil
			} else {
				logger.Info("未知错误")
				return "", errors.New("未知错误")
			}
		}
		return "", err
	}
	return result.String(), nil
}
