package run

import (
	"bufio"
	"encoding/json"
	"encoding/xml"
	"errors"
	"strings"
)

// exec_parsers.go 把常见测试输出（go test -json / JUnit XML / vitest --reporter=json）
// 归一化为 TestSummary。所有解析器都是防御性的：永不 panic；无法解析时返回 error，
// 由 RunTests 折成 ParseError（始终保留原始输出）。

// parseTestOutput 按 format 分派；format=="" 或 "auto" 时按内容嗅探。
func parseTestOutput(format, stdout, stderr string) (TestSummary, error) {
	combined := stdout
	if strings.TrimSpace(combined) == "" {
		combined = stderr
	}
	switch strings.ToLower(strings.TrimSpace(format)) {
	case "gotest":
		return parseGoTestJSON([]byte(combined))
	case "junit":
		return parseJUnitXML([]byte(combined))
	case "vitest":
		return parseVitestJSON([]byte(combined))
	case "", "auto":
		return sniffAndParse(combined)
	default:
		return TestSummary{}, errors.New("unknown test format: " + format)
	}
}

// sniffAndParse 按首个非空 token 推断框架：JSON 行 + Action 字段 → gotest；
// JSON 对象含 numPassedTests → vitest；'<testsuite' → junit。
func sniffAndParse(s string) (TestSummary, error) {
	trimmed := strings.TrimSpace(s)
	if trimmed == "" {
		return TestSummary{}, errors.New("empty test output")
	}
	switch trimmed[0] {
	case '<':
		if strings.Contains(trimmed, "<testsuite") {
			return parseJUnitXML([]byte(s))
		}
	case '{', '[':
		// vitest 是单个 JSON 对象（含 numPassedTests）；go test -json 是 JSONL（每行含 Action）。
		if strings.Contains(trimmed, "numPassedTests") || strings.Contains(trimmed, "testResults") {
			return parseVitestJSON([]byte(s))
		}
		if strings.Contains(trimmed, "\"Action\"") {
			return parseGoTestJSON([]byte(s))
		}
		// 退而求其次：单对象当 vitest，多行当 gotest。
		if strings.HasPrefix(trimmed, "{\"Time\"") || strings.HasPrefix(trimmed, "{\"Action\"") {
			return parseGoTestJSON([]byte(s))
		}
		return parseVitestJSON([]byte(s))
	}
	return TestSummary{}, errors.New("could not sniff test output format")
}

// ===== go test -json =====

type goTestEvent struct {
	Action  string  `json:"Action"`
	Package string  `json:"Package"`
	Test    string  `json:"Test"`
	Output  string  `json:"Output"`
	Elapsed float64 `json:"Elapsed"`
}

// parseGoTestJSON 解析 `go test -json` 的 JSONL 流。只统计具体 test（Test 非空）的
// pass/fail/skip；包级 action 不计入用例数。失败用例汇集其 output 作为 message。
func parseGoTestJSON(data []byte) (TestSummary, error) {
	if len(strings.TrimSpace(string(data))) == 0 {
		return TestSummary{}, errors.New("empty go test output")
	}
	sum := TestSummary{Framework: "gotest"}
	type key struct{ pkg, test string }
	outputs := map[key][]string{}
	var totalElapsed float64
	var anyEvent bool

	sc := bufio.NewScanner(strings.NewReader(string(data)))
	sc.Buffer(make([]byte, 0, 64*1024), 4*1024*1024)
	for sc.Scan() {
		line := strings.TrimSpace(sc.Text())
		if line == "" || line[0] != '{' {
			continue
		}
		var ev goTestEvent
		if err := json.Unmarshal([]byte(line), &ev); err != nil {
			continue // 非 JSON 行（如裸 build 错误）跳过，保持防御性。
		}
		anyEvent = true
		if ev.Test == "" {
			// 包级事件：累计耗时。
			if ev.Action == "pass" || ev.Action == "fail" {
				totalElapsed += ev.Elapsed
			}
			continue
		}
		k := key{ev.Package, ev.Test}
		switch ev.Action {
		case "output":
			outputs[k] = append(outputs[k], ev.Output)
		case "pass":
			sum.Passed++
		case "skip":
			sum.Skipped++
		case "fail":
			sum.Failed++
			sum.Failures = append(sum.Failures, TestFailure{
				Name:    ev.Test,
				Package: ev.Package,
				Message: strings.TrimSpace(strings.Join(outputs[k], "")),
			})
		}
	}
	if err := sc.Err(); err != nil {
		return sum, err
	}
	if !anyEvent {
		return sum, errors.New("no go test -json events found")
	}
	sum.Total = sum.Passed + sum.Failed + sum.Skipped
	sum.DurationMs = int64(totalElapsed * 1000)
	return sum, nil
}

// ===== JUnit XML =====

type junitTestSuites struct {
	XMLName xml.Name         `xml:"testsuites"`
	Suites  []junitTestSuite `xml:"testsuite"`
}

type junitTestSuite struct {
	XMLName   xml.Name        `xml:"testsuite"`
	Name      string          `xml:"name,attr"`
	Tests     int             `xml:"tests,attr"`
	Failures  int             `xml:"failures,attr"`
	Errors    int             `xml:"errors,attr"`
	Skipped   int             `xml:"skipped,attr"`
	Time      float64         `xml:"time,attr"`
	TestCases []junitTestCase `xml:"testcase"`
	// 嵌套 testsuite（部分工具输出 testsuites>testsuite>testsuite）。
	Nested []junitTestSuite `xml:"testsuite"`
}

type junitTestCase struct {
	Name      string        `xml:"name,attr"`
	ClassName string        `xml:"classname,attr"`
	Time      float64       `xml:"time,attr"`
	Failure   *junitDetail  `xml:"failure"`
	Error     *junitDetail  `xml:"error"`
	Skipped   *junitSkipped `xml:"skipped"`
}

type junitDetail struct {
	Message string `xml:"message,attr"`
	Body    string `xml:",chardata"`
}

type junitSkipped struct {
	Message string `xml:"message,attr"`
}

// parseJUnitXML 解析 JUnit XML（顶层 testsuites 或裸 testsuite）。按 testcase 逐个
// 统计，failure/error → failed，skipped → skipped，其余 → passed。
func parseJUnitXML(data []byte) (TestSummary, error) {
	if strings.TrimSpace(string(data)) == "" {
		return TestSummary{}, errors.New("empty junit output")
	}
	sum := TestSummary{Framework: "junit"}

	var suites []junitTestSuite
	var root junitTestSuites
	if err := xml.Unmarshal(data, &root); err == nil && len(root.Suites) > 0 {
		suites = root.Suites
	} else {
		var single junitTestSuite
		if err := xml.Unmarshal(data, &single); err != nil {
			return sum, err
		}
		suites = []junitTestSuite{single}
	}

	var totalTime float64
	var walk func(s junitTestSuite)
	walk = func(s junitTestSuite) {
		totalTime += s.Time
		for _, tc := range s.TestCases {
			sum.Total++
			pkg := tc.ClassName
			switch {
			case tc.Failure != nil || tc.Error != nil:
				sum.Failed++
				d := tc.Failure
				if d == nil {
					d = tc.Error
				}
				msg := strings.TrimSpace(d.Message)
				if msg == "" {
					msg = strings.TrimSpace(d.Body)
				}
				sum.Failures = append(sum.Failures, TestFailure{Name: tc.Name, Package: pkg, Message: msg})
			case tc.Skipped != nil:
				sum.Skipped++
			default:
				sum.Passed++
			}
		}
		for _, n := range s.Nested {
			walk(n)
		}
	}
	for _, s := range suites {
		walk(s)
	}
	if sum.Total == 0 {
		return sum, errors.New("no junit testcases found")
	}
	sum.DurationMs = int64(totalTime * 1000)
	return sum, nil
}

// ===== vitest --reporter=json =====

type vitestReport struct {
	NumTotalTests   *int               `json:"numTotalTests"`
	NumPassedTests  *int               `json:"numPassedTests"`
	NumFailedTests  *int               `json:"numFailedTests"`
	NumPendingTests *int               `json:"numPendingTests"`
	StartTime       *int64             `json:"startTime"`
	TestResults     []vitestFileResult `json:"testResults"`
}

type vitestFileResult struct {
	Name             string            `json:"name"`
	AssertionResults []vitestAssertion `json:"assertionResults"`
}

type vitestAssertion struct {
	Title           string   `json:"title"`
	FullName        string   `json:"fullName"`
	Status          string   `json:"status"` // passed|failed|skipped|pending
	FailureMessages []string `json:"failureMessages"`
	Duration        float64  `json:"duration"`
}

// parseVitestJSON 解析 vitest（jest 兼容）的 JSON 报告。优先使用顶层计数；
// 同时遍历 testResults 收集失败用例名与首条 failureMessage。
func parseVitestJSON(data []byte) (TestSummary, error) {
	if strings.TrimSpace(string(data)) == "" {
		return TestSummary{}, errors.New("empty vitest output")
	}
	var rep vitestReport
	if err := json.Unmarshal(data, &rep); err != nil {
		return TestSummary{}, err
	}
	sum := TestSummary{Framework: "vitest"}
	var durMs float64
	for _, fr := range rep.TestResults {
		for _, a := range fr.AssertionResults {
			switch a.Status {
			case "failed":
				sum.Failed++
				msg := ""
				if len(a.FailureMessages) > 0 {
					msg = strings.TrimSpace(a.FailureMessages[0])
				}
				name := a.FullName
				if name == "" {
					name = a.Title
				}
				sum.Failures = append(sum.Failures, TestFailure{Name: name, Package: fr.Name, Message: msg})
			case "skipped", "pending", "todo":
				sum.Skipped++
			case "passed":
				sum.Passed++
			}
			durMs += a.Duration
		}
	}
	// 顶层计数优先（更可靠）；没有就用遍历得到的计数。
	if rep.NumPassedTests != nil {
		sum.Passed = *rep.NumPassedTests
	}
	if rep.NumFailedTests != nil {
		sum.Failed = *rep.NumFailedTests
	}
	if rep.NumPendingTests != nil {
		sum.Skipped = *rep.NumPendingTests
	}
	if rep.NumTotalTests != nil {
		sum.Total = *rep.NumTotalTests
	} else {
		sum.Total = sum.Passed + sum.Failed + sum.Skipped
	}
	if sum.Total == 0 && len(rep.TestResults) == 0 && rep.NumTotalTests == nil {
		return sum, errors.New("not a vitest report")
	}
	sum.DurationMs = int64(durMs)
	return sum, nil
}
