package update

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

	"golang.org/x/mod/semver"
)

const (
	moduleProxyURL = "https://proxy.golang.org/git.secluded.site/crush/@latest"
	userAgent      = "crush/1.0"
)

// Default is the default [Client].
var Default Client = &moduleProxy{}

// Info contains information about an available update.
type Info struct {
	Current string
	Latest  string
	URL     string
}

// Matches a version string like:
// v0.0.0-0.20251231235959-06c807842604
var goInstallRegexp = regexp.MustCompile(`^v?\d+\.\d+\.\d+-\d+\.\d{14}-[0-9a-f]{12}$`)

func (i Info) IsDevelopment() bool {
	return i.Current == "devel" || i.Current == "unknown" || strings.Contains(i.Current, "dirty") || goInstallRegexp.MatchString(i.Current)
}

// Available returns true if there's an update available.
//
// Fork prereleases are treated as latest for their base version. Other
// prereleases follow standard semver comparison.
func (i Info) Available() bool {
	vCurrent := "v" + i.Current
	vLatest := "v" + i.Latest

	if semver.Compare(vLatest, vCurrent) <= 0 {
		return false
	}

	// Fork vs stable override:
	// If current is a fork prerelease (contains "-fork") and latest is the
	// stable version of the same base, don't upgrade.
	if strings.Contains(i.Current, "-fork") && !strings.Contains(i.Latest, "-") {
		baseCurrent := strings.Split(vCurrent, "-")[0]
		if baseCurrent == vLatest {
			return false
		}
	}

	return true
}

// Check checks if a new version is available.
func Check(ctx context.Context, current string, client Client) (Info, error) {
	info := Info{
		Current: current,
		Latest:  current,
		URL:     "https://git.secluded.site/crush",
	}

	version, err := client.Latest(ctx)
	if err != nil {
		return info, fmt.Errorf("failed to fetch latest version: %w", err)
	}

	info.Latest = strings.TrimPrefix(version.Version, "v")
	info.Current = strings.TrimPrefix(info.Current, "v")
	return info, nil
}

// ModuleVersion represents a version from the Go module proxy.
type ModuleVersion struct {
	Version string    `json:"Version"`
	Time    time.Time `json:"Time"`
}

// Client is a client that can get the latest version.
type Client interface {
	Latest(ctx context.Context) (*ModuleVersion, error)
}

type moduleProxy struct{}

// Latest implements [Client].
func (c *moduleProxy) Latest(ctx context.Context) (*ModuleVersion, error) {
	client := &http.Client{
		Timeout: 30 * time.Second,
	}

	req, err := http.NewRequestWithContext(ctx, "GET", moduleProxyURL, nil)
	if err != nil {
		return nil, err
	}
	req.Header.Set("User-Agent", userAgent)

	resp, err := client.Do(req)
	if err != nil {
		return nil, err
	}
	defer resp.Body.Close()

	if resp.StatusCode != http.StatusOK {
		body, _ := io.ReadAll(resp.Body)
		return nil, fmt.Errorf("module proxy returned status %d: %s", resp.StatusCode, string(body))
	}

	var version ModuleVersion
	if err := json.NewDecoder(resp.Body).Decode(&version); err != nil {
		return nil, err
	}

	return &version, nil
}
