package service

import (
	"bytes"
	"context"
	"encoding/json"
	"fmt"
	"github.com/PuerkitoBio/goquery"
	v1 "github.com/go-nunu/nunu-layout-advanced/api/v1"
	"strings"
)

type ParserService interface {
	GetMessage(ctx context.Context, req []byte) (string, error)
	ParseAlert(html string) (message string, err error)
	GetRuleId(ctx context.Context, htmlBytes []byte) (string, error)
	ParseSDKOnlineHTMLTable(htmlContent string) ([]v1.SDKInfo, error)
	CheckSDKKeyStatus(htmlData string, sdkKeyToFind string) error
	GetRuleIdByColumnName(ctx context.Context, htmlBytes []byte, columnName string) (string, error)
}

func NewParserService(
	service *Service,
) ParserService {
	return &parserService{
		Service: service,
	}
}

type parserService struct {
	*Service
}

// 解析 alert 消息
func (s *parserService) ParseAlert(html string) (message string, err error) {
	doc, err := goquery.NewDocumentFromReader(strings.NewReader(html))
	if err != nil {
		return "", err
	}

	sel := doc.Find(".alert")
	if sel.Length() == 0 {
		// 没有 .alert 元素
		return "", nil
	}

	// 找到 .alert，继续提取
	t := strings.TrimSpace(sel.Find("h4").Text())
	full := strings.TrimSpace(sel.Text())
	full = strings.TrimPrefix(full, "×")
	full = strings.TrimSpace(full)
	m := strings.TrimSpace(strings.TrimPrefix(full, t))

	return m, nil
}

func (s *parserService) GetMessage(ctx context.Context, req []byte) (string, error) {
	type msg struct {
		Message string `json:"msg"` // 如果字段叫 msg，用 `json:"msg"`
	}
	var m msg
	if err := json.Unmarshal(req, &m); err != nil {
		return "", fmt.Errorf("解析 message 失败: %v", err)
	}
	if m.Message == "no affect row" {
		return "", fmt.Errorf("没有该条数据")
	}
	return m.Message, nil
}

func (s *parserService) GetRuleId(ctx context.Context, htmlBytes []byte) (string, error) {
	// 1. 把 []byte 包成 io.Reader
	reader := bytes.NewReader(htmlBytes)

	// 2. 用 goquery 解析
	doc, err := goquery.NewDocumentFromReader(reader)
	if err != nil {
		return "", err
	}

	// 方法一：按位置拿（第 2 个 tr、第 2 个 td）
	id := doc.
		Find("table.table tbody tr").Eq(1). // 跳过表头行，拿第一条数据
		Find("td").Eq(1).Text()             // 第 2 个 td
	return strings.TrimSpace(id), nil
}

func (s *parserService) GetRuleIdByColumnName(ctx context.Context, htmlBytes []byte, name string) (string, error) {
	// 1. 定义我们用来搜索的列所有可能的名称。
	possibleKeyNames := []string{"标签", "网关组名称"}

	// 2. 解析HTML。
	reader := bytes.NewReader(htmlBytes)
	doc, err := goquery.NewDocumentFromReader(reader)
	if err != nil {
		return "", fmt.Errorf("failed to parse html: %w", err)
	}

	// 3. 动态查找第一个匹配的“关键字列”的索引。
	headerRow := doc.Find("table.table tbody tr:first-child")
	if headerRow.Length() == 0 {
		return "", nil
	}

	keyColumnIndex := -1
	// 我们使用 EachWithBreak，一旦找到有效的列就立即停止搜索，提高效率。
	headerRow.Find("th").EachWithBreak(func(index int, th *goquery.Selection) bool {
		headerText := strings.TrimSpace(th.Text())
		// 检查当前的表头文本是否匹配我们预设的任何一个可能名称。
		for _, possibleName := range possibleKeyNames {
			if headerText == possibleName {
				keyColumnIndex = index
				return false // 找到了，立即停止遍历表头(th)。
			}
		}
		return true // 没找到，继续遍历下一个表头(th)。
	})

	// 4. 检查我们是否找到了任何一个可能的关键字列。
	if keyColumnIndex == -1 {
		return "", fmt.Errorf("找不到任何一个指定的关键字列: %v", possibleKeyNames)
	}

	// 5. 遍历数据行以查找匹配的行。
	var foundID string
	var found bool
	doc.Find("table.table tbody tr").Slice(1, goquery.ToEnd).EachWithBreak(func(i int, row *goquery.Selection) bool {
		// 使用动态找到的索引来获取正确的单元格。
		keyCell := row.Find("td").Eq(keyColumnIndex)
		keyText := strings.TrimSpace(keyCell.Text())

		if keyText == name {
			// 如果关键字匹配，就从固定位置（第二个<td>，索引为1）获取值。
			idCell := row.Find("td").Eq(1)
			foundID = strings.TrimSpace(idCell.Text())
			found = true
			return false // 找到了目标行，停止循环。
		}
		return true // 不是目标行，继续。
	})

	// 6. 返回结果，如果未找到则返回错误。
	if !found {
		return "", fmt.Errorf("找不到关键字列值为 '%s' 的行", name)
	}

	return foundID, nil
}

// 解析 Sdk在线情况 表格
func (s *parserService) ParseSDKOnlineHTMLTable(htmlContent string) ([]v1.SDKInfo, error) {
	// 创建goquery文档
	doc, err := goquery.NewDocumentFromReader(strings.NewReader(htmlContent))
	if err != nil {
		return nil, fmt.Errorf("解析HTML失败: %v", err)
	}

	var sdkInfos []v1.SDKInfo

	// 查找表格并解析数据行
	doc.Find("table.table.table-hover tbody tr").Each(func(i int, s *goquery.Selection) {
		// 跳过表头行（如果有的话）
		if s.Find("th").Length() > 0 {
			return
		}

		var info v1.SDKInfo

		// 解析每一列的数据
		s.Find("td").Each(func(j int, td *goquery.Selection) {
			text := strings.TrimSpace(td.Text())

			// 根据列的位置分配到对应字段（跳过第一列的复选框）
			switch j {
			case 1: // 规则ID
				info.RuleID = text
			case 2: // 客户端IP
				info.ClientIP = text
			//case 3: // 网关IP
			//	info.GatewayIP = text
			case 4: // SDK-UUID
				info.SDKUUID = text
			case 5: // 会话ID
				info.SessionID = text
			case 6: // SDK类型
				info.SDKType = text
			//case 7: // SDK版本
			//	info.SDKVersion = text
			case 8: // 系统
				info.System = text
			case 9: // 附加信息
				// 对于附加信息列，提取JSON内容
				info.ExtraInfo = extractJSONFromExtraInfo(text)
			}
		})

		// 只有当规则ID不为空时才添加记录
		if info.RuleID != "" {
			sdkInfos = append(sdkInfos, info)
		}
	})

	return sdkInfos, nil
}

// extractJSONFromExtraInfo 从附加信息字符串中提取JSON内容
func extractJSONFromExtraInfo(text string) string {
	text = strings.TrimSpace(text)

	// 尝试直接解析
	if result := tryParseJSON(text); result != "" {
		return result
	}

	// 尝试解析JSON字符串（去掉外层引号）
	if strings.HasPrefix(text, `"`) && strings.HasSuffix(text, `"`) {
		var jsonContent string
		if json.Unmarshal([]byte(text), &jsonContent) == nil {
			if result := tryParseJSON(jsonContent); result != "" {
				return result
			}
		}
	}

	// 从复杂文本中提取JSON
	return extractFromComplexText(text)
}

// 统一的JSON解析和格式化函数
func tryParseJSON(text string) string {
	var temp interface{}
	if json.Unmarshal([]byte(text), &temp) == nil {
		if formatted, err := json.Marshal(temp); err == nil {
			return string(formatted)
		}
		return text
	}
	return ""
}

// 简化的复杂文本JSON提取
func extractFromComplexText(text string) string {
	// 找到最后一个完整的JSON对象
	for end := strings.LastIndex(text, "}"); end != -1; end = strings.LastIndex(text[:end], "}") {
		// 向前查找匹配的开始大括号
		braceCount := 1
		for start := end - 1; start >= 0; start-- {
			switch text[start] {
			case '}':
				braceCount++
			case '{':
				braceCount--
				if braceCount == 0 {
					candidate := text[start : end+1]
					if result := tryParseJSON(candidate); result != "" {
						return result
					}
					break // 跳出内层循环，继续寻找下一个'}'
				}
			}
		}
	}
	return "查看"
}

// CheckSDKKeyStatus 检查SDKKey是否存在且过期
func (s *parserService) CheckSDKKeyStatus(htmlData string, sdkKeyToFind string) error {
	// 使用 strings.NewReader 将字符串转换为一个 io.Reader，这是 go-query 所需的输入格式。
	// go-query 会加载并解析这个HTML，返回一个可供查询的文档对象(doc)。
	doc, err := goquery.NewDocumentFromReader(strings.NewReader(htmlData))
	if err != nil {
		// 如果go-query无法加载或解析HTML，则返回错误。
		return fmt.Errorf("无法解析HTML: %w", err)
	}

	// 定义两个变量用于在循环结束后判断状态
	var keyFound bool = false // 标记是否找到了Key
	var resultErr error = nil // 用于存储找到Key后的最终错误状态（如果是过期的话）

	// 使用go-query的选择器找到class为 "table" 的表格(table.table)的主体(tbody)中的所有行(tr)。
	// .EachWithBreak 方法会遍历每一行，并允许我们在满足特定条件时提前中断循环。
	doc.Find("table.table tbody tr").EachWithBreak(func(i int, row *goquery.Selection) bool {
		// 在当前行(row)中，查找第7个单元格(td:nth-of-type(7))，这是“SDK启动KEY”所在的列。
		// Key本身被隐藏在一个 <pre> 标签内，我们直接定位它。
		keyCell := row.Find("td:nth-of-type(7) pre")
		fullKeyText := keyCell.Text() // 获取 <pre> 标签内的所有文本内容。

		// 原始文本的格式是固定的，我们需要从中提取出真正的KEY。
		// 格式: "原始内容：... >>> SDK启动KEY如下，复制后启动SDK使用 <<< [THE_ACTUAL_KEY]"
		parts := strings.Split(fullKeyText, ">>> SDK启动KEY如下，复制后启动SDK使用 <<<")
		if len(parts) < 2 {
			// 如果当前行不符合这个格式，跳到下一行处理。
			return true // `true` 在 EachWithBreak 中表示继续循环
		}

		// 提取并清理KEY字符串，去掉它前后的所有空格和换行符。
		extractedKey := strings.TrimSpace(parts[1])
		// 检查从页面提取出的KEY是否与我们要找的KEY相匹配。
		if extractedKey == sdkKeyToFind {
			keyFound = true // 首先，标记我们已经找到了Key
			// 接着，在同一行中查找过期状态。
			// 第11个单元格(td:nth-of-type(11))包含“过期时间”信息。
			expirationCell := row.Find("td:nth-of-type(11)")
			expirationText := expirationCell.Text() // 获取该单元格的文本内容。

			// 检查过期时间文本中是否包含`(已过期)`
			if strings.Contains(expirationText, "(已过期)") {
				// 如果包含，我们将错误信息赋值给外部的 resultErr 变量
				resultErr = fmt.Errorf("该KEY已过期")
			}
			// 注意：即使未过期，resultErr 仍然是 nil，这正是我们想要的结果。

			// 我们已经找到了目标并处理完毕，没有必要再检查剩下的行了。
			return false // `false` 在 EachWithBreak 中表示中断循环
		}

		// 如果当前行的KEY不匹配，继续下一行的查找。
		return true
	})

	// --- 循环结束后的最终判断 ---

	// 如果 keyFound 标志位仍然是 false，说明遍历了所有行都没有找到匹配的Key。
	if !keyFound {
		return fmt.Errorf("未找到指定的Key")
	}

	// 如果 keyFound 是 true，说明找到了Key。
	// 此时，我们返回在循环中确定的 resultErr。
	// - 如果Key未过期，resultErr 就是它初始的 nil 值。
	// - 如果Key已过期，resultErr 就是我们在循环里设置的那个 error。
	return resultErr
}
