//! Agent configuration management
//!
//! Supports three configuration sources (in priority order):
//! 1. Embedded config (magic bytes appended to executable)
//! 2. Config file (guruconnect.toml or %ProgramData%\GuruConnect\agent.toml)
//! 3. Environment variables (fallback)

use anyhow::{anyhow, Context, Result};
use serde::{Deserialize, Serialize};
use std::io::{Read, Seek, SeekFrom};
use std::path::PathBuf;
use tracing::info;
use uuid::Uuid;

/// Magic marker for embedded configuration (10 bytes)
const MAGIC_MARKER: &[u8] = b"GURUCONFIG";

/// Embedded configuration data (appended to executable)
#[derive(Debug, Clone, Serialize, Deserialize)]
pub struct EmbeddedConfig {
    /// Server WebSocket URL
    pub server_url: String,
    /// API key for authentication
    pub api_key: String,
    /// Company/organization name
    #[serde(default)]
    pub company: Option<String>,
    /// Site/location name
    #[serde(default)]
    pub site: Option<String>,
    /// Tags for categorization
    #[serde(default)]
    pub tags: Vec<String>,
}

/// Detected run mode based on filename
#[derive(Debug, Clone, PartialEq)]
pub enum RunMode {
    /// Viewer-only installation (filename contains "Viewer")
    Viewer,
    /// Temporary support session (filename contains 6-digit code)
    TempSupport(String),
    /// Permanent agent with embedded config
    PermanentAgent,
    /// Unknown/default mode
    Default,
}

/// Agent configuration
#[derive(Debug, Clone, Serialize, Deserialize)]
pub struct Config {
    /// Server WebSocket URL (e.g., wss://connect.example.com/ws)
    pub server_url: String,

    /// Agent API key for authentication
    pub api_key: String,

    /// Unique agent identifier (generated on first run)
    #[serde(default = "generate_agent_id")]
    pub agent_id: String,

    /// Optional hostname override
    pub hostname_override: Option<String>,

    /// Company/organization name (from embedded config)
    #[serde(default)]
    pub company: Option<String>,

    /// Site/location name (from embedded config)
    #[serde(default)]
    pub site: Option<String>,

    /// Tags for categorization (from embedded config)
    #[serde(default)]
    pub tags: Vec<String>,

    /// Support code for one-time support sessions (set via command line or filename)
    #[serde(skip)]
    pub support_code: Option<String>,

    /// Capture settings
    #[serde(default)]
    pub capture: CaptureConfig,

    /// Encoding settings
    #[serde(default)]
    pub encoding: EncodingConfig,
}

fn generate_agent_id() -> String {
    Uuid::new_v4().to_string()
}

#[derive(Debug, Clone, Serialize, Deserialize)]
pub struct CaptureConfig {
    /// Target frames per second (1-60)
    #[serde(default = "default_fps")]
    pub fps: u32,

    /// Use DXGI Desktop Duplication (recommended)
    #[serde(default = "default_true")]
    pub use_dxgi: bool,

    /// Fall back to GDI if DXGI fails
    #[serde(default = "default_true")]
    pub gdi_fallback: bool,
}

#[derive(Debug, Clone, Serialize, Deserialize)]
pub struct EncodingConfig {
    /// Preferred codec (auto, raw, vp9, h264)
    #[serde(default = "default_codec")]
    pub codec: String,

    /// Quality (1-100, higher = better quality, more bandwidth)
    #[serde(default = "default_quality")]
    pub quality: u32,

    /// Use hardware encoding if available
    #[serde(default = "default_true")]
    pub hardware_encoding: bool,
}

fn default_fps() -> u32 {
    30
}

fn default_true() -> bool {
    true
}

fn default_codec() -> String {
    "auto".to_string()
}

fn default_quality() -> u32 {
    75
}

impl Default for CaptureConfig {
    fn default() -> Self {
        Self {
            fps: default_fps(),
            use_dxgi: true,
            gdi_fallback: true,
        }
    }
}

impl Default for EncodingConfig {
    fn default() -> Self {
        Self {
            codec: default_codec(),
            quality: default_quality(),
            hardware_encoding: true,
        }
    }
}

impl Config {
    /// Detect run mode from executable filename
    pub fn detect_run_mode() -> RunMode {
        let exe_path = match std::env::current_exe() {
            Ok(p) => p,
            Err(_) => return RunMode::Default,
        };

        let filename = match exe_path.file_stem() {
            Some(s) => s.to_string_lossy().to_string(),
            None => return RunMode::Default,
        };

        let filename_lower = filename.to_lowercase();

        // Check for viewer mode
        if filename_lower.contains("viewer") {
            info!("Detected viewer mode from filename: {}", filename);
            return RunMode::Viewer;
        }

        // Check for support code in filename (6-digit number)
        if let Some(code) = Self::extract_support_code(&filename) {
            info!("Detected support code from filename: {}", code);
            return RunMode::TempSupport(code);
        }

        // Check for embedded config
        if Self::has_embedded_config() {
            info!("Detected embedded config in executable");
            return RunMode::PermanentAgent;
        }

        RunMode::Default
    }

    /// Extract 6-digit support code from filename
    fn extract_support_code(filename: &str) -> Option<String> {
        // Look for patterns like "GuruConnect-123456" or "GuruConnect_123456"
        for part in filename.split(['-', '_', '.']) {
            let trimmed = part.trim();
            if trimmed.len() == 6 && trimmed.chars().all(|c| c.is_ascii_digit()) {
                return Some(trimmed.to_string());
            }
        }

        // Check if last 6 characters are all digits
        if filename.len() >= 6 {
            let last_six = &filename[filename.len() - 6..];
            if last_six.chars().all(|c| c.is_ascii_digit()) {
                return Some(last_six.to_string());
            }
        }

        None
    }

    /// Check if embedded configuration exists in the executable
    pub fn has_embedded_config() -> bool {
        Self::read_embedded_config().is_ok()
    }

    /// Read embedded configuration from the executable
    pub fn read_embedded_config() -> Result<EmbeddedConfig> {
        let exe_path = std::env::current_exe().context("Failed to get current executable path")?;

        let mut file =
            std::fs::File::open(&exe_path).context("Failed to open executable for reading")?;

        let file_size = file.metadata()?.len();
        if file_size < (MAGIC_MARKER.len() + 4) as u64 {
            return Err(anyhow!("File too small to contain embedded config"));
        }

        // Read the last part of the file to find magic marker
        // Structure: [PE binary][GURUCONFIG][length:u32][json config]
        // We need to search backwards from the end

        // Read last 64KB (should be more than enough for config)
        let search_size = std::cmp::min(65536, file_size as usize);
        let search_start = file_size - search_size as u64;

        file.seek(SeekFrom::Start(search_start))?;
        let mut buffer = vec![0u8; search_size];
        file.read_exact(&mut buffer)?;

        // Find magic marker
        let marker_pos = buffer
            .windows(MAGIC_MARKER.len())
            .rposition(|window| window == MAGIC_MARKER)
            .ok_or_else(|| anyhow!("Magic marker not found"))?;

        // Read config length (4 bytes after marker)
        let length_start = marker_pos + MAGIC_MARKER.len();
        if length_start + 4 > buffer.len() {
            return Err(anyhow!("Invalid embedded config: length field truncated"));
        }

        let config_length = u32::from_le_bytes([
            buffer[length_start],
            buffer[length_start + 1],
            buffer[length_start + 2],
            buffer[length_start + 3],
        ]) as usize;

        // Read config data
        let config_start = length_start + 4;
        if config_start + config_length > buffer.len() {
            return Err(anyhow!("Invalid embedded config: data truncated"));
        }

        let config_bytes = &buffer[config_start..config_start + config_length];
        let config: EmbeddedConfig =
            serde_json::from_slice(config_bytes).context("Failed to parse embedded config JSON")?;

        info!(
            "Loaded embedded config: server={}, company={:?}",
            config.server_url, config.company
        );

        Ok(config)
    }

    /// Check if an explicit agent configuration file exists
    /// This returns true only if there's a real config file, not generated defaults
    pub fn has_agent_config() -> bool {
        // Check for embedded config first
        if Self::has_embedded_config() {
            return true;
        }

        // Check for config in current directory
        let local_config = PathBuf::from("guruconnect.toml");
        if local_config.exists() {
            return true;
        }

        // Check in program data directory (Windows)
        #[cfg(windows)]
        {
            if let Ok(program_data) = std::env::var("ProgramData") {
                let path = PathBuf::from(program_data)
                    .join("GuruConnect")
                    .join("agent.toml");
                if path.exists() {
                    return true;
                }
            }
        }

        false
    }

    /// Load configuration from embedded config, file, or environment
    pub fn load() -> Result<Self> {
        // Priority 1: Try loading from embedded config
        if let Ok(embedded) = Self::read_embedded_config() {
            info!("Using embedded configuration");
            let config = Config {
                server_url: embedded.server_url,
                api_key: embedded.api_key,
                agent_id: generate_agent_id(),
                hostname_override: None,
                company: embedded.company,
                site: embedded.site,
                tags: embedded.tags,
                support_code: None,
                capture: CaptureConfig::default(),
                encoding: EncodingConfig::default(),
            };

            // Save to file for persistence (so agent_id is preserved)
            let _ = config.save();
            return Ok(config);
        }

        // Priority 2: Try loading from config file
        let config_path = Self::config_path();

        if config_path.exists() {
            let contents = std::fs::read_to_string(&config_path)
                .with_context(|| format!("Failed to read config from {:?}", config_path))?;

            let mut config: Config =
                toml::from_str(&contents).with_context(|| "Failed to parse config file")?;

            // Ensure agent_id is set and saved
            if config.agent_id.is_empty() {
                config.agent_id = generate_agent_id();
                let _ = config.save();
            }

            // support_code is always None when loading from file (set via CLI)
            config.support_code = None;

            return Ok(config);
        }

        // Priority 3: Fall back to environment variables
        let server_url = std::env::var("GURUCONNECT_SERVER_URL")
            .unwrap_or_else(|_| "wss://connect.azcomputerguru.com/ws/agent".to_string());

        let api_key =
            std::env::var("GURUCONNECT_API_KEY").unwrap_or_else(|_| "dev-key".to_string());

        let agent_id =
            std::env::var("GURUCONNECT_AGENT_ID").unwrap_or_else(|_| generate_agent_id());

        let config = Config {
            server_url,
            api_key,
            agent_id,
            hostname_override: std::env::var("GURUCONNECT_HOSTNAME").ok(),
            company: None,
            site: None,
            tags: Vec::new(),
            support_code: None,
            capture: CaptureConfig::default(),
            encoding: EncodingConfig::default(),
        };

        // Save config with generated agent_id for persistence
        let _ = config.save();

        Ok(config)
    }

    /// Get the configuration file path
    fn config_path() -> PathBuf {
        // Check for config in current directory first
        let local_config = PathBuf::from("guruconnect.toml");
        if local_config.exists() {
            return local_config;
        }

        // Check in program data directory (Windows)
        #[cfg(windows)]
        {
            if let Ok(program_data) = std::env::var("ProgramData") {
                let path = PathBuf::from(program_data)
                    .join("GuruConnect")
                    .join("agent.toml");
                if path.exists() {
                    return path;
                }
            }
        }

        // Default to local config
        local_config
    }

    /// Get the hostname to use
    pub fn hostname(&self) -> String {
        self.hostname_override.clone().unwrap_or_else(|| {
            hostname::get()
                .map(|h| h.to_string_lossy().to_string())
                .unwrap_or_else(|_| "unknown".to_string())
        })
    }

    /// Save current configuration to file
    pub fn save(&self) -> Result<()> {
        let config_path = Self::config_path();

        // Ensure parent directory exists
        if let Some(parent) = config_path.parent() {
            std::fs::create_dir_all(parent)?;
        }

        let contents = toml::to_string_pretty(self)?;
        std::fs::write(&config_path, contents)?;

        Ok(())
    }
}

/// Example configuration file content
// Retained for documentation / config-template generation.
#[allow(dead_code)]
pub fn example_config() -> &'static str {
    r#"# GuruConnect Agent Configuration

# Server connection
server_url = "wss://connect.example.com/ws"
api_key = "your-agent-api-key"
agent_id = "auto-generated-uuid"

# Optional: override hostname
# hostname_override = "custom-hostname"

[capture]
fps = 30
use_dxgi = true
gdi_fallback = true

[encoding]
codec = "auto"      # auto, raw, vp9, h264
quality = 75        # 1-100
hardware_encoding = true
"#
}
