package client

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

	"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
}

// NewClient creates a new API client
func NewClient(baseURL, token string) *Client {
	return &Client{
		baseURL: baseURL,
		token:   token,
		http: &http.Client{
			Timeout: 30 * time.Second,
		},
	}
}

// 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"`
	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)

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

	httpReq, err := http.NewRequest("POST", url, bytes.NewBuffer(body))
	if err != nil {
		return nil, err
	}
	httpReq.Header.Set("Content-Type", "application/json")

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

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

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

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

	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"`
}

// 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) error {
	url := fmt.Sprintf("%s/api/v1/agents/renew", c.baseURL)

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

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

	httpReq, err := http.NewRequest("POST", url, bytes.NewBuffer(body))
	if err != nil {
		return err
	}
	httpReq.Header.Set("Content-Type", "application/json")

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

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

	var result TokenRenewalResponse
	if err := json.NewDecoder(resp.Body).Decode(&result); err != nil {
		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"`
}

// CommandsResponse contains pending commands
type CommandsResponse struct {
	Commands     []Command           `json:"commands"`
	RapidPolling *RapidPollingConfig `json:"rapid_polling,omitempty"`
}

// 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
}

// GetCommands retrieves pending commands from the server
// Optionally sends lightweight system metrics in the request
func (c *Client) GetCommands(agentID uuid.UUID, metrics *SystemMetrics) ([]Command, 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)

	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.Commands, 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)

	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
}

// 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"`
}

// 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)

	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)

	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)

	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)

	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
}

// 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"
}
