package doctor

import (
	"encoding/json"
	"fmt"
	"io"
	"net/http"
	"os"
	"path/filepath"
	"strings"
	"time"
)

// CheckCLIVersion checks if the CLI version is up to date.
// Takes cliVersion parameter since it can't access the Version variable from main package.
func CheckCLIVersion(cliVersion string) DoctorCheck {
	latestVersion, err := fetchLatestGitHubRelease()
	if err != nil {
		// Network error or API issue - don't fail, just warn
		return DoctorCheck{
			Name:    "CLI Version",
			Status:  StatusOK,
			Message: fmt.Sprintf("%s (unable to check for updates)", cliVersion),
		}
	}

	if latestVersion == "" || latestVersion == cliVersion {
		return DoctorCheck{
			Name:    "CLI Version",
			Status:  StatusOK,
			Message: fmt.Sprintf("%s (latest)", cliVersion),
		}
	}

	// Compare versions using simple semver-aware comparison
	if CompareVersions(latestVersion, cliVersion) > 0 {
		upgradeCmd := getUpgradeCommand()
		return DoctorCheck{
			Name:    "CLI Version",
			Status:  StatusWarning,
			Message: fmt.Sprintf("%s (latest: %s)", cliVersion, latestVersion),
			Fix:     fmt.Sprintf("Upgrade: %s", upgradeCmd),
		}
	}

	return DoctorCheck{
		Name:    "CLI Version",
		Status:  StatusOK,
		Message: fmt.Sprintf("%s (latest)", cliVersion),
	}
}

// getUpgradeCommand returns the appropriate upgrade command based on how bd was installed.
// Detects Homebrew on macOS/Linux, and falls back to the install script on all platforms.
func getUpgradeCommand() string {
	// Get the executable path
	execPath, err := os.Executable()
	if err != nil {
		return "curl -fsSL https://raw.githubusercontent.com/steveyegge/beads/main/scripts/install.sh | bash"
	}

	// Resolve symlinks to get the real path
	realPath, err := filepath.EvalSymlinks(execPath)
	if err != nil {
		realPath = execPath
	}

	// Normalize to lowercase for comparison
	lowerPath := strings.ToLower(realPath)

	// Check for Homebrew installation (macOS/Linux)
	// Homebrew paths: /opt/homebrew/Cellar/bd, /usr/local/Cellar/bd, /home/linuxbrew/.linuxbrew/Cellar/bd
	if strings.Contains(lowerPath, "/cellar/bd/") ||
		strings.Contains(lowerPath, "/homebrew/") ||
		strings.Contains(lowerPath, "/linuxbrew/") {
		return "brew upgrade bd"
	}

	// Default to install script (works on all platforms including Windows via WSL/Git Bash)
	return "curl -fsSL https://raw.githubusercontent.com/steveyegge/beads/main/scripts/install.sh | bash"
}

// localVersionFile is the gitignored file that stores the last bd version used locally.
// Must match the constant in version_tracking.go.
const localVersionFile = ".local_version"

// CheckMetadataVersionTracking checks if version tracking is properly configured.
// Version tracking uses .local_version file (gitignored) to track the last bd version used.
//
// GH#662: This was updated to check .local_version instead of metadata.json:LastBdVersion,
// which is now deprecated.
func CheckMetadataVersionTracking(path string, currentVersion string) DoctorCheck {
	// Follow redirect to resolve actual beads directory (bd-tvus fix)
	beadsDir := resolveBeadsDir(filepath.Join(path, ".beads"))
	localVersionPath := filepath.Join(beadsDir, localVersionFile)

	// Read .local_version file
	// #nosec G304 - path is constructed from controlled beadsDir + constant
	data, err := os.ReadFile(localVersionPath)
	if err != nil {
		if os.IsNotExist(err) {
			// File doesn't exist yet - will be created on next bd command
			return DoctorCheck{
				Name:    "Version Tracking",
				Status:  StatusWarning,
				Message: "Version tracking not initialized",
				Detail:  "The .local_version file will be created on next bd command",
				Fix:     "Run any bd command (e.g., 'bd ready') to initialize version tracking",
			}
		}
		// Other error reading file
		return DoctorCheck{
			Name:    "Version Tracking",
			Status:  StatusError,
			Message: "Unable to read .local_version file",
			Detail:  err.Error(),
			Fix:     "Check file permissions on .beads/.local_version",
		}
	}

	lastVersion := strings.TrimSpace(string(data))

	// Check if file is empty
	if lastVersion == "" {
		return DoctorCheck{
			Name:    "Version Tracking",
			Status:  StatusWarning,
			Message: ".local_version file is empty",
			Detail:  "Version tracking will be initialized on next command",
			Fix:     "Run any bd command to initialize version tracking",
		}
	}

	// Validate that version is a valid semver-like string
	if !IsValidSemver(lastVersion) {
		return DoctorCheck{
			Name:    "Version Tracking",
			Status:  StatusWarning,
			Message: fmt.Sprintf("Invalid version format in .local_version: %q", lastVersion),
			Detail:  "Expected semver format like '0.24.2'",
			Fix:     "Run any bd command to reset version tracking to current version",
		}
	}

	// Check if version is very old (> 10 versions behind)
	versionDiff := CompareVersions(currentVersion, lastVersion)
	if versionDiff > 0 {
		// Current version is newer - check how far behind
		currentParts := ParseVersionParts(currentVersion)
		lastParts := ParseVersionParts(lastVersion)

		// Simple heuristic: warn if minor version is 10+ behind or major version differs by 1+
		majorDiff := currentParts[0] - lastParts[0]
		minorDiff := currentParts[1] - lastParts[1]

		if majorDiff >= 1 || (majorDiff == 0 && minorDiff >= 10) {
			return DoctorCheck{
				Name:    "Version Tracking",
				Status:  StatusWarning,
				Message: fmt.Sprintf("Last recorded version is very old: %s (current: %s)", lastVersion, currentVersion),
				Detail:  "You may have missed important upgrade notifications",
				Fix:     "Run 'bd upgrade review' to see recent changes",
			}
		}

		// Version is behind but not too old - this is normal after upgrade
		return DoctorCheck{
			Name:    "Version Tracking",
			Status:  StatusOK,
			Message: fmt.Sprintf("Version tracking active (last: %s, current: %s)", lastVersion, currentVersion),
		}
	}

	// Version is current or ahead
	return DoctorCheck{
		Name:    "Version Tracking",
		Status:  StatusOK,
		Message: fmt.Sprintf("Version tracking active (version: %s)", lastVersion),
	}
}

// fetchLatestGitHubRelease fetches the latest release version from GitHub API.
func fetchLatestGitHubRelease() (string, error) {
	url := "https://api.github.com/repos/steveyegge/beads/releases/latest"

	client := &http.Client{
		Timeout: 5 * time.Second,
	}

	req, err := http.NewRequest("GET", url, nil)
	if err != nil {
		return "", err
	}

	// Set User-Agent as required by GitHub API
	req.Header.Set("User-Agent", "beads-cli-doctor")

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

	if resp.StatusCode != http.StatusOK {
		return "", fmt.Errorf("github api returned status %d", resp.StatusCode)
	}

	body, err := io.ReadAll(resp.Body)
	if err != nil {
		return "", err
	}

	var release struct {
		TagName string `json:"tag_name"`
	}

	if err := json.Unmarshal(body, &release); err != nil {
		return "", err
	}

	// Strip 'v' prefix if present
	version := strings.TrimPrefix(release.TagName, "v")

	return version, nil
}

// CompareVersions compares two semantic version strings.
// Returns: -1 if v1 < v2, 0 if v1 == v2, 1 if v1 > v2
// Handles versions like "0.20.1", "1.2.3", etc.
func CompareVersions(v1, v2 string) int {
	// Split versions into parts
	parts1 := strings.Split(v1, ".")
	parts2 := strings.Split(v2, ".")

	// Compare each part
	maxLen := len(parts1)
	if len(parts2) > maxLen {
		maxLen = len(parts2)
	}

	for i := 0; i < maxLen; i++ {
		var p1, p2 int

		// Get part value or default to 0 if part doesn't exist
		if i < len(parts1) {
			_, _ = fmt.Sscanf(parts1[i], "%d", &p1)
		}
		if i < len(parts2) {
			_, _ = fmt.Sscanf(parts2[i], "%d", &p2)
		}

		if p1 < p2 {
			return -1
		}
		if p1 > p2 {
			return 1
		}
	}

	return 0
}

// IsValidSemver checks if a version string is valid semver-like format (X.Y.Z)
func IsValidSemver(version string) bool {
	if version == "" {
		return false
	}

	// Split by dots and ensure all parts are numeric
	versionParts := strings.Split(version, ".")
	if len(versionParts) < 1 {
		return false
	}

	// Parse each part to ensure it's a valid number
	for _, part := range versionParts {
		if part == "" {
			return false
		}
		var num int
		if _, err := fmt.Sscanf(part, "%d", &num); err != nil {
			return false
		}
		if num < 0 {
			return false
		}
	}

	return true
}

// ParseVersionParts parses version string into numeric parts
// Returns [major, minor, patch, ...] or empty slice on error
func ParseVersionParts(version string) []int {
	parts := strings.Split(version, ".")
	result := make([]int, 0, len(parts))

	for _, part := range parts {
		var num int
		if _, err := fmt.Sscanf(part, "%d", &num); err != nil {
			return result
		}
		result = append(result, num)
	}

	return result
}
