package scanner

import (
	"bufio"
	"bytes"
	"fmt"
	"os/exec"
	"regexp"
	"strings"

	"github.com/Fimeg/RedFlag/aggregator-agent/internal/client"
)

// DNFScanner scans for DNF/RPM package updates
type DNFScanner struct{}

// NewDNFScanner creates a new DNF scanner
func NewDNFScanner() *DNFScanner {
	return &DNFScanner{}
}

// IsAvailable checks if DNF is available on this system
func (s *DNFScanner) IsAvailable() bool {
	_, err := exec.LookPath("dnf")
	return err == nil
}

// Scan scans for available DNF updates
func (s *DNFScanner) Scan() ([]client.UpdateReportItem, error) {
	// Check for updates (don't update cache to avoid needing sudo)
	cmd := exec.Command("dnf", "check-update")
	output, err := cmd.Output()
	if err != nil {
		// dnf check-update returns exit code 100 when updates are available
		if exitErr, ok := err.(*exec.ExitError); ok && exitErr.ExitCode() == 100 {
			// Updates are available, continue processing
		} else {
			return nil, fmt.Errorf("failed to run dnf check-update: %w", err)
		}
	}

	return parseDNFOutput(output)
}

func parseDNFOutput(output []byte) ([]client.UpdateReportItem, error) {
	var updates []client.UpdateReportItem
	scanner := bufio.NewScanner(bytes.NewReader(output))

	// Regex to parse dnf check-update output:
	// package-name.version arch      new-version
	re := regexp.MustCompile(`^([^\s]+)\.([^\s]+)\s+([^\s]+)\s+([^\s]+)$`)

	for scanner.Scan() {
		line := strings.TrimSpace(scanner.Text())

		// Skip empty lines and header/footer
		if line == "" ||
		   strings.HasPrefix(line, "Last metadata") ||
		   strings.HasPrefix(line, "Dependencies") ||
		   strings.HasPrefix(line, "Obsoleting") ||
		   strings.Contains(line, "Upgraded") {
			continue
		}

		matches := re.FindStringSubmatch(line)
		if len(matches) < 5 {
			continue
		}

		packageName := matches[1]
		arch := matches[2]
		repoAndVersion := matches[3]
		newVersion := matches[4]

		// Extract repository and current version from repoAndVersion
		// Format is typically: repo-version current-version
		parts := strings.Fields(repoAndVersion)
		var repository, currentVersion string

		if len(parts) >= 2 {
			repository = parts[0]
			currentVersion = parts[1]
		} else if len(parts) == 1 {
			repository = parts[0]
			// Try to get current version from rpm
			currentVersion = getInstalledVersion(packageName)
		}

		// Determine severity based on repository and update type
		severity := determineSeverity(repository, packageName, newVersion)

		update := client.UpdateReportItem{
			PackageType:      "dnf",
			PackageName:      packageName,
			CurrentVersion:   currentVersion,
			AvailableVersion: newVersion,
			Severity:         severity,
			RepositorySource: repository,
			Metadata: map[string]interface{}{
				"architecture": arch,
			},
		}

		updates = append(updates, update)
	}

	return updates, nil
}

// getInstalledVersion gets the currently installed version of a package
func getInstalledVersion(packageName string) string {
	cmd := exec.Command("rpm", "-q", "--queryformat", "%{VERSION}", packageName)
	output, err := cmd.Output()
	if err != nil {
		return "unknown"
	}
	return strings.TrimSpace(string(output))
}

// determineSeverity determines the severity of an update based on repository and package information
func determineSeverity(repository, packageName, newVersion string) string {
	// Security updates
	if strings.Contains(strings.ToLower(repository), "security") ||
	   strings.Contains(strings.ToLower(repository), "updates") ||
	   strings.Contains(strings.ToLower(packageName), "security") ||
	   strings.Contains(strings.ToLower(packageName), "selinux") ||
	   strings.Contains(strings.ToLower(packageName), "crypto") ||
	   strings.Contains(strings.ToLower(packageName), "openssl") ||
	   strings.Contains(strings.ToLower(packageName), "gnutls") {
		return "critical"
	}

	// Kernel updates are important
	if strings.Contains(strings.ToLower(packageName), "kernel") {
		return "important"
	}

	// Core system packages
	if strings.Contains(strings.ToLower(packageName), "glibc") ||
	   strings.Contains(strings.ToLower(packageName), "systemd") ||
	   strings.Contains(strings.ToLower(packageName), "bash") ||
	   strings.Contains(strings.ToLower(packageName), "coreutils") {
		return "important"
	}

	// Development tools
	if strings.Contains(strings.ToLower(packageName), "gcc") ||
	   strings.Contains(strings.ToLower(packageName), "python") ||
	   strings.Contains(strings.ToLower(packageName), "nodejs") ||
	   strings.Contains(strings.ToLower(packageName), "java") ||
	   strings.Contains(strings.ToLower(packageName), "go") {
		return "moderate"
	}

	// Default severity
	return "low"
}