package updater

import (
	"context"
	"sync"
	"time"

	"github.com/standardbeagle/agnt/internal/debug"
)

// UpdateInfo holds information about available updates
type UpdateInfo struct {
	Available      bool      `json:"available"`
	LatestVersion  string    `json:"latest_version,omitempty"`
	CurrentVersion string    `json:"current_version"`
	ReleaseURL     string    `json:"release_url,omitempty"`
	ReleaseNotes   string    `json:"release_notes,omitempty"`
	LastChecked    time.Time `json:"last_checked"`
	CheckError     string    `json:"check_error,omitempty"`
}

// releaseFetcher is the minimal seam UpdateChecker depends on for fetching the
// latest release. *GitHubChecker satisfies it; tests substitute a fake.
type releaseFetcher interface {
	CheckLatestRelease() (*GitHubRelease, error)
}

// UpdateChecker periodically checks for updates
type UpdateChecker struct {
	mu             sync.RWMutex
	currentVersion string
	checkInterval  time.Duration
	githubChecker  releaseFetcher
	updateInfo     UpdateInfo
	ctx            context.Context
	cancel         context.CancelFunc
	wg             sync.WaitGroup
}

// Config holds configuration for the update checker
type Config struct {
	CurrentVersion string
	CheckInterval  time.Duration
	GitHubRepo     string
	Enabled        bool
}

// DefaultConfig returns default update checker configuration
func DefaultConfig() Config {
	return Config{
		CurrentVersion: "unknown",
		CheckInterval:  24 * time.Hour,
		GitHubRepo:     DefaultGitHubRepo,
		Enabled:        true,
	}
}

// NewUpdateChecker creates a new update checker
func NewUpdateChecker(config Config) *UpdateChecker {
	if config.CheckInterval == 0 {
		config.CheckInterval = 24 * time.Hour
	}

	ctx, cancel := context.WithCancel(context.Background())

	uc := &UpdateChecker{
		currentVersion: config.CurrentVersion,
		checkInterval:  config.CheckInterval,
		githubChecker:  NewGitHubChecker(config.GitHubRepo),
		updateInfo: UpdateInfo{
			CurrentVersion: config.CurrentVersion,
			LastChecked:    time.Time{}, // Zero time indicates never checked
		},
		ctx:    ctx,
		cancel: cancel,
	}

	return uc
}

// Start begins periodic update checking
func (uc *UpdateChecker) Start() {
	uc.wg.Add(1)
	go uc.checkLoop()
}

// Stop stops the update checker. The provided context bounds how long Stop will
// wait for the check loop goroutine to finish. If ctx expires, Stop returns
// immediately (the goroutine is still cancelled via uc.cancel but may not have
// exited yet, e.g. if blocked on a network request).
func (uc *UpdateChecker) Stop(ctx context.Context) {
	uc.cancel()

	done := make(chan struct{})
	go func() {
		uc.wg.Wait()
		close(done)
	}()

	select {
	case <-done:
	case <-ctx.Done():
	}
}

// checkLoop runs the periodic update check
func (uc *UpdateChecker) checkLoop() {
	defer uc.wg.Done()

	// Check immediately on start
	uc.checkForUpdates()

	ticker := time.NewTicker(uc.checkInterval)
	defer ticker.Stop()

	for {
		select {
		case <-uc.ctx.Done():
			return
		case <-ticker.C:
			uc.checkForUpdates()
		}
	}
}

// checkForUpdates performs a single update check
func (uc *UpdateChecker) checkForUpdates() {
	debug.Log("updater", "Checking for updates...")

	release, err := uc.githubChecker.CheckLatestRelease()
	if err != nil {
		debug.Log("updater", "Failed to check for updates: %v", err)
		uc.mu.Lock()
		uc.updateInfo.CheckError = err.Error()
		uc.updateInfo.LastChecked = time.Now()
		uc.mu.Unlock()
		return
	}

	isNewer, err := release.IsNewer(uc.currentVersion)
	if err != nil {
		debug.Log("updater", "Failed to compare versions: %v", err)
		uc.mu.Lock()
		uc.updateInfo.CheckError = err.Error()
		uc.updateInfo.LastChecked = time.Now()
		uc.mu.Unlock()
		return
	}

	uc.mu.Lock()
	uc.updateInfo = UpdateInfo{
		Available:      isNewer,
		LatestVersion:  release.GetVersion(),
		CurrentVersion: uc.currentVersion,
		ReleaseURL:     release.HTMLURL,
		ReleaseNotes:   release.Body,
		LastChecked:    time.Now(),
		CheckError:     "",
	}
	uc.mu.Unlock()

	if isNewer {
		debug.Log("updater", "Update available: %s -> %s",
			uc.currentVersion, release.GetVersion())
	} else {
		debug.Log("updater", "No update available (current: %s, latest: %s)",
			uc.currentVersion, release.GetVersion())
	}
}

// GetUpdateInfo returns the current update information
func (uc *UpdateChecker) GetUpdateInfo() UpdateInfo {
	uc.mu.RLock()
	defer uc.mu.RUnlock()
	return uc.updateInfo
}

// CheckNow triggers an immediate update check
func (uc *UpdateChecker) CheckNow() {
	uc.checkForUpdates()
}
