package main

import (
	"flag"
	"log"
	"net/url"
	"regexp"
	"slices"
	"strconv"
	"time"

	"src.opensuse.org/autogits/common"
	"src.opensuse.org/autogits/common/gitea-generated/models"
)

var configs common.AutogitConfigs
var reviewRx *regexp.Regexp
var rejectRx *regexp.Regexp
var groupName string

func InitRegex(groupName string) {
	reviewRx = regexp.MustCompile("^" + groupName + "\\s*:\\s*LGTM")
	rejectRx = regexp.MustCompile("^" + groupName + "\\s*:")
}

func ReviewAccepted(reviewText string) bool {
	return reviewRx.MatchString(reviewText)
}

func ReviewRejected(reviewText string) bool {
	return rejectRx.MatchString(reviewText)
}

func ProcessNotifications(notification *models.NotificationThread, gitea common.Gitea) {
	defer func() {
		if r := recover(); r != nil {
			log.Println("--- resovered")
		}
	}()

	rx := regexp.MustCompile(`^https://src\.(?:open)?suse\.(?:org|de)/api/v\d+/repos/(?<org>[a-zA-Z0-9]+)/(?<project>[_a-zA-Z0-9]+)/issues/(?<num>[0-9]+)$`)
	subject := notification.Subject
	match := rx.FindStringSubmatch(subject.URL)
	if match == nil {
		log.Panicf("** Unexpected format of notification: %s", subject.URL)
	}

	org := match[1]
	repo := match[2]
	id, _ := strconv.ParseInt(match[3], 10, 64)

	log.Printf("processing: %s/%s#%d\n", org, repo, id)
	pr, err := gitea.GetPullRequest(org, repo, id)
	if err != nil {
		log.Println("  ** Cannot fetch PR associated with review:", subject.URL, "Error:", err)
		return
	}

	config := configs.GetPrjGitConfig(org, repo, pr.Base.Name)
	if pr.State == "closed" {
		// dismiss the review
		log.Println("  -- closed request, so nothing to review")
		gitea.SetNotificationRead(notification.ID)
		return
	}

	reviews, err := gitea.GetPullRequestReviews(org, repo, id)
	if err != nil {
		log.Println("  ** No reviews associated with request:", subject.URL, "Error:", err)
		return
	}

	requestReviewers, err := config.GetReviewGroupMembers(groupName)
	if err != nil {
		log.Println(err)
		return
	}

	// submitter cannot be reviewer
	requestReviewers = slices.DeleteFunc(requestReviewers, func(u string) bool { return u == pr.User.UserName })

	for _, review := range reviews {
		user := review.User.UserName
		if !review.Stale && !review.Dismissed &&
			slices.Contains(requestReviewers, user) {

			if review.State == common.ReviewStateApproved && ReviewAccepted(review.Body) {
				gitea.AddReviewComment(pr, common.ReviewStateApproved, "Signed off by: "+user)
				if err := gitea.SetNotificationRead(notification.ID); err != nil {
					log.Println("  Cannot set notification as read", err)
				}
				log.Println("  -> approved by", user)
				return
			} else if review.State == common.ReviewStateRequestChanges && ReviewRejected(review.Body) {
				gitea.AddReviewComment(pr, common.ReviewStateRequestChanges, "Request changes. See review by: "+user)
				if err := gitea.SetNotificationRead(notification.ID); err != nil {
					log.Println("  Cannot set notification as read", err)
				}
				log.Println("  -> declined by", user)
				return
			}
		}
	}

	// request group member reviews, if missing
	log.Println("  Requesting reviews for:", requestReviewers)
	if _, err := gitea.RequestReviews(pr, requestReviewers...); err != nil {
		log.Println("   -> err:", err)
	}
}

func PeriodReviewCheck(gitea common.Gitea) {
	notifications, err := gitea.GetPullNotifications(nil)
	if err != nil {
		log.Println(" EEE  Error fetching unread notifications: %w", err)
		return
	}

	for _, notification := range notifications {
		ProcessNotifications(notification, gitea)

	}
}

func main() {
	giteaUrl := flag.String("gitea-url", "https://src.opensuse.org", "Gitea instance used for reviews")
	rabbitMqHost := flag.String("rabbit-host", "rabbit.opensuse.org", "RabbitMQ instance where Gitea webhook notifications are sent")
	interval := flag.Int64("interval", 5, "Notification polling interval in minutes (min 1 min)")
	configFile := flag.String("config", "", "PrjGit listing config file")
	flag.Parse()

	args := flag.Args()
	if len(args) != 1 {
		log.Println("  syntax:")
		log.Println("      group-review [OPTIONS] <review-group-name>")
		log.Println()
		flag.Usage()
		return
	}
	groupName = args[0]

	configData, err := common.ReadConfigFile(*configFile)
	if err != nil {
		log.Println("Failed to read config file", err)
		return
	}

	if err := common.RequireGiteaSecretToken(); err != nil {
		log.Panicln(err)
	}

	if err := common.RequireRabbitSecrets(); err != nil {
		log.Panicln(err)
	}

	gitea := common.AllocateGiteaTransport(*giteaUrl)
	configs, err = common.ResolveWorkflowConfigs(gitea, configData)
	if err != nil {
		log.Panicln(err)
	}

	reviewer, err := gitea.GetCurrentUser()
	if err != nil {
		log.Panicln("Cannot fetch review user: %w", err)
	}

	if *interval < 1 {
		*interval = 1
	}

	InitRegex(groupName)

	log.Println(" ** processing group reviews for group:", groupName)
	log.Println(" ** username in Gitea:", reviewer.UserName)
	log.Println(" ** polling internval:", *interval, "min")
	log.Println(" ** connecting to RabbitMQ:", *rabbitMqHost)

	if groupName != reviewer.UserName {
		log.Println(" ***** Reviewer does not match group name. Aborting. *****")
		return
	}

	_, err = url.Parse("amqps://" + *rabbitMqHost)
	if err != nil {
		log.Panicln("Cannot parse RabbitMQ host:", err)
	}
	/*
		common.ListenDefinitions{
			RabbitURL: u,
			GitAuthor: groupName,
			Orgs:      []string{"#"},
			Handlers: map[string]common.RequestProcessor{
				common.RequestType_PRReviewRequest:  ReviewRequest,
				common.RequestType_PRReviewRejected: ProcessReview,
				common.RequestType_PRReviewAccepted: ProcessReview,
			},
		}
	*/
	for {
		PeriodReviewCheck(gitea)
		time.Sleep(time.Duration(*interval * int64(time.Minute)))
	}
}
