package warmlint

import (
	"strings"
	"testing"
)

func hasIssue(res ScoreResult, code string) bool {
	for _, i := range res.Issues {
		if i.Code == code {
			return true
		}
	}
	return false
}

func TestScoreCleanCopyIsUnpenalized(t *testing.T) {
	res := Score(
		"Quick question about your hiring process",
		"<p>Hi Ada, saw you are growing the support team. Worth a chat?</p>",
		"Hi Ada, saw you are growing the support team. Worth a chat?",
	)
	if res.Score != 100 {
		t.Errorf("clean copy scored %d with %+v, want 100", res.Score, res.Issues)
	}
}

func TestScoreImageHeavyBody(t *testing.T) {
	// Almost all image, no readable text: filters cannot read it, and that is
	// treated as evasion rather than as a design choice.
	res := Score("Hello", `<img src="a.png"><img src="b.png">`, "")
	if !hasIssue(res, "image_heavy") {
		t.Errorf("image-only body not flagged: %+v", res.Issues)
	}

	// Plenty of text, a few images: fine.
	body := strings.Repeat("A real sentence about the recipient's work. ", 20)
	res = Score("Hello", "<p>"+body+`</p><img src="a.png">`, body)
	if hasIssue(res, "image_heavy") || hasIssue(res, "many_images") {
		t.Errorf("ordinary copy with one image was flagged: %+v", res.Issues)
	}

	// Many images alongside text is a softer warning.
	res = Score("Hello", "<p>"+body+`</p>`+strings.Repeat(`<img src="x.png">`, 6), body)
	if !hasIssue(res, "many_images") {
		t.Errorf("six images not flagged: %+v", res.Issues)
	}
	if hasIssue(res, "image_heavy") {
		t.Errorf("text-rich body wrongly flagged as image-only: %+v", res.Issues)
	}
}

func TestScoreWithAttachments(t *testing.T) {
	subject, html, plain := "Following up", "<p>Hi Ada, following up on my note.</p>", "Hi Ada, following up on my note."

	base := ScoreWithAttachments(subject, html, plain, 0)
	if hasIssue(base, "has_attachments") {
		t.Errorf("no attachments should not flag: %+v", base.Issues)
	}

	withAtt := ScoreWithAttachments(subject, html, plain, 2)
	if !hasIssue(withAtt, "has_attachments") {
		t.Errorf("attachments not flagged: %+v", withAtt.Issues)
	}
	if withAtt.Score >= base.Score {
		t.Errorf("attachment score %d not below the clean %d", withAtt.Score, base.Score)
	}
}

func TestScoreNeverGoesNegative(t *testing.T) {
	// Every heuristic at once: the floor is 0, not a negative number the UI
	// would have to special-case.
	res := ScoreWithAttachments(
		"FREE CASH PRIZE GUARANTEED!!!",
		`<img src="a.png"><img src="b.png"><img src="c.png"><img src="d.png">`,
		"",
		5,
	)
	if res.Score < 0 {
		t.Errorf("score = %d, want a floor of 0", res.Score)
	}
	if len(res.Issues) == 0 {
		t.Error("obviously spammy copy produced no issues")
	}
}

func TestScoreCountsLinksInHTMLAnchors(t *testing.T) {
	// A normal HTML email carries its URLs in href attributes. Stripping tags
	// throws those away, so counting the text alone reported zero links and the
	// cap never fired for the case it exists for.
	body := strings.Repeat("A real sentence about the recipient's work. ", 10)
	html := "<p>" + body + "</p><p>" +
		strings.Repeat(`<a href="https://example.com/x">see this</a> `, 6) + "</p>"

	res := Score("Quick question", html, "")
	if !hasIssue(res, "too_many_links") {
		t.Errorf("six anchors in an HTML body were not counted: %+v", res.Issues)
	}

	// The editor stores a plain-text body derived from the HTML, which also
	// drops the hrefs. The score must not depend on which one is present.
	res = Score("Quick question", html, body)
	if !hasIssue(res, "too_many_links") {
		t.Errorf("six anchors alongside a link-free plain body were not counted: %+v", res.Issues)
	}
}

func TestScoreDoesNotDoubleCountSelfLinkingAnchors(t *testing.T) {
	// A URL used as its own anchor text appears in both the href and the text.
	// Counting both would flag three links as six.
	body := strings.Repeat("A real sentence about the recipient's work. ", 10)
	html := "<p>" + body + "</p><p>" +
		`<a href="https://a.com/1">https://a.com/1</a> ` +
		`<a href="https://b.com/2">https://b.com/2</a> ` +
		`<a href="https://c.com/3">https://c.com/3</a>` + "</p>"
	plain := body + " https://a.com/1 https://b.com/2 https://c.com/3"

	res := Score("Quick question", html, plain)
	if hasIssue(res, "too_many_links") {
		t.Errorf("three self-linking anchors were counted as more: %+v", res.Issues)
	}
}

func TestScoreStillCountsPlainTextURLs(t *testing.T) {
	body := strings.Repeat("A real sentence about the recipient's work. ", 10)
	plain := body + " https://a.com/1 https://b.com/2 https://c.com/3 https://d.com/4 https://e.com/5"

	res := Score("Quick question", "", plain)
	if !hasIssue(res, "too_many_links") {
		t.Errorf("five bare URLs in a plain body were not counted: %+v", res.Issues)
	}
}

func TestScoreCountsAnchorsAndBareURLsTogether(t *testing.T) {
	// Labeled anchors and bare URLs are different destinations. Counting only
	// the larger of the two sets let six distinct links score a clean 100.
	body := strings.Repeat("A real sentence about the recipient's work. ", 10)
	html := "<p>" + body + "</p><p>" +
		`<a href="https://a.com/1">one</a> <a href="https://b.com/2">two</a> <a href="https://c.com/3">three</a> ` +
		"https://d.com/4 https://e.com/5 https://f.com/6</p>"
	plain := body + " one two three https://d.com/4 https://e.com/5 https://f.com/6"

	res := Score("Quick question", html, plain)
	if !hasIssue(res, "too_many_links") {
		t.Errorf("three anchors plus three bare URLs were not counted as six: %+v", res.Issues)
	}
}

func TestScoreIgnoresTrailingPunctuationWhenMatchingAnchors(t *testing.T) {
	// A URL that ends a sentence in the plain text is the same link as the
	// anchor's destination, so it must not count a second time.
	body := strings.Repeat("A real sentence about the recipient's work. ", 10)
	html := "<p>" + body + "</p><p>" +
		`<a href="https://a.com/1">https://a.com/1</a>, <a href="https://b.com/2">https://b.com/2</a>.` + "</p>"
	plain := body + " https://a.com/1, https://b.com/2."

	res := Score("Quick question", html, plain)
	if hasIssue(res, "too_many_links") {
		t.Errorf("two self-linking anchors counted as more than two: %+v", res.Issues)
	}
}
