package versioning

import (
	"context"
	"encoding/json"
	"fmt"
	"net/http"
	"strings"
	"sync"
	"time"
)

const (
	DefaultStableManifestURL         = "https://static.pingcap.com/agentteam/releases/desktop/stable.json"
	DefaultFallbackStableManifestURL = "https://static.pingcap.com/agentteam/releases/daemon/stable.json"
	defaultManifestRetryTTL          = 5 * time.Minute
)

type StableManifestProviderConfig struct {
	StableURL   string
	FallbackURL string
	CacheTTL    time.Duration
	RetryTTL    time.Duration
	HTTPClient  *http.Client
	Now         func() time.Time
}

type StableManifestProvider struct {
	urls     []string
	cacheTTL time.Duration
	retryTTL time.Duration
	client   *http.Client
	now      func() time.Time

	mu            sync.Mutex
	cachedVersion string
	cachedUntil   time.Time
}

type stableManifest struct {
	SchemaVersion string `json:"schema_version"`
	Product       string `json:"product"`
	Current       string `json:"current_version"`
}

func NewStableManifestProvider(cfg StableManifestProviderConfig) *StableManifestProvider {
	stableURL := strings.TrimSpace(cfg.StableURL)
	if stableURL == "" {
		stableURL = DefaultStableManifestURL
	}
	fallbackURL := strings.TrimSpace(cfg.FallbackURL)
	if fallbackURL == "" {
		fallbackURL = DefaultFallbackStableManifestURL
	}

	urls := []string{stableURL}
	if fallbackURL != stableURL {
		urls = append(urls, fallbackURL)
	}

	cacheTTL := cfg.CacheTTL
	if cacheTTL <= 0 {
		cacheTTL = 6 * time.Hour
	}
	retryTTL := cfg.RetryTTL
	if retryTTL <= 0 {
		retryTTL = defaultManifestRetryTTL
	}
	client := cfg.HTTPClient
	if client == nil {
		client = http.DefaultClient
	}
	now := cfg.Now
	if now == nil {
		now = time.Now
	}

	return &StableManifestProvider{
		urls:     urls,
		cacheTTL: cacheTTL,
		retryTTL: retryTTL,
		client:   client,
		now:      now,
	}
}

func (provider *StableManifestProvider) Latest(ctx context.Context) (string, error) {
	if provider == nil {
		return "", nil
	}

	provider.mu.Lock()
	defer provider.mu.Unlock()

	now := provider.now()
	if now.Before(provider.cachedUntil) {
		return provider.cachedVersion, nil
	}

	version, err := provider.fetchLatest(ctx)
	if err == nil && version != "" {
		provider.cachedVersion = version
		provider.cachedUntil = now.Add(provider.cacheTTL)
		return provider.cachedVersion, nil
	}

	provider.cachedUntil = now.Add(provider.retryTTL)
	return provider.cachedVersion, nil
}

func (provider *StableManifestProvider) fetchLatest(ctx context.Context) (string, error) {
	var lastErr error
	for _, url := range provider.urls {
		if strings.TrimSpace(url) == "" {
			continue
		}
		version, err := provider.fetchStableManifest(ctx, url)
		if err == nil {
			return version, nil
		}
		lastErr = err
	}
	if lastErr != nil {
		return "", lastErr
	}
	return "", fmt.Errorf("no stable manifest URLs configured")
}

func (provider *StableManifestProvider) fetchStableManifest(ctx context.Context, url string) (string, error) {
	req, err := http.NewRequestWithContext(ctx, http.MethodGet, url, nil)
	if err != nil {
		return "", fmt.Errorf("build stable manifest request: %w", err)
	}

	resp, err := provider.client.Do(req)
	if err != nil {
		return "", fmt.Errorf("fetch stable manifest: %w", err)
	}
	defer resp.Body.Close()

	if resp.StatusCode < http.StatusOK || resp.StatusCode >= http.StatusMultipleChoices {
		return "", fmt.Errorf("fetch stable manifest: unexpected HTTP status %d", resp.StatusCode)
	}

	var manifest stableManifest
	if err := json.NewDecoder(resp.Body).Decode(&manifest); err != nil {
		return "", fmt.Errorf("decode stable manifest: %w", err)
	}

	version, err := stableManifestCurrentVersion(manifest)
	if err != nil {
		return "", err
	}
	return version, nil
}

func stableManifestCurrentVersion(manifest stableManifest) (string, error) {
	switch strings.ToLower(strings.TrimSpace(manifest.Product)) {
	case "agentteam-desktop", "agentteam-daemon":
	default:
		return "", fmt.Errorf("stable manifest product %q is not an AgentTeam client product", manifest.Product)
	}
	version, err := NormalizeDaemonVersion(manifest.Current)
	if err != nil {
		return "", fmt.Errorf("stable manifest current_version: %w", err)
	}
	if _, err := CompareSemver(version, "0.0.0"); err != nil {
		return "", fmt.Errorf("stable manifest current_version: %w", err)
	}
	return version, nil
}
