package common

import (
	"errors"
	"os"
	"strings"
	"time"

	"git.listensoft.net/tool/jspkit/common/lxrod"
	"git.listensoft.net/tool/jspkit/common/models"
	"git.listensoft.net/tool/jspkit/common/watermark"
	"git.listensoft.net/tool/jspkit/logger"
	"git.listensoft.net/tool/jspkit/taxerr"
	"github.com/go-rod/rod"
	"github.com/go-rod/rod/lib/input"
	"github.com/go-rod/rod/lib/proto"
	"github.com/go-rod/rod/lib/utils"
	"go.uber.org/zap"
)

func MustHas(page *rod.Page, xpath string) bool {
	x, _, _ := HasX(page, xpath)
	return x
}

func MustHasX(page *rod.Page, xpath string) bool {
	x, _, _ := HasX(page, xpath)
	return x
}

func MustHasXV(page *rod.Page, xpath string) bool {
	for _, element := range MustElementsX(page, xpath) {
		visible, _ := element.Visible()
		if visible {
			return true
		}
	}
	return false
}

func MustElementX(page *rod.Page, xpath string) *rod.Element {
	for i := 0; i < 5; i++ {
		x, _ := ElementX(page, xpath)
		if x != nil {
			return x
		}
		utils.Sleep(1)
	}
	return nil
}

func MustHasXV1(page *rod.Page, xpath string) bool {
	for _, element := range MustElementsX1(page, xpath) {
		visible, _ := element.Visible()
		if visible {
			return true
		}
	}
	return false
}

func HasX(page *rod.Page, xpath string) (bool, *rod.Element, error) {
	var x *rod.Element
	var has bool
	var err error
	err = rod.Try(func() {
		has, x, err = page.HasX(xpath)
		if has {
			return
		}
		if err != nil {
			panic(err)
		}
		if has {
			return
		}
		iframe := GetAllIframe(page)
		for _, pg := range iframe {
			has, x, err = pg.HasX(xpath)
			if has {
				return
			}
			if err != nil {
				panic(err)
			}
			if has {
				return
			}
		}
	})
	if x != nil {
		x = x.Timeout(time.Second * 15)
	}
	return has, x, err
}

func ElementX(page *rod.Page, xpath string) (*rod.Element, error) {
	_, element, err := HasX(page, xpath)
	if err != nil {
		err = taxerr.NewWebStuckTitle(false)
	}
	return element, err
}

func GetAllIframe(page *rod.Page) []*rod.Page {
	fs := make([]*rod.Page, 0)
	if page.MustHasX(`//iframe`) {
		for _, element := range page.MustElementsX(`//iframe`) {
			fs = append(fs, element.MustFrame())
			fs = append(fs, GetAllIframe(element.MustFrame())...)
		}
	}
	return fs
}

func MustElementXV(page *rod.Page, xpath string) *rod.Element {
	for i := 0; i < 5; i++ {
		x, _ := ElementsX(page, xpath)
		for _, element := range x {
			visible, _ := element.Visible()
			if visible {
				return element
			}
		}
		utils.Sleep(1)
	}
	return nil
}

func MustElementsX(page *rod.Page, xpath string) (x []*rod.Element) {
	for i := 0; i < 5; i++ {
		x, _ = ElementsX(page, xpath)
		if len(x) != 0 {
			break
		}
		utils.Sleep(1)
	}
	return x
}

func MustElementsX1(page *rod.Page, xpath string) (x []*rod.Element) {
	x, _ = ElementsX(page, xpath)
	return x
}

func ElementsX(page *rod.Page, xpath string) ([]*rod.Element, error) {
	var arr []*rod.Element
	var err error
	err = rod.Try(func() {
		r, _ := page.Timeout(ClickTimeOut).ElementsX(xpath)
		if err != nil {
			panic(err)
		}
		arr = append(arr, r...)
		for _, pg := range GetAllIframe(page) {
			pg.MustWaitLoad()
			r, err = pg.Timeout(ClickTimeOut).ElementsX(xpath)
			if err != nil {
				panic(err)
			}
			arr = append(arr, r...)
		}
	})
	return arr, err
}

func WaitElementX(page *rod.Page, xpath string, interval float64) *rod.Element {
	for i := 0; i < 60; i++ {
		if has, el, _ := HasX(page, xpath); has {
			return el
		}
		time.Sleep(time.Duration(float64(time.Second) * interval))
	}
	return nil
}

func WaitNavigation(page *rod.Page, url string) {
	navigation := page.Timeout(time.Minute).MustWaitNavigation()
	page.Timeout(time.Minute).MustNavigate(url)
	logger.Info("调试埋点11-1")
	utils.Sleep(1)
	WaitTimeOut(navigation, time.Minute)
	logger.Info("埋点11-2")
}

func WaitTimeOut(f func(), timeout time.Duration) {
	resultChan := make(chan struct{}, 1)
	go func() {
		f()
		resultChan <- struct{}{}
	}()
	select {
	case <-resultChan:
		return
	case <-time.After(timeout):
		panic(taxerr.NewWebStuckTitle(ComInfo.Cscsts))
	}
}

func Link(p *rod.Page) {
	go func() {
		//for {
		//utils.Sleep(1)
		if p == nil {
			return
		}
		rod.Try(func() {
			lxrod.DialogWatch(nil, p)
			go p.Browser().EachEvent(func(e *proto.TargetTargetCreated) {
				defer func() {
					if r := recover(); r != nil {
						logger.Error("EachPageCreatedEvent recover:", zap.Any("r", r))
					}
				}()
				if e.TargetInfo.Type != proto.TargetTargetInfoTypePage {
					return
				}
				page := p.Browser().MustPageFromTargetID(e.TargetInfo.TargetID)
				lxrod.DialogWatch(nil, page)
			})()
			//		has, _, _ := p.Has("dialog")
			//		if has {
			//			_, _ = p.Eval(`
			//window.alert = function() {};
			//window.confirm = function() { return true; };
			//window.prompt = function() {};`)
			//		}
		})
		//}
	}()
}

// 有就点 没有就不点击
func MustClick(page *rod.Page, xpath string) {
	x := MustElementsX(page, xpath)
	for _, element := range x {
		visible, _ := element.Visible()
		if visible {
			element.MustClick()
			break
		}
	}
}

// 获取所有 iframe 中 xpath 对应元素的文本，顺便去掉空白字符。
//
// 注意: MustElementX 在没有找到元素时会返回 nil，导致
// MustText panic 空指针异常，和期望错误（元素未找到）不一致。
func MustText(page *rod.Page, xpath string) string {
	//去掉左右空格
	return strings.TrimSpace(MustElementX(page, xpath).MustText())
}

// 输入文字
func Input(p *rod.Page, selector, inputVal string) {
	err := rod.Try(func() {
		if inputVal == "" || inputVal == "*" {
			inputVal = "0"
		}
		e := rod.Try(func() {
			p.Timeout(time.Second).MustSearch(selector).MustSelectAllText().MustFrame().Keyboard.MustType(input.Backspace)
		})
		if e != nil {
			if w := errors.Unwrap(e); w != nil {
				logger.Info(w.Error() + "-" + selector + "-" + inputVal)
			} else {
				logger.Info(e.Error() + "-" + selector + "-" + inputVal)
			}
		}
		e = rod.Try(func() {
			p.Timeout(time.Second).MustSearch(selector).MustSelectAllText().MustInput(inputVal)
		})
		if e != nil {
			if w := errors.Unwrap(e); w != nil {
				logger.Info(strings.Split(w.Error(), "\t")[0] + "-" + selector + "-" + inputVal)
			} else {
				logger.Info(strings.Split(e.Error(), "\t")[0] + "-" + selector + "-" + inputVal)
			}
		}
	})
	if err != nil {
		logger.Info(err.Error() + "-" + selector + "-" + inputVal)
	}
}

// 输入文字
func MustInputX(p *rod.Page, xPath, inputVal string) {
	e := MustElementX(p, xPath)
	_ = e.ScrollIntoView()
	e.MustClick().MustSelectAllText().MustInput(inputVal)
}

// 拦截请求
func PageHijackReq(b *rod.Page, pattern string, ch chan []byte) *rod.HijackRouter {
	router := b.HijackRequests()
	router.MustAdd(pattern, func(ctx *rod.Hijack) {
		rod.Try(func() {
			ctx.MustLoadResponse()
			ch <- []byte(ctx.Response.Body())
			utils.Sleep(5)
			select {
			case <-ch:
				break
			}
		})
	})
	go router.Run()
	return router
}

// 获取拦截请求的值
func GetHijackResp(ch chan []byte, t float64) ([]byte, error) {
	d := time.Duration(t * float64(time.Second))
	select {
	case val := <-ch:
		return val, nil
	case <-time.After(d):
		return nil, errors.New("请求网页超时，请稍后重试!")
	}
}

// WaitForPageLoad 等待页面完全加载，selector 为等待某个元素加载的选择器，可选
func WaitForPageLoad(page *rod.Page, timeoutDuration time.Duration, selector ...string) {
	start := time.Now()
	// 1. 等待页面 readyState 为 "complete"，加上超时控制
	for {
		readyState := page.MustEval(`() => document.readyState`).String()
		if readyState == "complete" {
			break // 页面加载完成，退出循环
		}
		// 检查是否超时
		if time.Since(start) > timeoutDuration {
			break
		}
		time.Sleep(500 * time.Millisecond) // 每次检查之间等待一段时间
	}
	// 2. 等待页面元素稳定
	page.MustWaitStable()
	// 3. 如果传入了 selector，等待特定元素加载完成
	if len(selector) > 0 {
		rod.Try(func() {
			page.Timeout(timeoutDuration).MustElement(selector[0]) // 使用传入的选择器等待元素
		})
	}
	// 4. 等待所有网络请求结束
	rod.Try(func() {
		page.Timeout(timeoutDuration).MustWaitIdle()
	})
	utils.Sleep(3)
}

func WaitHasX(page *rod.Page, xpath string) (x bool) {
	for i := 0; i < 5; i++ {
		x, _, _ = HasX(page, xpath)
		if !x {
			utils.Sleep(1)
		}
	}
	return x
}

func SaveErrImg(p *rod.Page, info models.CompanyInfo) string {
	var check_path string
	check_path = "./data/sbImg/" + info.TaxNo + "/"
	if !PathExists(check_path) {
		os.MkdirAll(check_path, os.ModePerm)
	}
	check_path = check_path + info.Period + "-" + time.Now().Format("2006-01-02-15-04-05") + ".png"
	check_path2 := ""
	_ = rod.Try(func() {
		p.Timeout(ClickTimeOut).MustScreenshot(check_path)
		watermark.Add(check_path)
		check_path2 = PostSbjt(info.TaxNo, info.Period, check_path)
		os.Remove(check_path)
	})
	return check_path2
}

// InputWithTimeout 规定时间内填入输入值，默认5s
func InputWithTimeout(p *rod.Page, selector, inputVal string, timeout ...int64) {
	var t int64
	if len(timeout) == 0 {
		t = 5
	} else {
		t = timeout[0]
	}
	p.Timeout(time.Duration(t) * time.Second).MustSearch(selector).MustSelectAllText().MustInput(inputVal)
}

func SaveErrImgTask(p *rod.Page, info *models.TaxTask) string {
	var check_path string
	check_path = "./data/sbImg/" + info.TaxNo + "/"
	if !PathExists(check_path) {
		os.MkdirAll(check_path, os.ModePerm)
	}
	check_path = check_path + info.Period + "-" + time.Now().Format("2006-01-02-15-04-05") + ".png"
	var check_path2 string
	err := rod.Try(func() {
		p.Timeout(ClickTimeOut).MustScreenshot(check_path)
		watermark.Add(check_path)
		check_path2 = PostSbjt(info.TaxNo, info.Period, check_path)
		os.Remove(check_path)
	})
	if err != nil {
		logger.Error("截图失败", err)
	}
	return check_path2
}

// 使用键盘模拟输入
func InputStrNew(page *rod.Page, el *rod.Element, str string) {
	el.MustScrollIntoView()
	box := el.MustShape().Box()
	Click(page, box.X+(box.Width/2), box.Y+(box.Height/2))
	time.Sleep(time.Millisecond * 100)
	selectAll2(page)
	time.Sleep(time.Millisecond * 100)
	_ = page.Keyboard.Type(input.Delete)
	_ = page.Keyboard.Type(input.Backspace)
	time.Sleep(time.Millisecond * 100)
	_ = page.Keyboard.Type(input.Backspace)
	for _, v := range str {
		_ = page.Keyboard.Type(input.Key(v))
		time.Sleep(time.Millisecond * 100)
	}
}

func Click(page *rod.Page, x, y float64) {
	page.Mouse.MustMoveTo(x, y)
	time.Sleep(time.Millisecond * 100)
	page.Mouse.MustDown("left")
	page.Mouse.MustUp("left")
}
func selectAll2(page *rod.Page) {
	page.Keyboard.MustType(input.ControlLeft).MustType('a').MustType('a').MustType(input.ControlLeft)
}

// 输入文字Str
func InputStr(p *rod.Page, selector, inputVal string) {
	err := rod.Try(func() {
		e := rod.Try(func() {
			p.Timeout(time.Duration(1 * float64(time.Second))).MustSearch(selector).MustSelectAllText().MustFrame().Keyboard.MustType(input.Backspace)
		})
		if e != nil {
			logger.Info(e.Error() + "-" + selector + "-" + inputVal)
		}
		e = rod.Try(func() {
			p.Timeout(time.Duration(1 * float64(time.Second))).MustSearch(selector).MustSelectAllText().MustInput(inputVal)
		})
		if e != nil {
			logger.Info(strings.Split(e.Error(), "\t")[0] + "-" + selector + "-" + inputVal)
		}
	})
	if err != nil {
		logger.Info(err.Error() + "-" + selector + "-" + inputVal)
	}
}

// 输入文字 string
func InputElementXStr(p *rod.Page, xPath, inputVal string) {
	elementX := MustElementX(p, xPath)
	disabled := elementX.MustAttribute("disabled")
	if disabled != nil {
		return
	}
	readonly := elementX.MustAttribute("readonly")
	if readonly != nil {
		return
	}
	elementX.MustSelectAllText().MustInput(inputVal)
}

// 等待所有 iframe 中 xpath 对应的元素加载。
// 注意：加载失败返回为 nil，不做处理的话很容易引起空指针 panic。
func WaitElementXFoTimesNil(page *rod.Page, xpath string, times int) *rod.Element {
	for i := 0; i < times; i++ {
		if has, el, _ := HasX(page, xpath); has && MustElementX(page, xpath).MustVisible() {
			return el
		}
		time.Sleep(time.Second)
	}
	return nil
}

func MustElementX10(page *rod.Page, xpath string) *rod.Element {
	for i := 0; i < 10; i++ {
		x, _ := ElementX(page, xpath)
		if x != nil {
			return x
		}
		utils.Sleep(1)
	}
	return nil
}

func MustElementX15(page *rod.Page, xpath string) *rod.Element {
	for i := 0; i < 15; i++ {
		x, _ := ElementX(page, xpath)
		if x != nil {
			return x
		}
		utils.Sleep(1)
	}
	return nil
}

func MustHasXV30(page *rod.Page, xpath string) bool {
	for _, element := range MustElementsX30(page, xpath) {
		visible, _ := element.Visible()
		if visible {
			return true
		}
	}
	return false
}
func MustElementsX30(page *rod.Page, xpath string) (x []*rod.Element) {
	for i := 0; i < 30; i++ {
		x, _ = ElementsX(page, xpath)
		if len(x) != 0 {
			break
		}
		utils.Sleep(1)
	}
	return x
}

// 等待所有 iframe 中 xpath 对应的元素加载。
// 注意：加载失败 panic 的是税局卡顿错误。非税局采集任务不应使用该函数。
func WaitElementXFoTimes(page *rod.Page, xpath string, times int) *rod.Element {
	for i := 0; i < times; i++ {
		if has, el, _ := HasX(page, xpath); has && MustElementX(page, xpath).MustVisible() {
			return el
		}
		time.Sleep(time.Second)
	}
	logger.Info("页面加载失败元素未找到,请稍后再试", xpath)
	panic(taxerr.NewWebStuckTitle(InfoCscsts))
}

func MustElementsX10(page *rod.Page, xpath string) (x []*rod.Element) {
	for i := 0; i < 10; i++ {
		x, _ = ElementsX(page, xpath)
		if len(x) != 0 {
			break
		}
		utils.Sleep(1)
	}
	return x
}

func WaitMustElementsX(page *rod.Page, xpath string) (elementsX []*rod.Element) {
	for i := 0; i < 5; i++ {
		elementsX = MustElementsX(page, xpath)
		if len(elementsX) != 0 {
			return
		}
		utils.Sleep(1)
	}
	return
}
func ClickB(page *rod.Page, el string) {
	box := MustElementX(page, el).MustShape().Box()
	Click(page, box.X+(box.Width/2), box.Y+(box.Height/2))
}

// 等待所有 iframe 中 xpath 对应的元素加载。
// 注意：加载失败 panic 的是税局卡顿错误。非税局采集任务不应使用该函数。
func WaitElementX10(page *rod.Page, xpath string) *rod.Element {
	return WaitElementXFoTimes(page, xpath, 10)
}

func MustElementX30(page *rod.Page, xpath string) *rod.Element {
	for i := 0; i < 30; i++ {
		x, _ := ElementX(page, xpath)
		if x != nil {
			return x
		}
		utils.Sleep(1)
	}
	return nil
}

func WaitHasX10(page *rod.Page, xpath string) (x bool) {
	for i := 0; i < 10; i++ {
		x, _, _ = HasX(page, xpath)
		if !x {
			utils.Sleep(1)
		}
	}
	return x
}

// 返回当前元素下一个元素的text 如果没有元素就直接返回空
func MustNextText(page *rod.Page, xpath string) string {
	x := MustElementXV(page, xpath)
	if x == nil {
		return ""
	}
	return x.MustNext().MustText()
}

// 2033年12月31日 // 2033-12-31
func DataFormat(data string) string {
	strings.ReplaceAll(data, "年", "-")
	strings.ReplaceAll(data, "月", "-")
	strings.ReplaceAll(data, "日", "")
	if len(data) > 10 {
		data = data[:10]
	}
	return data
}

// 返回当前元素下一个元素的text 如果没有元素就直接返回空
func MustNext2Text(page *rod.Page, xpath string) string {
	x := MustElementXV(page, xpath)
	if x == nil {
		return ""
	}
	return x.MustNext().MustNext().MustText()
}

// 拦截
func HijackReq(page *rod.Page, uri, xPath string) []byte {
	ch := make(chan []byte)
	r := PageHijackReq(page, uri, ch)
	MustElementX(page, xPath).MustClick()
	data, err := GetHijackResp(ch, 10)
	if err != nil {
		panic(taxerr.NewWebStuckTitle(false))
	}
	_ = r.Stop()
	return data
}

// 输入文字 float
func InputElementX(p *rod.Page, xPath, inputVal string) {
	disabled := MustElementX(p, xPath).MustAttribute("disabled")
	if disabled != nil {
		return
	}
	readonly := MustElementX(p, xPath).MustAttribute("readonly")
	if readonly != nil {
		return
	}
	float := StrToFloat(inputVal)
	if float == 0 {
		inputVal = ""
	} else {
		inputVal = FloatToStr(float)
	}
	MustElementX(p, xPath).MustSelectAllText().MustInput(inputVal)
}

func GetText(page *rod.Page, selector string) string {
	return page.Timeout(ClickTimeOut).MustSearch(selector).MustText()
}

func WaitHasX15(page *rod.Page, xpath string) (x bool) {
	for i := 0; i < 15; i++ {
		x, _, _ = HasX(page, xpath)
		if !x {
			utils.Sleep(1)
		}
	}
	return x
}

// 这个方法存在bug 如果新页面没加载完，爬出异常了，就会存在新页面没关闭的情况
func WaitTimeOutPage(f func() *rod.Page, timeout time.Duration) *rod.Page {
	resultChan := make(chan *rod.Page, 1)
	go func() {
		page := f()
		resultChan <- page
	}()
	select {
	case p := <-resultChan:
		return p
	case <-time.After(timeout):
		panic(taxerr.NewWebStuckTitle(ComInfo.Cscsts))
	}
}

func MustHasXV10(page *rod.Page, xpath string) bool {
	for _, element := range MustElementsX10(page, xpath) {
		visible, _ := element.Visible()
		if visible {
			return true
		}
	}
	return false
}

// 输入文字 string 有的公司有 有的公司没有 工商
func InputElementXStrOrNo(p *rod.Page, xPath, inputVal string) {
	elementX := MustElementX(p, xPath)
	if elementX == nil {
		return
	}
	disabled := elementX.MustAttribute("disabled")
	if disabled != nil {
		return
	}
	readonly := elementX.MustAttribute("readonly")
	if readonly != nil {
		return
	}
	elementX.MustSelectAllText().MustInput(inputVal)
}

func CheckLogin(page *rod.Page) error {
	for i := 0; i < 30; i++ {
		if MustHasXV1(page, `//div[@class='leftMain']//div[contains(@class,'title')]`) {
			break
		}
		if MustHasXV1(page, `//div[@aria-label="密码修改"]`) {
			return taxerr.NewUserV3("税局强制要求修改密码", "请修改后重新发起")
		}
		if MustHasXV1(page, `//div[text()="账户中心"]`) {
			return taxerr.NewUserV3("登录后不能进入税局首页", "请检查后重新发起")
		}
		if MustHasXV1(page, `//div[@aria-label="用户协议"]//span[text()="确认"]`) {
			MustClick(page, `//div[@aria-label="用户协议"]//span[text()="确认"]`)
		}
		if MustHasXV1(page, `//div[contains(text(),"为提升纳税人办税体验")]/../..//span[text()="确 定"]`) {
			MustClick(page, `//div[contains(text(),"为提升纳税人办税体验")]/../..//span[text()="确 定"]`)
		}
		utils.Sleep(1)
	}
	if !MustHasXV1(page, `//div[@class='leftMain']//div[contains(@class,'title')]`) {
		return taxerr.NewSystemV3("税局登录异常", "请稍后重试")
	}
	i := 0
	for {
		name := MustText(page, `//div[@class='leftMain']//div[contains(@class,'title')]`)
		if name != "" {
			return nil
		}
		utils.Sleep(1)
		if i > 30 {
			return taxerr.NewSystemV3("税局登录异常", "请稍后重试")
		}
		i++
	}
}
