package common

import (
	"bytes"
	"context"
	"crypto/md5"
	"encoding/hex"
	"encoding/json"
	"fmt"
	"git.listensoft.net/tool/jspkit/common/lxhttp"
	"git.listensoft.net/tool/jspkit/common/models"
	"git.listensoft.net/tool/jspkit/common/variable"
	"git.listensoft.net/tool/jspkit/common/watermark"
	"git.listensoft.net/tool/jspkit/logger"
	"git.listensoft.net/tool/jspkit/taxerr"
	"github.com/go-kratos/kratos/v2/log"
	"github.com/go-rod/rod"
	"github.com/go-rod/rod/lib/input"
	"github.com/go-rod/rod/lib/utils"
	"github.com/tidwall/gjson"
	"io"
	"math/rand"
	"net/http"
	"net/url"
	"os"
	"reflect"
	"regexp"
	"strings"
	"time"
)

type LvName string

const (
	CheckSuccess  LvName = "申报成功" // 检查成功 ------ 检查完全通过
	CheckFail     LvName = "检查失败" // 检查任务失败 ----- 网页打开失败等
	CheckOmit     LvName = "有遗漏"  // 检查有遗漏 ----- 未申报问题
	CheckAbnormal LvName = "有异常"  //检查有异常 ----- 税款问题
)

func GeneratePath(suffix string) string {
	ps := os.PathSeparator
	//检查路径
	if !PathExists("captcha" + string(ps)) {
		_ = os.MkdirAll("captcha"+string(ps), os.ModePerm)
	}
	return "captcha" + string(ps) + GetTimestamp(13) + "." + suffix
}

func PathExists(path string) bool {
	_, err := os.Stat(path)
	if err == nil {
		return true
	}
	if os.IsNotExist(err) {
		return false
	}
	return false
}

// 返回一个32位md5加密后的字符串
func Md5(data string) string {
	h := md5.New()
	h.Write([]byte(data))
	return hex.EncodeToString(h.Sum(nil))
}

/*
生成检查截图本地路径

		lv    图片对应的错误等级 用上面定义好的  Omit>Abnormal
		taxNo 税号
	    period 账期
		location 截图位置,例如申报清册、申报结果查询、税款缴纳、缴费缴纳查询
*/
func GenerateCheckImagePath(lv LvName, taxNo, period, location string) string {
	var path string
	path = "./data/check/" + taxNo + "/"
	if !PathExists(path) {
		os.MkdirAll(path, os.ModePerm)
	}
	return fmt.Sprintf(`%s-%s-%s-新版_%s_%s.png`, path+taxNo, period, GetTimestamp(13), location, lv)
}

func PostSbjt(taxno, period, path string) string {
	var params = map[string]string{
		"tax_no": taxno,
		"period": period,
		"type":   "etax", //电子税务局
	}
	str, err := PostFile("https://file.listensoft.net/api/v1/uploadsbjt", map[string]string{
		"file": path,
	}, params)
	if err != nil {
		log.Warnf("【%s】文件上传失败", path)
	}
	return gjson.GetBytes(str, "path").String()
}

// 获取申报截图的path
// taxNo 纳税人识别号 period 账期 taxType 任务名称
func GetSbjtPath(taxNo, period string, taxType variable.TaskName) string {
	var path string
	path = "./data/" + taxNo + "/"
	if !PathExists(path) {
		os.MkdirAll(path, os.ModePerm)
	}
	path = path + period + string(taxType) + "_" + GetTimestamp(13) + ".png"
	return path
}

// 判断字符串是否全是英文
func IsAllEnglish(s string) bool {
	// 检查字符串是否全部由ASCII字符组成
	for _, c := range s {
		if c > 127 {
			return false
		}
	}
	return true
}

// CreateFormReader 将 map 转换成 header
func CreateFormReader(data map[string]string) io.Reader {
	form := url.Values{}
	for k, v := range data {
		form.Add(k, v)
	}
	return strings.NewReader(form.Encode())
}

func Unbracket(str string) string {
	str = strings.ReplaceAll(str, "（", "(")
	str = strings.ReplaceAll(str, "）", ")")
	str = strings.ReplaceAll(str, " ", "")
	return str
}

// 检查是否是手机号
func CheckMobile(mobile string) bool {
	r := regexp.MustCompile(`^1[0-9]{10}$`)
	return r.Match([]byte(mobile))
}

func NewWebStuckTitle() *taxerr.SystemErr {
	return taxerr.NewSystemV3("电子税局页面卡顿", "系统将于30分钟后重试(可在\"通用设置\"关闭)")
}

// 查看设备是否在线
func CheckEqmLive(tel string) bool {
	client := lxhttp.NewHttpClient()
	defer client.CloseIdleConnections()
	bys, err := lxhttp.POSTJson(client, "https://keep.jsptax.com"+"/api/v1/telDeviceStatus", map[string]string{"mobiles": tel}, map[string]string{})
	if err != nil {
		return false
	}
	if gjson.GetBytes(bys, `data.status`).Bool() {
		return true
	}
	return false
}

func GetData(key variable.SbKey, data string, target interface{}) bool {
	str := gjson.Get(data, string(key)).String()
	if err := MapToStruct(str, target); err != nil {
		logger.Info(err)
		return false
	}
	return true
}

// target 传指针
func MapToStruct(source interface{}, target interface{}) error {
	resByre := []byte("")
	var resByteErr error
	if reflect.TypeOf(source).String() == "string" {
		str := source.(string)
		resByre = []byte(str)
	} else {
		resByre, resByteErr = json.Marshal(source)
		if resByteErr != nil {
			return resByteErr
		}
	}
	err := json.Unmarshal(resByre, target)
	if err != nil {
		return err
	}
	return nil
}

// CancelLeftTasks 撤销其他的任务，避免一直发验证码导致超限，仅新版登录使用
func CancelLeftTasks(tel string) {
	c := lxhttp.NewHttpClient()
	lxhttp.POSTJson(c, variable.TaxTaskURL+"/api/v1/stopQueue", map[string]string{"tel": tel}, nil)
}

// GetTelTaskStatus 如果有任务正在进行 返回 true
func GetTelTaskStatus(tel string) (bool, error) {
	c := lxhttp.NewHttpClient()
	bys, err := lxhttp.POSTJson(c, `https://task.listensoft.net/api/v1/other/telTaskDoi`, map[string]string{
		"tel": tel,
	}, nil)
	if err != nil {
		return false, err
	}
	if gjson.GetBytes(bys, `data.res`).Bool() {
		return true, nil
	} else {
		return false, nil
	}
}

var rng = rand.New(rand.NewSource(time.Now().UnixMicro()))

// GenTracks 函数生成移动轨迹。先做加速运动，再做减速运动。
//
//	设滑块滑动的加速度为 a；当前速度为 v；初速度为 v0；位移为 x；所需时间为 t。
//	根据加速度物理公式，它们满足以下关系：
//	v = v0 + a * t
//	x = v0 * t + 0.5 * a * t * t
//
// 参考：
//
// https://python3webspider.cuiqingcai.com/8.2-ji-yan-hua-dong-yan-zheng-ma-shi-bie
func GenTracks(distance float64) []float64 {
	var track = make([]float64, 0)     // 移动轨迹
	var current float64 = 0            // 当前位移
	var mid float64 = distance * 4 / 5 // 减速阈值
	var t float64 = 0.15               // 计算间隔
	var v float64 = 0                  // 初速度
	var a1 = rng.Float64()*6 + 2       // 正加速度 [2,8)
	var a2 = -a1 - 2                   // 负加速度 (-10,-4]

	for current < distance {
		// 加速度
		var a float64
		if current < mid {
			a = a1
		} else {
			a = -a2
		}
		// 初速度 v0
		v0 := v
		// 当前速度 v = v0 + at
		v = v0 + a*t
		// 移动距离 x = v0t + 1/2 * a * t^2
		x := v0*t + 1.0/2.0*a*t*t

		current += x
		track = append(track, x)
	}

	// 修正超标值
	track = append(track, distance-current)
	return track
}

func Trim(str interface{}) string {
	return strings.TrimSpace(fmt.Sprintf("%s", str))
}

// 生成随机数
func RandSeq(n int) string {
	var letters = []rune("1234567890ABCDEFGHIJKLMNOPQRSTUVWXYZ")
	b := make([]rune, n)
	for i := range b {
		b[i] = letters[rand.Intn(len(letters))]
	}
	return string(b)
}

func SwitchCwbb(baseCwbb, targetCwbb variable.Kjze, basezcfz []models.KjZcfz, baseLrb []models.KjLrb) (zcfz []models.KjZcfz, lrb []models.KjLrb, err error) {
	uri := `https://daizhang.jsptax.com/api/public/kjzzConvert`
	payload := map[string]interface{}{
		"oldKjzz":  string(baseCwbb),
		"newKjzz":  string(targetCwbb),
		"dataLrb":  baseLrb,
		"dataZcfz": basezcfz,
	}
	data, _ := json.Marshal(payload)
	p := "./data/kjzzConvert" + GetTimestamp(13) + ".json"
	if !PathExists("pdf" + string(p)) {
		os.MkdirAll("pdf"+string(p), os.ModePerm)
	}
	logger.Info(p)
	_ = utils.OutputFile(p, data)
	req, err := http.NewRequest("POST", uri, bytes.NewReader(data))

	if err != nil {
		fmt.Println(err)
		return
	}
	req.Header.Add("Content-Type", "application/json")
	client := &http.Client{}
	res, err := client.Do(req)
	if err != nil {
		fmt.Println(err)
		return
	}
	defer res.Body.Close()

	body, err := io.ReadAll(res.Body)
	if err != nil {
		fmt.Println(err)
		return
	}
	if gjson.GetBytes(body, "msg").String() != "success" {
		return nil, nil, taxerr.NewUserV3(gjson.GetBytes(body, "msg").String(), "请核实")
	}
	_ = json.Unmarshal([]byte(gjson.GetBytes(body, "data.lrb").String()), &lrb)
	_ = json.Unmarshal([]byte(gjson.GetBytes(body, "data.zcfz").String()), &zcfz)
	return
}

// 获取省份
func GetProvince(url string) string {
	if strings.Contains(url, "sichuan") {
		return "四川省"
	} else if strings.Contains(url, "beijing") {
		return "北京市"
	} else if strings.Contains(url, "tianjin") {
		return "天津市"
	} else if strings.Contains(url, "hebei") {
		return "河北省"
	} else if strings.Contains(url, "shanxi") {
		return "山西省"
	} else if strings.Contains(url, "neimenggu") {
		return "内蒙古自治区"
	} else if strings.Contains(url, "liaoning") {
		return "辽宁省"
	} else if strings.Contains(url, "dalian") {
		return "大连市"
	} else if strings.Contains(url, "jilin") {
		return "吉林省"
	} else if strings.Contains(url, "heilongjiang") {
		return "黑龙江省"
	} else if strings.Contains(url, "shanghai") {
		return "上海市"
	} else if strings.Contains(url, "jiangsu") {
		return "江苏省"
	} else if strings.Contains(url, "zhejiang") {
		return "浙江省"
	} else if strings.Contains(url, "ningbo") {
		return "宁波市"
	} else if strings.Contains(url, "anhui") {
		return "安徽省"
	} else if strings.Contains(url, "fujian") {
		return "福建省"
	} else if strings.Contains(url, "xiamen") {
		return "厦门市"
	} else if strings.Contains(url, "jiangxi") {
		return "江西省"
	} else if strings.Contains(url, "shandong") {
		return "山东省"
	} else if strings.Contains(url, "qingdao") {
		return "青岛市"
	} else if strings.Contains(url, "henan") {
		return "河南省"
	} else if strings.Contains(url, "hubei") {
		return "湖北省"
	} else if strings.Contains(url, "hunan") {
		return "湖南省"
	} else if strings.Contains(url, "guangdong") {
		return "广东省"
	} else if strings.Contains(url, "shenzhen") {
		return "深圳市"
	} else if strings.Contains(url, "guangxi") {
		return "广西壮族自治区"
	} else if strings.Contains(url, "hainan") {
		return "海南省"
	} else if strings.Contains(url, "chongqing") {
		return "重庆市"
	} else if strings.Contains(url, "guizhou") {
		return "贵州省"
	} else if strings.Contains(url, "yunnan") {
		return "云南省"
	} else if strings.Contains(url, "xizang") {
		return "西藏自治区"
	} else if strings.Contains(url, "shaanxi") {
		return "陕西省"
	} else if strings.Contains(url, "gansu") {
		return "甘肃省"
	} else if strings.Contains(url, "qinghai") {
		return "青海省"
	} else if strings.Contains(url, "ningxia") {
		return "宁夏回族自治区"
	} else if strings.Contains(url, "xinjiang") {
		return "新疆维吾尔自治区"
	} else if strings.Contains(url, "taiwan") {
		return "台湾省"
	} else {
		return ""
	}
}

// 输入文字
func InputX(p *rod.Page, xPath, inputVal string) {
	err := rod.Try(func() {
		if inputVal == "" {
			inputVal = "0"
		}
		_ = rod.Try(func() {
			elementX := MustElementX(p, xPath)
			disabled := elementX.MustAttribute(`disabled`)
			if disabled == nil {
				elementX.MustType(input.Backspace)
				elementX.MustSelectAllText().MustInput(inputVal)
			}
		})
	})
	if err != nil {
		logger.Info(err.Error() + "-" + xPath + "-" + inputVal)
	}
}

// 校验填写的对不对
func CheckInput(p *rod.Page, xPath, inputVal string, name string) (errMsg string) {
	if inputVal == "" {
		inputVal = "0"
	}
	val := GetInputValue(p, xPath)
	if StrToFloat(val) != StrToFloat(inputVal) {
		errMsg = fmt.Sprintf("%s不一致，税局：%s，系统：%s;", name, val, inputVal)
	}
	return
}

func SaveCookiesNoPhone(key string) error {
	var a interface{}
	_ = lxhttp.PostJson(variable.SessionKeepURL+"/api/v1/session/save", map[string]interface{}{
		"sessionKey":  key,
		"tsessionKey": key,
	}, &a)
	logger.Info("SaveSessionKey:", key)
	return nil
}

func SaveErrImgLv(lv LvName, p *rod.Page, info models.CompanyInfo, location string) string {
	checkPath := GenerateCheckImagePath(lv, info.TaxNo, info.Period, location)
	p.Timeout(ClickTimeOut).MustScreenshot(checkPath)
	_ = watermark.Add(checkPath)
	checkPath2 := PostSbjt(info.TaxNo, info.Period, checkPath)
	_ = os.Remove(checkPath)
	return checkPath2
}

func CheckError(ctx context.Context, task *models.TaxTask, err error) {
	errStr := HandleError(ctx, err).Error()
	if strings.Contains(errStr, "网页") {
		//task.Result.Status = variable.TaxFail
		//task.Result.ErrLog = errStr
		task.Result.BusinessStatus = variable.TaxFail
		task.Result.BusinessLog = errStr
	} else {
		task.Result.BusinessStatus = variable.TaxFail
		task.Result.BusinessLog = errStr
	}
}

// 时间格式化
func TimeFormatM(str string, Type int) string {
	if str == "" {
		return ""
	}
	//1.str = 2035年6月23日
	if Type == 1 {
		data, _ := time.Parse("2006年1月2日", str)
		return data.Format("2006-01-02")
	}
	return str
}

// 去空格
func DelSpace(str string) string {
	return strings.Replace(str, " ", "", -1)
}

// 登录异常统一提示
func LoginErr() *taxerr.SystemErr {
	return taxerr.NewSystemV3("登录出现异常", "系统将于30分钟后重试(可在\"通用设置\"关闭)")
}

func GetMobileOnline(mobile string) bool {
	type getAppHeartbeatSmsRes struct {
		Data struct {
			List []struct {
				ID        int       `json:"ID"`
				CreatedAt time.Time `json:"CreatedAt"`
				UpdatedAt time.Time `json:"UpdatedAt"`
				Mobile    string    `json:"Mobile"`
				Heartbeat time.Time `json:"Heartbeat"`
				Status    string    `json:"status"`
			} `json:"list"`
			Total int `json:"total"`
		} `json:"data"`
		ErrNo int    `json:"errNo"`
		Msg   string `json:"msg"`
	}

	resp, err := http.Post(`https://public.listensoft.net/api/getAppHeartbeatSms`, "application/json", bytes.NewBufferString(`{"mobile":"`+mobile+`"}`))
	if err != nil {
		return false
	}
	all, err := io.ReadAll(resp.Body)
	logger.Info("getAppHeartbeatSms:", string(all))
	res := getAppHeartbeatSmsRes{}
	_ = json.Unmarshal(all, &res)
	for _, m := range res.Data.List {
		if m.Mobile == mobile && m.Status == "ok" {
			return true
		}
	}
	return false
}
