package common

import (
	"bufio"
	"errors"
	"fmt"
	"io"
	"regexp"
	"slices"
	"strconv"
	"strings"
)

const PrPattern = "PR: %s/%s#%d"

type BasicPR struct {
	Org, Repo string
	Num       int64
}

var validOrgAndRepoRx *regexp.Regexp = regexp.MustCompile("^[A-Za-z0-9_-]+$")

func parsePrLine(line string) (BasicPR, error) {
	var ret BasicPR
	trimmedLine := strings.TrimSpace(line)

	// min size > 9 -> must fit all parameters in th PrPattern with at least one item per parameter
	if len(trimmedLine) < 9 || trimmedLine[0:4] != "PR: " {
		return ret, errors.New("Line too short")
	}

	trimmedLine = trimmedLine[4:]
	org := strings.SplitN(trimmedLine, "/", 2)
	ret.Org = org[0]
	if len(org) != 2 {
		return ret, errors.New("missing / separator")
	}

	repo := strings.SplitN(org[1], "#", 2)
	ret.Repo = repo[0]
	if len(repo) != 2 {
		return ret, errors.New("Missing # separator")
	}

	// Gitea requires that each org and repo be [A-Za-z0-9_-]+
	var err error
	if ret.Num, err = strconv.ParseInt(repo[1], 10, 64); err != nil {
		return ret, errors.New("Invalid number")
	}

	if !validOrgAndRepoRx.MatchString(repo[0]) || !validOrgAndRepoRx.MatchString(org[0]) {
		return ret, errors.New("Invalid repo or org character set")
	}

	return ret, nil
}

func ExtractDescriptionAndPRs(data *bufio.Scanner) (string, []BasicPR) {
	prs := make([]BasicPR, 0, 1)
	var desc strings.Builder

	for data.Scan() {
		line := data.Text()

		pr, err := parsePrLine(line)
		if err != nil {
			desc.WriteString(line)
			desc.WriteByte('\n')
		} else {
			prs = append(prs, pr)
		}
	}

	return strings.TrimSpace(desc.String()), prs
}

func prToLine(writer io.Writer, pr BasicPR) {
	writer.Write([]byte("\n"))
	fmt.Fprintf(writer, PrPattern, pr.Org, pr.Repo, pr.Num)
}

// returns:
//    <0 for a<b
//    >0 for a>b
//    =0 when equal
func compareBasicPRs(a BasicPR, b BasicPR) int {
	if c := strings.Compare(a.Org, b.Org); c != 0 {
		return c
	}
	if c := strings.Compare(a.Repo, b.Repo); c != 0 {
		return c
	}

	if a.Num > b.Num {
		return 1
	}
	if a.Num < b.Num {
		return -1
	}

	return 0
}

func AppendPRsToDescription(desc string, prs []BasicPR) string {
	var out strings.Builder

	out.WriteString(strings.TrimSpace(desc))
	out.WriteString("\n")

	slices.SortFunc(prs, compareBasicPRs)
	prs = slices.Compact(prs)

	for _, pr := range prs {
		prToLine(&out, pr)
	}

	return out.String()
}
