package main

import (
	"context"
	_ "embed"
	"fmt"
	"log"
	"os"
	"os/signal"
	"regexp"
	"sort"
	"strings"
	"time"

	"github.com/google/go-github/v62/github"
	"github.com/slack-go/slack"
	"github.com/supabase/cli/internal/utils"
)

func main() {
	slackChannel := ""
	if len(os.Args) > 1 {
		slackChannel = os.Args[1]
	}

	ctx, _ := signal.NotifyContext(context.Background(), os.Interrupt)
	if err := showChangeLog(ctx, slackChannel); err != nil {
		log.Fatalln(err)
	}
}

func showChangeLog(ctx context.Context, slackChannel string) error {
	client := utils.GetGitHubClient(ctx)
	releases, _, err := client.Repositories.ListReleases(ctx, utils.CLI_OWNER, utils.CLI_REPO, &github.ListOptions{})
	if err != nil {
		return err
	}
	opts := github.GenerateNotesOptions{}
	n := getLatestRelease(releases)
	if n < len(releases) {
		opts.TagName = *releases[n].TagName
		if m := getLatestRelease(releases[n+1:]) + n + 1; m < len(releases) {
			opts.PreviousTagName = releases[m].TagName
		}
	} else {
		branch := "main"
		opts.TargetCommitish = &branch
		opts.TagName = "v1.0.0"
	}
	fmt.Fprintln(os.Stderr, "Generating changelog for", opts.TagName)
	notes, _, err := client.Repositories.GenerateReleaseNotes(ctx, utils.CLI_OWNER, utils.CLI_REPO, &opts)
	if err != nil {
		return err
	}
	title := Title(releases[n])
	body := Body(notes)
	fmt.Println(title)
	fmt.Println(body)
	if len(slackChannel) == 0 {
		return nil
	}
	title = slackFormat(title)
	body = slackFormat(body)
	return slackAnnounce(ctx, slackChannel, title, body)
}

func getLatestRelease(releases []*github.RepositoryRelease) int {
	for i, r := range releases {
		if !*r.Draft && !*r.Prerelease {
			return i
		}
	}
	return len(releases)
}

func Title(r *github.RepositoryRelease) string {
	timestamp := r.PublishedAt.GetTime()
	if timestamp == nil {
		now := time.Now().UTC()
		timestamp = &now
	}
	return fmt.Sprintf("# %s (%s)\n", timestamp.Format("2 Jan 2006"), *r.TagName)
}

var logPattern = regexp.MustCompile(`^\* (.*): (.*) by @(.*) in (https:.*)$`)

type ChangeGroup struct {
	Prefix   string
	Header   string
	Messages []string
}

func (g ChangeGroup) Markdown() string {
	result := make([]string, len(g.Messages)+2)
	result[1] = "### " + g.Header
	for i, m := range g.Messages {
		result[i+2] = logPattern.ReplaceAllString(m, "* [$1]($4): $2")
	}
	return strings.Join(result, "\n")
}

func Body(n *github.RepositoryReleaseNotes) string {
	lines := strings.Split(n.Body, "\n")
	// Group features, fixes, dependencies, and chores
	groups := []ChangeGroup{
		{Prefix: "feat", Header: "Features"},
		{Prefix: "fix", Header: "Bug fixes"},
		{Prefix: "chore(deps)", Header: "Dependencies"},
		{Header: "Others"},
	}
	footer := []string{}
	for _, msg := range lines[1:] {
		matches := logPattern.FindStringSubmatch(msg)
		if len(matches) != 5 {
			footer = append(footer, msg)
			continue
		}
		cat := strings.ToLower(matches[1])
		for i, g := range groups {
			if strings.HasPrefix(cat, g.Prefix) {
				groups[i].Messages = append(g.Messages, msg)
				break
			}
		}
	}
	// Concatenate output
	result := []string{lines[0]}
	for _, g := range groups {
		if len(g.Messages) > 0 && g.Header != "Dependencies" {
			sort.Strings(g.Messages)
			result = append(result, g.Markdown())
		}
	}
	result = append(result, footer...)
	return strings.Join(result, "\n")
}

var linkPattern = regexp.MustCompile(`^(.*)\[(.*?)\]\((.*?)\)(.*)$`)

func toSlack(md string) string {
	// Change link format
	line := linkPattern.ReplaceAllString(md, "$1<$3|$2>$4")
	// Change first header to plain text
	if strings.HasPrefix(line, "# ") {
		return line[2:]
	}
	// Change second header to italics
	if strings.HasPrefix(line, "## ") {
		return fmt.Sprintf("_%s_", line[3:])
	}
	// Change third header to bold
	if strings.HasPrefix(line, "### ") {
		return fmt.Sprintf("*%s*", line[4:])
	}
	// Keep original list style
	if strings.HasPrefix(line, "* ") {
		return "• " + line[2:]
	}
	// Keep original bold style
	return strings.ReplaceAll(line, "**", "*")
}

func slackFormat(md string) string {
	lines := strings.Split(md, "\n")
	for i, md := range lines {
		lines[i] = toSlack(md)
	}
	return strings.Join(lines, "\n")
}

func slackAnnounce(ctx context.Context, channel, title, body string) error {
	api := slack.New(os.Getenv("SLACK_TOKEN"), slack.OptionDebug(true))
	msg := slack.MsgOptionBlocks(
		slack.NewHeaderBlock(&slack.TextBlockObject{Type: slack.PlainTextType, Text: title}),
		slack.NewSectionBlock(&slack.TextBlockObject{Type: slack.MarkdownType, Text: body}, nil, nil),
	)
	_, timestamp, err := api.PostMessageContext(ctx, channel, msg)
	if err != nil {
		return err
	}
	fmt.Fprintln(os.Stderr, "Announced changelog", timestamp)
	return nil
}
