package oscmd

import (
	"regexp"

	"sigs.k8s.io/descheduler/tools/junitreport/pkg/api"
	"sigs.k8s.io/descheduler/tools/junitreport/pkg/parser/stack"
)

func newTestDataParser() stack.TestDataParser {
	return &testDataParser{
		// testStartPattern matches the test beginning bookend
		testStartPattern: regexp.MustCompile(`=== BEGIN TEST CASE ===`),

		// testDeclarationPattern matches the test declaration line, the full match is the name of the test being run
		// as we're starting the test declaration line with a Unix path, misprinting a leading newline will cause this
		// pattern to match the entire misprinted line
		testDeclarationPattern: regexp.MustCompile(`.+:[0-9]+: executing '.+' expecting .+`),

		// testConclusionPattern matches the test conclusion line, and contains the following submatches:
		//  - 1: test result
		//  - 2: test duration
		//  - 3: test name
		//  - 5: test result message
		// In order to make this regex sane, we *require* a end-line anchor and therefore make us a little more fragile
		// in the face of broken input
		testConclusionPattern: regexp.MustCompile(`(SUCCESS|FAILURE) after ([0-9]+\.[0-9]+s): (.+:[0-9]+: executing '.*' expecting .*?)(: (.*))?$`),

		// testEndPattern matches the test end bookend
		testEndPattern: regexp.MustCompile(`=== END TEST CASE ===`),
	}
}

type testDataParser struct {
	testStartPattern       *regexp.Regexp
	testDeclarationPattern *regexp.Regexp
	testConclusionPattern  *regexp.Regexp
	testEndPattern         *regexp.Regexp
}

// MarksBeginning determines if the line marks the beginning of a test case
func (p *testDataParser) MarksBeginning(line string) bool {
	return p.testStartPattern.MatchString(line)
}

// ExtractName extracts the name of the test case from test output lines
func (p *testDataParser) ExtractName(line string) (string, bool) {
	// The test declaration pattern is technically a subset of the test conclusion pattern, and will therefore
	// match anything the test declaration pattern matches. The match from the test conclusion pattern is more
	// correct, if it exists, so we check the conclusion pattern first and return if we have a name candidate.
	if matches := p.testConclusionPattern.FindStringSubmatch(line); len(matches) > 3 && len(matches[3]) > 0 {
		return matches[3], true
	}

	if matches := p.testDeclarationPattern.FindStringSubmatch(line); len(matches) > 0 && len(matches[0]) > 0 {
		return matches[0], true
	}

	return "", false
}

// ExtractResult extracts the test result from a test output line
func (p *testDataParser) ExtractResult(line string) (api.TestResult, bool) {
	if matches := p.testConclusionPattern.FindStringSubmatch(line); len(matches) > 1 && len(matches[1]) > 0 {
		switch matches[1] {
		case "SUCCESS":
			return api.TestResultPass, true
		case "FAILURE":
			return api.TestResultFail, true
		}
	}

	return "", false
}

// ExtractDuration extracts the test duration from a test output line
func (p *testDataParser) ExtractDuration(line string) (string, bool) {
	if matches := p.testConclusionPattern.FindStringSubmatch(line); len(matches) > 2 && len(matches[2]) > 0 {
		return matches[2], true
	}

	return "", false
}

// ExtractMessage extracts a message (e.g. for signalling why a failure or skip occurred) from a test output line
func (p *testDataParser) ExtractMessage(line string) (string, bool) {
	if matches := p.testConclusionPattern.FindStringSubmatch(line); len(matches) > 5 && len(matches[5]) > 0 {
		return matches[5], true
	}

	return "", false
}

// MarksCompletion determines if the line marks the completion of a test case
func (p *testDataParser) MarksCompletion(line string) bool {
	return p.testEndPattern.MatchString(line)
}

func newTestSuiteDataParser() stack.TestSuiteDataParser {
	return &testSuiteDataParser{
		// suiteDeclarationPattern matches the suite declaration line and has the following submatches:
		//  - 1: suite name
		suiteDeclarationPattern: regexp.MustCompile(`=== BEGIN TEST SUITE (.*) ===`),

		// suiteConclusionPattern matches the suite conclusion line
		suiteConclusionPattern: regexp.MustCompile(`=== END TEST SUITE ===`),
	}
}

type testSuiteDataParser struct {
	suiteDeclarationPattern *regexp.Regexp
	suiteConclusionPattern  *regexp.Regexp
}

// MarksBeginning determines if the line marks the beginning of a test suite
func (p *testSuiteDataParser) MarksBeginning(line string) bool {
	return p.suiteDeclarationPattern.MatchString(line)
}

// ExtractName extracts the name of the test suite from a test output line
func (p *testSuiteDataParser) ExtractName(line string) (string, bool) {
	if matches := p.suiteDeclarationPattern.FindStringSubmatch(line); len(matches) > 1 && len(matches[1]) > 0 {
		return matches[1], true
	}

	return "", false
}

// ExtractProperties extracts any metadata properties of the test suite from a test output line
func (p *testSuiteDataParser) ExtractProperties(line string) (map[string]string, bool) {
	// `os::cmd` suites cannot expose properties
	return map[string]string{}, false
}

// MarksCompletion determines if the line marks the completion of a test suite
func (p *testSuiteDataParser) MarksCompletion(line string) bool {
	return p.suiteConclusionPattern.MatchString(line)
}
