package main

import (
	"bufio"
	"bytes"
	"context"
	_ "embed"
	"encoding/base64"
	"flag"
	"fmt"
	"log"
	"net/http"
	"os"
	"os/signal"
	"strings"
	"text/template"

	"github.com/google/go-github/v62/github"
	"github.com/supabase/cli/internal/utils"
	"github.com/supabase/cli/pkg/fetcher"
	"github.com/supabase/cli/tools/shared"
)

const (
	SUPABASE_OWNER = "supabase"
	HOMEBREW_REPO  = "homebrew-tap"
	SCOOP_REPO     = "scoop-bucket"
)

var (
	//go:embed templates/supabase.rb
	brewFormula         string
	brewFormulaTemplate = template.Must(template.New(HOMEBREW_REPO).Parse(brewFormula))
	//go:embed templates/supabase.json
	scoopBucket         string
	scoopBucketTemplate = template.Must(template.New(SCOOP_REPO).Parse(scoopBucket))
)

func main() {
	beta := flag.Bool("beta", false, "Updates the beta release channel.")
	flag.Parse()

	semver := flag.Arg(0)
	if len(semver) == 0 {
		log.Fatalln("Missing required arg: version")
	} else if semver[0] == 'v' {
		semver = semver[1:]
	}

	ctx, _ := signal.NotifyContext(context.Background(), os.Interrupt)
	if err := publishPackages(ctx, semver, *beta); err != nil {
		log.Fatalln(err)
	}
}

func publishPackages(ctx context.Context, version string, beta bool) error {
	config, err := fetchConfig(ctx, version)
	if err != nil {
		return err
	}
	config.FormulaName = "Supabase"
	config.Description = "Supabase CLI"
	filename := "supabase"
	if beta {
		config.FormulaName += "Beta"
		config.Description += " (Beta)"
		filename += "-beta"
	}
	client := utils.GetGitHubClient(ctx)
	if err := updatePackage(ctx, client, HOMEBREW_REPO, filename+".rb", brewFormulaTemplate, config); err != nil {
		return err
	}
	return updatePackage(ctx, client, SCOOP_REPO, filename+".json", scoopBucketTemplate, config)
}

type PackageConfig struct {
	Version     string
	Checksum    map[string]string
	Description string
	FormulaName string
}

func fetchConfig(ctx context.Context, version string) (PackageConfig, error) {
	client := fetcher.NewFetcher("https://github.com", fetcher.WithExpectedStatus(http.StatusOK))
	checkPath := fmt.Sprintf("/%s/%s/releases/download/v%[3]s/supabase_%[3]s_checksums.txt",
		utils.CLI_OWNER,
		utils.CLI_REPO,
		version,
	)
	log.Println("Downloading checksum:", checkPath)
	config := PackageConfig{Version: version}
	resp, err := client.Send(ctx, http.MethodGet, checkPath, nil)
	if err != nil {
		return config, err
	}
	defer resp.Body.Close()
	// Read checksums into map: filename -> sha256
	config.Checksum = make(map[string]string)
	scanner := bufio.NewScanner(resp.Body)
	for scanner.Scan() {
		tokens := strings.Fields(scanner.Text())
		key := strings.TrimSuffix(tokens[1], ".tar.gz")
		config.Checksum[key] = tokens[0]
	}
	if err := scanner.Err(); err != nil {
		return config, err
	}
	return config, nil
}

func updatePackage(ctx context.Context, client *github.Client, repo, path string, tmpl *template.Template, config PackageConfig) error {
	fmt.Fprintf(os.Stderr, "Updating %s: %s\n", repo, path)
	// Render formula from template
	var buf bytes.Buffer
	if err := tmpl.Option("missingkey=error").Execute(&buf, config); err != nil {
		return err
	}
	branch := "release/cli"
	master := "main"
	if err := shared.CreateGitBranch(ctx, client, SUPABASE_OWNER, repo, branch, master); err != nil {
		return err
	}
	// Get file SHA
	opts := github.RepositoryContentGetOptions{Ref: "heads/" + branch}
	file, _, _, err := client.Repositories.GetContents(ctx, SUPABASE_OWNER, repo, path, &opts)
	if err != nil {
		return err
	}
	content, err := base64.StdEncoding.DecodeString(*file.Content)
	if err != nil {
		return err
	}
	if bytes.Equal(content, buf.Bytes()) {
		fmt.Fprintln(os.Stderr, "All versions are up to date.")
		return nil
	}
	// Update file content
	message := "Release " + config.Description
	commit := github.RepositoryContentFileOptions{
		Message: &message,
		Content: buf.Bytes(),
		SHA:     file.SHA,
		Branch:  &branch,
	}
	resp, _, err := client.Repositories.UpdateFile(ctx, SUPABASE_OWNER, repo, path, &commit)
	if err != nil {
		return err
	}
	fmt.Fprintln(os.Stderr, "Committed changes to", *resp.Commit.SHA)
	// Create pull request
	pr := github.NewPullRequest{
		Title: &message,
		Head:  &branch,
		Base:  &master,
	}
	return shared.CreatePullRequest(ctx, client, SUPABASE_OWNER, repo, pr)
}
