package common

import (
	"bufio"
	"slices"
	"strings"
	"testing"
)

func newStringScanner(s string) *bufio.Scanner {
	return bufio.NewScanner(strings.NewReader(s))
}

func TestAssociatedPRScanner(t *testing.T) {
	testTable := []struct {
		name  string
		input string
		prs   []BasicPR
		desc  string
	}{
		{
			"No PRs",
			"",
			[]BasicPR{},
			"",
		},
		{
			"Single PRs",
			"Some header of the issue\n\nFollowed by some description\n\nPR: test/foo#4\n",
			[]BasicPR{{Org: "test", Repo: "foo", Num: 4}},
			"Some header of the issue\n\nFollowed by some description",
		},
		{
			"Multiple PRs",
			"Some header of the issue\n\nFollowed by some description\nPR: test/foo#4\n\nPR: test/goo#5\n",
			[]BasicPR{
				{Org: "test", Repo: "foo", Num: 4},
				{Org: "test", Repo: "goo", Num: 5},
			},
			"Some header of the issue\n\nFollowed by some description",
		},
		{
			"Multiple PRs with whitespace",
			"Some header of the issue\n\n\tPR: test/goo#5\n\n    Followed by some description\n \t  PR: test/foo#4\n",
			[]BasicPR{
				{Org: "test", Repo: "foo", Num: 4},
				{Org: "test", Repo: "goo", Num: 5},
			},
			"Some header of the issue\n\n\n    Followed by some description",
		},
		{
			"Multiple PRs with missing names and other special cases to ignore",
			"Some header of the issue\n\n\n\t PR: foobar#5 \n\t PR: rd/goo5 \n\t PR: test/#5 \n" +
				"\t PR: /goo#5 \n\t PR: test/goo# \n\t PR: test / goo # 10 \n\tPR: test/gool# 10 \n" +
				"\t PR: test/goo#5 \n\t\n       Followed by some description\n\t PR: test/foo#4 \n\t\n\n",
			[]BasicPR{
				{
					Org:  "test",
					Repo: "foo",
					Num:  4,
				},
				{
					Org:  "test",
					Repo: "goo",
					Num:  5,
				},
			},
			"Some header of the issue\n\n\n\t PR: foobar#5 \n\t PR: rd/goo5 \n\t PR: test/#5 \n" +
				"\t PR: /goo#5 \n\t PR: test/goo# \n\t PR: test / goo # 10 \n\tPR: test/gool# 10 \n" +
				"\t\n       Followed by some description",
		},
	}

	for _, test := range testTable {
		t.Run(test.name, func(t *testing.T) {
			desc, prs := ExtractDescriptionAndPRs(newStringScanner(test.input))
			if len(prs) != len(test.prs) {
				t.Error("Unexpected length:", len(prs), "expected:", len(test.prs))
				return
			}

			for _, p := range test.prs {
				if !slices.Contains(prs, p) {
					t.Error("missing expected PR", p)
				}
			}

			if desc != test.desc {
				t.Error("Desc output", len(desc), "!=", len(test.desc), ":", desc)
			}
		})
	}
}

func TestAppendingPRsToDescription(t *testing.T) {
	testTable := []struct {
		name   string
		desc   string
		PRs    []BasicPR
		output string
	}{
		{
			"Append single PR to end of description",
			"something",
			[]BasicPR{
				{Org: "a", Repo: "b", Num: 100},
			},
			"something\n\nPR: a/b#100",
		},
		{
			"Append multiple PR to end of description",
			"something",
			[]BasicPR{
				{Org: "a1", Repo: "b", Num: 100},
				{Org: "a1", Repo: "c", Num: 100},
				{Org: "a1", Repo: "c", Num: 101},
				{Org: "b", Repo: "b", Num: 100},
				{Org: "c", Repo: "b", Num: 100},
			},
			"something\n\nPR: a1/b#100\nPR: a1/c#100\nPR: a1/c#101\nPR: b/b#100\nPR: c/b#100",
		},
		{
			"Append multiple sorted PR to end of description and remove dups",
			"something",
			[]BasicPR{
				{Org: "a1", Repo: "c", Num: 101},
				{Org: "a1", Repo: "c", Num: 100},
				{Org: "c", Repo: "b", Num: 100},
				{Org: "b", Repo: "b", Num: 100},
				{Org: "a1", Repo: "c", Num: 101},
				{Org: "a1", Repo: "c", Num: 101},
				{Org: "a1", Repo: "b", Num: 100},
			},
			"something\n\nPR: a1/b#100\nPR: a1/c#100\nPR: a1/c#101\nPR: b/b#100\nPR: c/b#100",
		},
	}

	for _, test := range testTable {
		t.Run(test.name, func(t *testing.T) {
			d := AppendPRsToDescription(test.desc, test.PRs)
			if d != test.output {
				t.Error(len(d), "vs", len(test.output))
				t.Error("unpected output", d)
			}
		})
	}
}
