package updater

import (
	"context"
	"encoding/json"
	"fmt"
	"io"
	"net/http"
	"regexp"
	"time"

	modulepkg "github.com/dbledeez/panel/pkg/module"
	"github.com/dbledeez/panel/pkg/version"
)

// GitHubProvider queries a repo's latest release, finds an asset whose
// name matches the provider's AssetRegex, and writes it to TargetPath.
type GitHubProvider struct{}

// Name identifies this provider kind in logs / manifest.
func (GitHubProvider) Name() string { return "github" }

type githubAsset struct {
	Name        string `json:"name"`
	DownloadURL string `json:"browser_download_url"`
	Size        int64  `json:"size"`
}

type githubRelease struct {
	Name    string        `json:"name"`
	TagName string        `json:"tag_name"`
	Assets  []githubAsset `json:"assets"`
}

// Update fetches the latest release of spec.Repo, picks the first asset
// matching spec.AssetRegex, and writes its bytes to spec.TargetPath.
func (GitHubProvider) Update(ctx context.Context, uc *Context, spec *modulepkg.UpdateProvider) error {
	if spec.Repo == "" {
		return fmt.Errorf("github provider: repo is required (owner/name)")
	}
	if spec.AssetRegex == "" {
		return fmt.Errorf("github provider: asset_regex is required")
	}
	if spec.TargetPath == "" {
		return fmt.Errorf("github provider: target_path is required")
	}
	re, err := regexp.Compile(spec.AssetRegex)
	if err != nil {
		return fmt.Errorf("asset_regex: %w", err)
	}

	apiURL := fmt.Sprintf("https://api.github.com/repos/%s/releases/latest", spec.Repo)
	uc.Log(fmt.Sprintf("querying GitHub: %s", apiURL))
	req, err := http.NewRequestWithContext(ctx, "GET", apiURL, nil)
	if err != nil {
		return fmt.Errorf("new request: %w", err)
	}
	req.Header.Set("Accept", "application/vnd.github+json")
	req.Header.Set("User-Agent", version.UserAgent())
	client := &http.Client{Timeout: 30 * time.Second}
	resp, err := client.Do(req)
	if err != nil {
		return fmt.Errorf("http: %w", err)
	}
	defer resp.Body.Close()
	if resp.StatusCode/100 != 2 {
		body, _ := io.ReadAll(io.LimitReader(resp.Body, 1024))
		return fmt.Errorf("github api %d: %s", resp.StatusCode, string(body))
	}
	var release githubRelease
	if err := json.NewDecoder(resp.Body).Decode(&release); err != nil {
		return fmt.Errorf("decode release: %w", err)
	}
	uc.Log(fmt.Sprintf("latest release: %s (%s), %d assets", release.Name, release.TagName, len(release.Assets)))

	var chosen *githubAsset
	for i := range release.Assets {
		if re.MatchString(release.Assets[i].Name) {
			chosen = &release.Assets[i]
			break
		}
	}
	if chosen == nil {
		return fmt.Errorf("no asset matches %q (saw: %s)", spec.AssetRegex, assetNames(release.Assets))
	}
	uc.Log(fmt.Sprintf("downloading asset %s (%d bytes)", chosen.Name, chosen.Size))

	direct := &modulepkg.UpdateProvider{
		Kind:       "direct",
		URL:        chosen.DownloadURL,
		TargetPath: spec.TargetPath,
	}
	return DirectProvider{}.Update(ctx, uc, direct)
}

func assetNames(as []githubAsset) string {
	out := ""
	for _, a := range as {
		if out != "" {
			out += ", "
		}
		out += a.Name
	}
	if out == "" {
		out = "(none)"
	}
	return out
}
