package client

import (
	"bytes"
	"encoding/json"
	"fmt"
	"io"
	"log"
	"net/http"
	"os"
	"path/filepath"
	"runtime"
	"strings"
	"time"

	"github.com/Fimeg/RedFlag/aggregator-agent/internal/event"
	"github.com/Fimeg/RedFlag/aggregator-agent/internal/models"
	"github.com/Fimeg/RedFlag/aggregator-agent/internal/system"
	"github.com/google/uuid"
)

// Client handles API communication with the server
type Client struct {
	baseURL             string
	token               string
	http                *http.Client
	RapidPollingEnabled bool
	RapidPollingUntil   time.Time
	machineID           string // Cached machine ID for security binding
	eventBuffer         *event.Buffer
	agentID             uuid.UUID
}

// NewClient creates a new API client
func NewClient(baseURL, token string) *Client {
	// Get machine ID for security binding (v0.1.22+)
	machineID, err := system.GetMachineID()
	if err != nil {
		// Log warning but don't fail — older servers may not require it
		log.Printf("[WARNING] [agent] [client] machine_id_error error=%q", err)
		machineID = "" // Will be handled by server validation
	}

	return &Client{
		baseURL:   baseURL,
		token:     token,
		machineID: machineID,
		http: &http.Client{
			Timeout: 30 * time.Second,
		},
	}
}

// NewClientWithEventBuffer creates a new API client with event buffering capability
func NewClientWithEventBuffer(baseURL, token string, statePath string, agentID uuid.UUID) *Client {
	client := NewClient(baseURL, token)
	client.agentID = agentID

	// Initialize event buffer if state path is provided
	if statePath != "" {
		eventBufferPath := filepath.Join(statePath, "events_buffer.json")
		client.eventBuffer = event.NewBuffer(eventBufferPath)
	}

	return client
}

// bufferEvent buffers a system event for later reporting
func (c *Client) bufferEvent(eventType, eventSubtype, severity, component, message string, metadata map[string]interface{}) {
	if c.eventBuffer == nil {
		return // Event buffering not enabled
	}

	// Use agent ID if available, otherwise create event with nil agent ID
	var agentIDPtr *uuid.UUID
	if c.agentID != uuid.Nil {
		agentIDPtr = &c.agentID
	}

	event := &models.SystemEvent{
		ID:           uuid.New(),
		AgentID:      agentIDPtr,
		EventType:    eventType,
		EventSubtype: eventSubtype,
		Severity:     severity,
		Component:    component,
		Message:      message,
		Metadata:     metadata,
		CreatedAt:    time.Now(),
	}

	// Buffer the event (best effort - don't fail if buffering fails)
	if err := c.eventBuffer.BufferEvent(event); err != nil {
		fmt.Printf("Warning: Failed to buffer event: %v\n", err)
	}
}

// GetBufferedEvents returns all buffered events and clears the buffer
func (c *Client) GetBufferedEvents() ([]*models.SystemEvent, error) {
	if c.eventBuffer == nil {
		return nil, nil // Event buffering not enabled
	}
	return c.eventBuffer.GetBufferedEvents()
}

// addMachineIDHeader adds X-Machine-ID header to authenticated requests (v0.1.22+)
func (c *Client) addMachineIDHeader(req *http.Request) {
	if c.machineID != "" {
		req.Header.Set("X-Machine-ID", c.machineID)
	}
}

// GetToken returns the current JWT token
func (c *Client) GetToken() string {
	return c.token
}

// SetToken updates the JWT token
func (c *Client) SetToken(token string) {
	c.token = token
}

// RegisterRequest is the payload for agent registration
type RegisterRequest struct {
	Hostname             string            `json:"hostname"`
	OSType               string            `json:"os_type"`
	OSVersion            string            `json:"os_version"`
	OSArchitecture       string            `json:"os_architecture"`
	AgentVersion         string            `json:"agent_version"`
	RegistrationToken    string            `json:"registration_token,omitempty"` // Fallback method
	MachineID            string            `json:"machine_id"`
	PublicKeyFingerprint string            `json:"public_key_fingerprint"`
	Metadata             map[string]string `json:"metadata"`
}

// RegisterResponse is returned after successful registration
type RegisterResponse struct {
	AgentID      uuid.UUID              `json:"agent_id"`
	Token        string                 `json:"token"`         // Short-lived access token (24h)
	RefreshToken string                 `json:"refresh_token"` // Long-lived refresh token (90d)
	Config       map[string]interface{} `json:"config"`
}

// Register registers the agent with the server
func (c *Client) Register(req RegisterRequest) (*RegisterResponse, error) {
	url := fmt.Sprintf("%s/api/v1/agents/register", c.baseURL)

	// If we have a registration token, include it in the request
	// Registration tokens are longer than regular JWT tokens (usually 64 chars vs JWT ~400 chars)
	if c.token != "" && len(c.token) > 40 {
		req.RegistrationToken = c.token
	}

	body, err := json.Marshal(req)
	if err != nil {
		// Buffer registration failure event
		c.bufferEvent("registration_failure", "marshal_error", "error", "client",
			fmt.Sprintf("Failed to marshal registration request: %v", err),
			map[string]interface{}{
				"error":    err.Error(),
				"hostname": req.Hostname,
			})
		return nil, err
	}

	httpReq, err := http.NewRequest("POST", url, bytes.NewBuffer(body))
	if err != nil {
		// Buffer registration failure event
		c.bufferEvent("registration_failure", "request_creation_error", "error", "client",
			fmt.Sprintf("Failed to create registration request: %v", err),
			map[string]interface{}{
				"error":    err.Error(),
				"hostname": req.Hostname,
			})
		return nil, err
	}
	httpReq.Header.Set("Content-Type", "application/json")

	// Add Authorization header if we have a registration token (preferred method)
	// Registration tokens are longer than regular JWT tokens (usually 64 chars vs JWT ~400 chars)
	if c.token != "" && len(c.token) > 40 {
		httpReq.Header.Set("Authorization", "Bearer "+c.token)
	}

	resp, err := c.http.Do(httpReq)
	if err != nil {
		// Buffer registration failure event
		c.bufferEvent("registration_failure", "network_error", "error", "client",
			fmt.Sprintf("Registration request failed: %v", err),
			map[string]interface{}{
				"error":      err.Error(),
				"hostname":   req.Hostname,
				"server_url": c.baseURL,
			})
		return nil, err
	}
	defer resp.Body.Close()

	if resp.StatusCode != http.StatusOK {
		bodyBytes, _ := io.ReadAll(resp.Body)
		errorMsg := fmt.Sprintf("registration failed: %s - %s", resp.Status, string(bodyBytes))

		// Buffer registration failure event
		c.bufferEvent("registration_failure", "api_error", "error", "client",
			errorMsg,
			map[string]interface{}{
				"status_code":   resp.StatusCode,
				"response_body": string(bodyBytes),
				"hostname":      req.Hostname,
				"server_url":    c.baseURL,
			})
		return nil, fmt.Errorf(errorMsg)
	}

	var result RegisterResponse
	if err := json.NewDecoder(resp.Body).Decode(&result); err != nil {
		// Buffer registration failure event
		c.bufferEvent("registration_failure", "decode_error", "error", "client",
			fmt.Sprintf("Failed to decode registration response: %v", err),
			map[string]interface{}{
				"error":    err.Error(),
				"hostname": req.Hostname,
			})
		return nil, err
	}

	// Update client token and agent ID
	c.token = result.Token
	c.agentID = result.AgentID

	return &result, nil
}

// TokenRenewalRequest is the payload for token renewal using refresh token
type TokenRenewalRequest struct {
	AgentID      uuid.UUID `json:"agent_id"`
	RefreshToken string    `json:"refresh_token"`
	AgentVersion string    `json:"agent_version,omitempty"` // Agent's current version for upgrade tracking
}

// TokenRenewalResponse is returned after successful token renewal
type TokenRenewalResponse struct {
	Token string `json:"token"` // New short-lived access token (24h)
}

// RenewToken uses refresh token to get a new access token (proper implementation)
func (c *Client) RenewToken(agentID uuid.UUID, refreshToken string, agentVersion string) error {
	url := fmt.Sprintf("%s/api/v1/agents/renew", c.baseURL)

	renewalReq := TokenRenewalRequest{
		AgentID:      agentID,
		RefreshToken: refreshToken,
		AgentVersion: agentVersion,
	}

	body, err := json.Marshal(renewalReq)
	if err != nil {
		// Buffer token renewal failure event
		c.bufferEvent("token_renewal_failure", "marshal_error", "error", "client",
			fmt.Sprintf("Failed to marshal token renewal request: %v", err),
			map[string]interface{}{
				"error":    err.Error(),
				"agent_id": agentID.String(),
			})
		return err
	}

	httpReq, err := http.NewRequest("POST", url, bytes.NewBuffer(body))
	if err != nil {
		// Buffer token renewal failure event
		c.bufferEvent("token_renewal_failure", "request_creation_error", "error", "client",
			fmt.Sprintf("Failed to create token renewal request: %v", err),
			map[string]interface{}{
				"error":    err.Error(),
				"agent_id": agentID.String(),
			})
		return err
	}
	httpReq.Header.Set("Content-Type", "application/json")

	resp, err := c.http.Do(httpReq)
	if err != nil {
		// Buffer token renewal failure event
		c.bufferEvent("token_renewal_failure", "network_error", "error", "client",
			fmt.Sprintf("Token renewal request failed: %v", err),
			map[string]interface{}{
				"error":      err.Error(),
				"agent_id":   agentID.String(),
				"server_url": c.baseURL,
			})
		return err
	}
	defer resp.Body.Close()

	if resp.StatusCode != http.StatusOK {
		bodyBytes, _ := io.ReadAll(resp.Body)
		errorMsg := fmt.Sprintf("token renewal failed: %s - %s", resp.Status, string(bodyBytes))

		// Buffer token renewal failure event
		c.bufferEvent("token_renewal_failure", "api_error", "error", "client",
			errorMsg,
			map[string]interface{}{
				"status_code":   resp.StatusCode,
				"response_body": string(bodyBytes),
				"agent_id":      agentID.String(),
				"server_url":    c.baseURL,
			})
		return fmt.Errorf(errorMsg)
	}

	var result TokenRenewalResponse
	if err := json.NewDecoder(resp.Body).Decode(&result); err != nil {
		// Buffer token renewal failure event
		c.bufferEvent("token_renewal_failure", "decode_error", "error", "client",
			fmt.Sprintf("Failed to decode token renewal response: %v", err),
			map[string]interface{}{
				"error":    err.Error(),
				"agent_id": agentID.String(),
			})
		return err
	}

	// Update client token
	c.token = result.Token

	return nil
}

// Command represents a command from the server
type Command struct {
	ID        string                 `json:"id"`
	Type      string                 `json:"type"`
	Params    map[string]interface{} `json:"params"`
	Signature string                 `json:"signature,omitempty"` // Ed25519 signature of the command
	KeyID     string                 `json:"key_id,omitempty"`    // Fingerprint of the signing key used
	SignedAt  *time.Time             `json:"signed_at,omitempty"` // Timestamp when command was signed
	AgentID   string                 `json:"agent_id,omitempty"`  // Target agent ID (F-1 fix: included in signed payload)
	CreatedAt *time.Time             `json:"created_at,omitempty"` // Server-side creation time (F-3 fix: old-format expiry)
}

// CommandItem is an alias for Command for consistency with server models
type CommandItem = Command

// CommandsResponse contains pending commands
type CommandsResponse struct {
	Commands        []Command           `json:"commands"`
	RapidPolling    *RapidPollingConfig `json:"rapid_polling,omitempty"`
	AcknowledgedIDs []string            `json:"acknowledged_ids,omitempty"` // IDs server has received
}

// RapidPollingConfig contains rapid polling configuration from server
type RapidPollingConfig struct {
	Enabled bool   `json:"enabled"`
	Until   string `json:"until"` // ISO 8601 timestamp
}

// SystemMetrics represents lightweight system metrics sent with check-ins
type SystemMetrics struct {
	CPUPercent    float64                `json:"cpu_percent,omitempty"`
	MemoryPercent float64                `json:"memory_percent,omitempty"`
	MemoryUsedGB  float64                `json:"memory_used_gb,omitempty"`
	MemoryTotalGB float64                `json:"memory_total_gb,omitempty"`
	DiskUsedGB    float64                `json:"disk_used_gb,omitempty"`
	DiskTotalGB   float64                `json:"disk_total_gb,omitempty"`
	DiskPercent   float64                `json:"disk_percent,omitempty"`
	Uptime        string                 `json:"uptime,omitempty"`
	Version       string                 `json:"version,omitempty"`  // Agent version
	Metadata      map[string]interface{} `json:"metadata,omitempty"` // Additional metadata

	// Command acknowledgment tracking
	PendingAcknowledgments []string `json:"pending_acknowledgments,omitempty"` // Command IDs awaiting ACK
}

// GetCommands retrieves pending commands from the server
// Optionally sends lightweight system metrics in the request
// Returns the full response including commands and acknowledged IDs
func (c *Client) GetCommands(agentID uuid.UUID, metrics *SystemMetrics) (*CommandsResponse, error) {
	url := fmt.Sprintf("%s/api/v1/agents/%s/commands", c.baseURL, agentID)

	var req *http.Request
	var err error

	// If metrics provided, send them in request body
	if metrics != nil {
		body, err := json.Marshal(metrics)
		if err != nil {
			return nil, err
		}
		req, err = http.NewRequest("GET", url, bytes.NewBuffer(body))
		if err != nil {
			return nil, err
		}
		req.Header.Set("Content-Type", "application/json")
	} else {
		req, err = http.NewRequest("GET", url, nil)
		if err != nil {
			return nil, err
		}
	}

	req.Header.Set("Authorization", "Bearer "+c.token)
	c.addMachineIDHeader(req) // Security: Validate machine binding (v0.1.22+)

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

	if resp.StatusCode != http.StatusOK {
		bodyBytes, _ := io.ReadAll(resp.Body)
		return nil, fmt.Errorf("failed to get commands: %s - %s", resp.Status, string(bodyBytes))
	}

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

	// Handle rapid polling configuration if provided
	if result.RapidPolling != nil {
		// Parse the timestamp
		if until, err := time.Parse(time.RFC3339, result.RapidPolling.Until); err == nil {
			// Update client's rapid polling configuration
			c.RapidPollingEnabled = result.RapidPolling.Enabled
			c.RapidPollingUntil = until
		}
	}

	return &result, nil
}

// UpdateReport represents discovered updates
type UpdateReport struct {
	CommandID string             `json:"command_id"`
	Timestamp time.Time          `json:"timestamp"`
	Updates   []UpdateReportItem `json:"updates"`
}

// UpdateReportItem represents a single update
type UpdateReportItem struct {
	PackageType        string                 `json:"package_type"`
	PackageName        string                 `json:"package_name"`
	PackageDescription string                 `json:"package_description"`
	CurrentVersion     string                 `json:"current_version"`
	AvailableVersion   string                 `json:"available_version"`
	Severity           string                 `json:"severity"`
	CVEList            []string               `json:"cve_list"`
	KBID               string                 `json:"kb_id"`
	RepositorySource   string                 `json:"repository_source"`
	SizeBytes          int64                  `json:"size_bytes"`
	Metadata           map[string]interface{} `json:"metadata"`
}

// ReportUpdates sends discovered updates to the server
func (c *Client) ReportUpdates(agentID uuid.UUID, report UpdateReport) error {
	url := fmt.Sprintf("%s/api/v1/agents/%s/updates", c.baseURL, agentID)

	body, err := json.Marshal(report)
	if err != nil {
		return err
	}

	req, err := http.NewRequest("POST", url, bytes.NewBuffer(body))
	if err != nil {
		return err
	}
	req.Header.Set("Content-Type", "application/json")
	req.Header.Set("Authorization", "Bearer "+c.token)
	c.addMachineIDHeader(req) // Security: Validate machine binding (v0.1.22+)

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

	if resp.StatusCode != http.StatusOK {
		bodyBytes, _ := io.ReadAll(resp.Body)
		return fmt.Errorf("failed to report updates: %s - %s", resp.Status, string(bodyBytes))
	}

	return nil
}

// MetricsReport represents metrics data (storage, system, CPU, memory)
type MetricsReport struct {
	CommandID string              `json:"command_id"`
	Timestamp time.Time           `json:"timestamp"`
	Metrics   []MetricsReportItem `json:"metrics"`
}

// MetricsReportItem represents a single metric
type MetricsReportItem struct {
	PackageType      string                 `json:"package_type"`
	PackageName      string                 `json:"package_name"`
	CurrentVersion   string                 `json:"current_version"`
	AvailableVersion string                 `json:"available_version"`
	Severity         string                 `json:"severity"`
	RepositorySource string                 `json:"repository_source"`
	Metadata         map[string]interface{} `json:"metadata"`
}

// ReportMetrics sends metrics data to the server
func (c *Client) ReportMetrics(agentID uuid.UUID, report MetricsReport) error {
	url := fmt.Sprintf("%s/api/v1/agents/%s/metrics", c.baseURL, agentID)

	body, err := json.Marshal(report)
	if err != nil {
		return err
	}

	req, err := http.NewRequest("POST", url, bytes.NewBuffer(body))
	if err != nil {
		return err
	}
	req.Header.Set("Content-Type", "application/json")
	req.Header.Set("Authorization", "Bearer "+c.token)
	c.addMachineIDHeader(req) // Security: Validate machine binding (v0.1.22+)

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

	if resp.StatusCode != http.StatusOK {
		bodyBytes, _ := io.ReadAll(resp.Body)
		return fmt.Errorf("failed to report metrics: %s - %s", resp.Status, string(bodyBytes))
	}

	return nil
}

// DockerReport represents Docker image information
type DockerReport struct {
	CommandID string             `json:"command_id"`
	Timestamp time.Time          `json:"timestamp"`
	Images    []DockerReportItem `json:"images"`
}

// DockerReportItem represents a single Docker image
type DockerReportItem struct {
	PackageType      string                 `json:"package_type"`
	PackageName      string                 `json:"package_name"`
	CurrentVersion   string                 `json:"current_version"`
	AvailableVersion string                 `json:"available_version"`
	Severity         string                 `json:"severity"`
	RepositorySource string                 `json:"repository_source"`
	Metadata         map[string]interface{} `json:"metadata"`
}

// ReportDockerImages sends Docker image information to the server
func (c *Client) ReportDockerImages(agentID uuid.UUID, report DockerReport) error {
	url := fmt.Sprintf("%s/api/v1/agents/%s/docker-images", c.baseURL, agentID)

	body, err := json.Marshal(report)
	if err != nil {
		return err
	}

	req, err := http.NewRequest("POST", url, bytes.NewBuffer(body))
	if err != nil {
		return err
	}
	req.Header.Set("Content-Type", "application/json")
	req.Header.Set("Authorization", "Bearer "+c.token)
	c.addMachineIDHeader(req) // Security: Validate machine binding (v0.1.22+)

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

	if resp.StatusCode != http.StatusOK {
		bodyBytes, _ := io.ReadAll(resp.Body)
		return fmt.Errorf("failed to report docker images: %s - %s", resp.Status, string(bodyBytes))
	}

	return nil
}

// ReportStorageMetrics sends storage metrics to the server via dedicated endpoint
func (c *Client) ReportStorageMetrics(agentID uuid.UUID, report models.StorageMetricReport) error {
	url := fmt.Sprintf("%s/api/v1/agents/%s/storage-metrics", c.baseURL, agentID)

	body, err := json.Marshal(report)
	if err != nil {
		return fmt.Errorf("failed to marshal storage metrics: %w", err)
	}

	req, err := http.NewRequest("POST", url, bytes.NewBuffer(body))
	if err != nil {
		return fmt.Errorf("failed to create request: %w", err)
	}
	req.Header.Set("Content-Type", "application/json")
	req.Header.Set("Authorization", "Bearer "+c.token)
	c.addMachineIDHeader(req) // Security: Validate machine binding (v0.1.22+)

	resp, err := c.http.Do(req)
	if err != nil {
		return fmt.Errorf("failed to send request: %w", err)
	}
	defer resp.Body.Close()

	if resp.StatusCode != http.StatusOK {
		bodyBytes, _ := io.ReadAll(resp.Body)
		return fmt.Errorf("failed to report storage metrics: %s - %s", resp.Status, string(bodyBytes))
	}

	return nil
}

// LogReport represents an execution log
type LogReport struct {
	CommandID       string            `json:"command_id"`
	Action          string            `json:"action"`
	Result          string            `json:"result"`
	Stdout          string            `json:"stdout"`
	Stderr          string            `json:"stderr"`
	ExitCode        int               `json:"exit_code"`
	DurationSeconds int               `json:"duration_seconds"`
	Metadata        map[string]string `json:"metadata,omitempty"`
}

// ReportLog sends an execution log to the server
func (c *Client) ReportLog(agentID uuid.UUID, report LogReport) error {
	url := fmt.Sprintf("%s/api/v1/agents/%s/logs", c.baseURL, agentID)

	// Extract subsystem from metadata if present
	subsystem := ""
	if report.Metadata != nil {
		subsystem = report.Metadata["subsystem"]
	}

	// Create UpdateLogRequest with subsystem extracted from metadata
	logRequest := struct {
		CommandID       string `json:"command_id"`
		Action          string `json:"action"`
		Subsystem       string `json:"subsystem,omitempty"`
		Result          string `json:"result"`
		Stdout          string `json:"stdout"`
		Stderr          string `json:"stderr"`
		ExitCode        int    `json:"exit_code"`
		DurationSeconds int    `json:"duration_seconds"`
	}{
		CommandID:       report.CommandID,
		Action:          report.Action,
		Subsystem:       subsystem,
		Result:          report.Result,
		Stdout:          report.Stdout,
		Stderr:          report.Stderr,
		ExitCode:        report.ExitCode,
		DurationSeconds: report.DurationSeconds,
	}

	body, err := json.Marshal(logRequest)
	if err != nil {
		return err
	}

	req, err := http.NewRequest("POST", url, bytes.NewBuffer(body))
	if err != nil {
		return err
	}
	req.Header.Set("Content-Type", "application/json")
	req.Header.Set("Authorization", "Bearer "+c.token)
	c.addMachineIDHeader(req) // Security: Validate machine binding (v0.1.22+)

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

	if resp.StatusCode != http.StatusOK {
		bodyBytes, _ := io.ReadAll(resp.Body)
		return fmt.Errorf("failed to report log: %s - %s", resp.Status, string(bodyBytes))
	}

	return nil
}

// DependencyReport represents a dependency report after dry run
type DependencyReport struct {
	PackageName  string         `json:"package_name"`
	PackageType  string         `json:"package_type"`
	Dependencies []string       `json:"dependencies"`
	UpdateID     string         `json:"update_id"`
	DryRunResult *InstallResult `json:"dry_run_result,omitempty"`
}

// InstallResult represents the result of a package installation attempt
type InstallResult struct {
	Success           bool     `json:"success"`
	ErrorMessage      string   `json:"error_message,omitempty"`
	Stdout            string   `json:"stdout,omitempty"`
	Stderr            string   `json:"stderr,omitempty"`
	ExitCode          int      `json:"exit_code"`
	DurationSeconds   int      `json:"duration_seconds"`
	Action            string   `json:"action,omitempty"`
	PackagesInstalled []string `json:"packages_installed,omitempty"`
	ContainersUpdated []string `json:"containers_updated,omitempty"`
	Dependencies      []string `json:"dependencies,omitempty"`
	IsDryRun          bool     `json:"is_dry_run"`
}

// ReportDependencies sends dependency report to the server
func (c *Client) ReportDependencies(agentID uuid.UUID, report DependencyReport) error {
	url := fmt.Sprintf("%s/api/v1/agents/%s/dependencies", c.baseURL, agentID)

	body, err := json.Marshal(report)
	if err != nil {
		return err
	}

	req, err := http.NewRequest("POST", url, bytes.NewBuffer(body))
	if err != nil {
		return err
	}
	req.Header.Set("Content-Type", "application/json")
	req.Header.Set("Authorization", "Bearer "+c.token)
	c.addMachineIDHeader(req) // Security: Validate machine binding (v0.1.22+)

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

	if resp.StatusCode != http.StatusOK {
		bodyBytes, _ := io.ReadAll(resp.Body)
		return fmt.Errorf("failed to report dependencies: %s - %s", resp.Status, string(bodyBytes))
	}

	return nil
}

// SystemInfoReport represents system information updates
type SystemInfoReport struct {
	Timestamp   time.Time              `json:"timestamp"`
	CPUModel    string                 `json:"cpu_model,omitempty"`
	CPUCores    int                    `json:"cpu_cores,omitempty"`
	CPUThreads  int                    `json:"cpu_threads,omitempty"`
	MemoryTotal uint64                 `json:"memory_total,omitempty"`
	DiskTotal   uint64                 `json:"disk_total,omitempty"`
	DiskUsed    uint64                 `json:"disk_used,omitempty"`
	IPAddress   string                 `json:"ip_address,omitempty"`
	Processes   int                    `json:"processes,omitempty"`
	Uptime      string                 `json:"uptime,omitempty"`
	Metadata    map[string]interface{} `json:"metadata,omitempty"`
}

// ReportSystemInfo sends updated system information to the server
func (c *Client) ReportSystemInfo(agentID uuid.UUID, report SystemInfoReport) error {
	url := fmt.Sprintf("%s/api/v1/agents/%s/system-info", c.baseURL, agentID)

	body, err := json.Marshal(report)
	if err != nil {
		return err
	}

	req, err := http.NewRequest("POST", url, bytes.NewBuffer(body))
	if err != nil {
		return err
	}
	req.Header.Set("Content-Type", "application/json")
	req.Header.Set("Authorization", "Bearer "+c.token)
	c.addMachineIDHeader(req) // Security: Validate machine binding (v0.1.22+)

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

	// Accept 200 OK or 404 Not Found (if endpoint doesn't exist yet)
	if resp.StatusCode != http.StatusOK && resp.StatusCode != http.StatusNotFound {
		bodyBytes, _ := io.ReadAll(resp.Body)
		return fmt.Errorf("failed to report system info: %s - %s", resp.Status, string(bodyBytes))
	}

	return nil
}

// DetectSystem returns basic system information (deprecated, use system.GetSystemInfo instead)
func DetectSystem() (osType, osVersion, osArch string) {
	osType = runtime.GOOS
	osArch = runtime.GOARCH

	// Read OS version
	switch osType {
	case "linux":
		data, _ := os.ReadFile("/etc/os-release")
		if data != nil {
			osVersion = parseOSRelease(data)
		}
	case "windows":
		osVersion = "Windows"
	case "darwin":
		osVersion = "macOS"
	}

	return
}

// AgentInfo represents agent information from the server
type AgentInfo struct {
	ID             string `json:"id"`
	Hostname       string `json:"hostname"`
	CurrentVersion string `json:"current_version"`
	OSType         string `json:"os_type"`
	OSVersion      string `json:"os_version"`
	OSArchitecture string `json:"os_architecture"`
	LastCheckIn    string `json:"last_check_in"`
}

// GetAgent retrieves agent information from the server
func (c *Client) GetAgent(agentID string) (*AgentInfo, error) {
	url := fmt.Sprintf("%s/api/v1/agents/%s", c.baseURL, agentID)

	req, err := http.NewRequest("GET", url, nil)
	if err != nil {
		return nil, fmt.Errorf("failed to create request: %w", err)
	}

	req.Header.Set("Authorization", "Bearer "+c.token)
	req.Header.Set("Content-Type", "application/json")
	c.addMachineIDHeader(req) // Security: Validate machine binding (v0.1.22+)

	resp, err := c.http.Do(req)
	if err != nil {
		return nil, fmt.Errorf("failed to send request: %w", err)
	}
	defer resp.Body.Close()

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

	var agent AgentInfo
	if err := json.NewDecoder(resp.Body).Decode(&agent); err != nil {
		return nil, fmt.Errorf("failed to decode response: %w", err)
	}

	return &agent, nil
}

// parseOSRelease parses /etc/os-release to get proper distro name
func parseOSRelease(data []byte) string {
	lines := strings.Split(string(data), "\n")
	id := ""
	prettyName := ""
	version := ""

	for _, line := range lines {
		if strings.HasPrefix(line, "ID=") {
			id = strings.Trim(strings.TrimPrefix(line, "ID="), "\"")
		}
		if strings.HasPrefix(line, "PRETTY_NAME=") {
			prettyName = strings.Trim(strings.TrimPrefix(line, "PRETTY_NAME="), "\"")
		}
		if strings.HasPrefix(line, "VERSION_ID=") {
			version = strings.Trim(strings.TrimPrefix(line, "VERSION_ID="), "\"")
		}
	}

	// Prefer PRETTY_NAME if available
	if prettyName != "" {
		return prettyName
	}

	// Fall back to ID + VERSION
	if id != "" {
		if version != "" {
			return strings.Title(id) + " " + version
		}
		return strings.Title(id)
	}

	return "Linux"
}

// AgentConfigResponse contains subsystem configuration from server
type AgentConfigResponse struct {
	Subsystems map[string]interface{} `json:"subsystems"`
	Version    int64                  `json:"version"`
}

// GetConfig retrieves current subsystem configuration from server
func (c *Client) GetConfig(agentID uuid.UUID) (*AgentConfigResponse, error) {
	url := fmt.Sprintf("%s/api/v1/agents/%s/config", c.baseURL, agentID)

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

	req.Header.Set("Authorization", "Bearer "+c.token)
	c.addMachineIDHeader(req) // Security: Validate machine binding (v0.1.22+)

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

	if resp.StatusCode != http.StatusOK {
		bodyBytes, _ := io.ReadAll(resp.Body)
		return nil, fmt.Errorf("failed to get config: %s - %s", resp.Status, string(bodyBytes))
	}

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

	return &result, nil
}

// ActivePublicKeyEntry represents a single active key from the server's /api/v1/public-keys endpoint
type ActivePublicKeyEntry struct {
	KeyID     string `json:"key_id"`
	PublicKey string `json:"public_key"`
	IsPrimary bool   `json:"is_primary"`
	Version   int    `json:"version"`
	Algorithm string `json:"algorithm"`
}

// GetActivePublicKeys fetches all currently active public keys from the server.
// Used during key rotation to pre-cache new keys before they become the primary signing key.
func (c *Client) GetActivePublicKeys(serverURL string) ([]ActivePublicKeyEntry, error) {
	url := fmt.Sprintf("%s/api/v1/public-keys", serverURL)
	resp, err := c.http.Get(url)
	if err != nil {
		return nil, fmt.Errorf("failed to fetch active public keys: %w", err)
	}
	defer resp.Body.Close()
	if resp.StatusCode != http.StatusOK {
		body, _ := io.ReadAll(resp.Body)
		return nil, fmt.Errorf("server returned %d: %s", resp.StatusCode, string(body))
	}
	var keys []ActivePublicKeyEntry
	if err := json.NewDecoder(resp.Body).Decode(&keys); err != nil {
		return nil, fmt.Errorf("failed to decode public keys: %w", err)
	}
	return keys, nil
}
