use std::process::Command;
use std::time::{Duration, Instant};
use tokio::time::timeout;
use crate::types::Result;
use crate::utils::{detect_package_manager, read_package_json, package_json_exists, is_pnpm_workspace_root, log::*};

#[derive(Debug, Clone)]
struct PackageUpgradeInfo {
    name: String,
    old_version: Option<String>,
    new_version: Option<String>,
    upgrade_time: Duration,
}

#[derive(Debug)]
struct UpgradeResult {
    successful_packages: Vec<PackageUpgradeInfo>,
    failed_packages: Vec<String>,
    total_time: Duration,
    warnings: Vec<String>,
    executed_commands: Vec<String>,
}

#[derive(Debug)]
enum UpgradeError {
    NetworkError(String),
    PermissionError(String),
    DependencyConflict(String),
    Timeout(String),
    InvalidDependency(String),
    Unknown(String),
}

impl std::fmt::Display for UpgradeError {
    fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
        let message = match self {
            UpgradeError::NetworkError(msg) => format!("Network error: {}", msg),
            UpgradeError::PermissionError(msg) => format!("Permission error: {}", msg),
            UpgradeError::DependencyConflict(msg) => format!("Dependency conflict: {}", msg),
            UpgradeError::Timeout(msg) => format!("Timeout error: {}", msg),
            UpgradeError::InvalidDependency(msg) => format!("Invalid dependency: {}", msg),
            UpgradeError::Unknown(msg) => format!("Unknown error: {}", msg),
        };
        write!(f, "{}", message)
    }
}

impl std::error::Error for UpgradeError {}

const MAX_RETRIES: u32 = 3;
const UPGRADE_TIMEOUT: Duration = Duration::from_secs(300); // 5 minutes timeout

/// Validates dependency name format
fn is_valid_dependency_name(name: &str) -> bool {
    // Basic validation: not empty, no spaces, valid npm package name pattern
    !name.is_empty() 
        && !name.contains(' ')
        && !name.starts_with('.')
        && !name.starts_with('_')
        && name.chars().all(|c| c.is_alphanumeric() || c == '-' || c == '_' || c == '.' || c == '/' || c == '@')
}

/// Categorizes error based on stderr output
fn categorize_error(stderr: &str) -> UpgradeError {
    let stderr_lower = stderr.to_lowercase();
    
    if stderr_lower.contains("network") || stderr_lower.contains("timeout") || stderr_lower.contains("enotfound") || stderr_lower.contains("econnreset") {
        UpgradeError::NetworkError(stderr.to_string())
    } else if stderr_lower.contains("permission") || stderr_lower.contains("eacces") || stderr_lower.contains("eperm") {
        UpgradeError::PermissionError(stderr.to_string())
    } else if stderr_lower.contains("conflict") || stderr_lower.contains("peer dep") || stderr_lower.contains("unable to resolve dependency") {
        UpgradeError::DependencyConflict(stderr.to_string())
    } else if stderr_lower.contains("404") || stderr_lower.contains("not found") || stderr_lower.contains("no such package") {
        UpgradeError::InvalidDependency(stderr.to_string())
    } else {
        UpgradeError::Unknown(stderr.to_string())
    }
}

/// Checks if error is retryable (network-related)
fn is_retryable_error(error: &UpgradeError) -> bool {
    matches!(error, UpgradeError::NetworkError(_))
}

/// Generates manual command for user to run
fn generate_manual_command(package_manager: &crate::types::PackageManager, deps: &[String], is_dev: bool) -> String {
    let mut cmd = package_manager.install_command().to_string();
    let mut args = package_manager.install_args(is_dev);
    if matches!(package_manager, crate::types::PackageManager::Pnpm) && is_pnpm_workspace_root() {
        args.push("-w");
    }
    
    for arg in args {
        cmd.push(' ');
        cmd.push_str(arg);
    }
    
    match package_manager {
        crate::types::PackageManager::Rush => {
            for dep in deps {
                cmd.push_str(" -p ");
                cmd.push_str(dep);
            }
        }
        _ => {
            for dep in deps {
                cmd.push(' ');
                cmd.push_str(dep);
            }
        }
    }
    
    cmd
}

/// Parses package manager output to extract upgrade information
fn parse_upgrade_output(output: &str, package_manager: &crate::types::PackageManager) -> (Vec<PackageUpgradeInfo>, Vec<String>) {
    let mut upgrades = Vec::new();
    let mut warnings = Vec::new();
    
    // Parse different package manager outputs
    match package_manager {
        crate::types::PackageManager::Npm => {
            // Parse npm output for version changes
            for line in output.lines() {
                if line.contains("added") || line.contains("updated") || line.contains("changed") {
                    // Extract package info from npm output
                    if let Some(package_info) = extract_npm_package_info(line) {
                        upgrades.push(package_info);
                    }
                }
                if line.contains("WARN") || line.contains("deprecated") {
                    warnings.push(line.to_string());
                }
            }
        }
        crate::types::PackageManager::Yarn => {
            // Parse yarn output
            for line in output.lines() {
                if line.contains("✨") || line.contains("success") {
                    if let Some(package_info) = extract_yarn_package_info(line) {
                        upgrades.push(package_info);
                    }
                }
                if line.contains("warning") {
                    warnings.push(line.to_string());
                }
            }
        }
        crate::types::PackageManager::Pnpm => {
            // Parse pnpm output
            for line in output.lines() {
                if line.contains("+") || line.contains("~") {
                    if let Some(package_info) = extract_pnpm_package_info(line) {
                        upgrades.push(package_info);
                    }
                }
                if line.contains("WARN") {
                    warnings.push(line.to_string());
                }
            }
        }
        crate::types::PackageManager::Rush => {
             // Rush calls underlying package manager, but output might be wrapped.
             // For now, we try to detect generic success or reuse pnpm/npm parsers if output looks familiar.
             // Rush add output is verbose.
             // We'll just collect warnings for now.
             for line in output.lines() {
                 if line.contains("WARN") || line.contains("warning") {
                     warnings.push(line.to_string());
                 }
             }
        }
    }
    
    (upgrades, warnings)
}

/// Extract package info from npm output line
fn extract_npm_package_info(line: &str) -> Option<PackageUpgradeInfo> {
    // Simple parsing - can be enhanced based on actual npm output format
    if let Some(start) = line.find("+ ") {
        let package_part = &line[start + 2..];
        if let Some(at_pos) = package_part.find('@') {
            let name = package_part[..at_pos].to_string();
            let version = package_part[at_pos + 1..].split_whitespace().next()?.to_string();
            return Some(PackageUpgradeInfo {
                name,
                old_version: None, // npm doesn't easily show old version in install output
                new_version: Some(version),
                upgrade_time: Duration::from_secs(0), // Will be set by caller
            });
        }
    }
    None
}

/// Extract package info from yarn output line
fn extract_yarn_package_info(line: &str) -> Option<PackageUpgradeInfo> {
    // Parse yarn output format
    if line.contains("✨") && line.contains("Done") {
        // Yarn typically shows summary, individual packages are harder to parse
        return None;
    }
    None
}

/// Extract package info from pnpm output line
fn extract_pnpm_package_info(line: &str) -> Option<PackageUpgradeInfo> {
    // Parse pnpm output format
    if line.starts_with("+") {
        let parts: Vec<&str> = line.split_whitespace().collect();
        if parts.len() >= 2 {
            let package_with_version = parts[1];
            if let Some(at_pos) = package_with_version.rfind('@') {
                let name = package_with_version[..at_pos].to_string();
                let version = package_with_version[at_pos + 1..].to_string();
                return Some(PackageUpgradeInfo {
                    name,
                    old_version: None,
                    new_version: Some(version),
                    upgrade_time: Duration::from_secs(0),
                });
            }
        }
    }
    None
}

/// Display upgrade summary
fn display_upgrade_summary(result: &UpgradeResult, dep_type: &str) {
    if !result.successful_packages.is_empty() {
        success(&format!("Upgrade Summary ({})", dep_type));
        info(&format!("  ✓ Successfully upgraded: {} packages", result.successful_packages.len()));
        info(&format!("  ⏱ Total time: {}s", result.total_time.as_secs_f64()));
        
        for package in &result.successful_packages {
            let version_info = match (&package.old_version, &package.new_version) {
                (Some(old), Some(new)) => format!("  {} {} → {}", package.name, old, new),
                (None, Some(new)) => format!("  {} → {}", package.name, new),
                _ => format!("  • {}: version info unavailable", package.name),
            };
            info(&version_info);
        }
        
        if !result.warnings.is_empty() {
            warning(&format!("⚠ Warnings: {}", result.warnings.len()));
            for (i, warn) in result.warnings.iter().enumerate().take(3) {
                warning(&format!("  {}. {}", i + 1, warn.trim()));
            }
            if result.warnings.len() > 3 {
                warning(&format!("  ... {} more warnings", result.warnings.len() - 3));
            }
        }
    }
    
    if !result.executed_commands.is_empty() {
        info("Executed commands:");
        for command in &result.executed_commands {
            info(&format!("  - {}", command));
        }
    }
    
    if !result.failed_packages.is_empty() {
        error(&format!("✗ Failed upgrades: {}", result.failed_packages.len()));
        for package in &result.failed_packages {
            error(&format!("  • {}", package));
        }
    }
}

pub async fn execute(prod_only: bool, dev_only: bool) -> Result<()> {
    // Check if package.json exists
    if !package_json_exists() {
        return Err("package.json not found in current directory".into());
    }

    // Read package.json
    let package_json = read_package_json()?;
    
    // Detect package manager
    let package_manager = detect_package_manager()?;
    
    info(&format!("Detected package manager: {:?}", package_manager));
    
    // Determine what to upgrade based on flags
    let upgrade_prod = !dev_only;  // upgrade prod unless dev_only is specified
    let upgrade_dev = !prod_only;  // upgrade dev unless prod_only is specified
    
    // Collect all dependencies to upgrade
    let mut deps_to_upgrade = Vec::new();
    
    // Add regular dependencies with validation and skip monorepo workspace deps
    if upgrade_prod {
        if let Some(deps) = &package_json.dependencies {
            for (name, version) in deps {
                // Skip monorepo workspace dependencies like "workspace:*"
                if version.starts_with("workspace:") {
                    info(&format!("Skipping workspace dependency '{}' ({})", name, version));
                    continue;
                }
                if is_valid_dependency_name(name) {
                    deps_to_upgrade.push((name.clone(), false));
                } else {
                    warning(&format!("Skipping invalid dependency '{}'", name));
                }
            }
        }
    }
    
    // Add dev dependencies with validation and skip monorepo workspace deps
    if upgrade_dev {
        if let Some(dev_deps) = &package_json.dev_dependencies {
            for (name, version) in dev_deps {
                // Skip monorepo workspace dependencies like "workspace:*"
                if version.starts_with("workspace:") {
                    info(&format!("Skipping workspace dependency '{}' ({})", name, version));
                    continue;
                }
                if is_valid_dependency_name(name) {
                    deps_to_upgrade.push((name.clone(), true));
                } else {
                    warning(&format!("Skipping invalid dependency '{}'", name));
                }
            }
        }
    }
    
    if deps_to_upgrade.is_empty() {
        warning("No dependencies to upgrade");
        return Ok(());
    }
    
    // Group dependencies by type (regular vs dev)
    let mut regular_deps = Vec::new();
    let mut dev_deps = Vec::new();
    
    for (name, is_dev) in deps_to_upgrade {
        if is_dev {
            dev_deps.push(name);
        } else {
            regular_deps.push(name);
        }
    }
    
    // Log upgrade plan
    let total_deps = regular_deps.len() + dev_deps.len();
    info("Upgrade Plan:");
    if !regular_deps.is_empty() {
        info(&format!("  Production dependencies: {}", regular_deps.len()));
        debug(&format!("    Packages: {}", regular_deps.join(", ")));
    }
    if !dev_deps.is_empty() {
        info(&format!("  Development dependencies: {}", dev_deps.len()));
        debug(&format!("    Packages: {}", dev_deps.join(", ")));
    }
    info(&format!("  Total: {} packages", total_deps));
    
    let mut has_errors = false;
    let mut error_messages = Vec::new();
    let overall_start = Instant::now();
    
    // Upgrade regular dependencies
    if !regular_deps.is_empty() {
        info(&format!("🚀 Starting production dependencies upgrade ({} packages)...", regular_deps.len()));
        match upgrade_packages_with_progress(&package_manager, &regular_deps, false).await {
            Ok(result) => {
                display_upgrade_summary(&result, "production");
                has_errors = !result.failed_packages.is_empty();
            }
            Err(e) => {
                has_errors = true;
                error_messages.push(format!("Production dependencies upgrade failed: {}", e));
            }
        }
    }
    
    // Upgrade dev dependencies
    if !dev_deps.is_empty() {
        info(&format!("🚀 Starting development dependencies upgrade ({} packages)...", dev_deps.len()));
        match upgrade_packages_with_progress(&package_manager, &dev_deps, true).await {
            Ok(result) => {
                display_upgrade_summary(&result, "development");
                has_errors = !result.failed_packages.is_empty();
            }
            Err(e) => {
                has_errors = true;
                error_messages.push(format!("Development dependencies upgrade failed: {}", e));
            }
        }
    }
    
    // Overall summary
    let total_time = overall_start.elapsed();
    if !has_errors {
        success(&format!("All upgrades completed in {}s", total_time.as_secs_f64()));
        info("💡 Recommendation: Run 'npm audit' to check for security vulnerabilities");
    }
    
    if has_errors {
         for msg in &error_messages {
             crate::utils::log::error(msg);
         }
        return Err(format!("Some or all dependencies upgrade failed: {}", error_messages.join("; ")).into());
     }
    
    Ok(())
}

async fn upgrade_packages_with_progress(
    package_manager: &crate::types::PackageManager,
    deps: &[String],
    is_dev: bool,
) -> Result<UpgradeResult> {
    // Batch construct '@latest' list and upgrade in one command
    let deps_with_versions: Vec<String> = deps.iter().map(|d| format!("{}@latest", d)).collect();
    upgrade_dependencies_with_retry(package_manager, &deps_with_versions, is_dev).await
}

async fn upgrade_dependencies_with_retry(
    package_manager: &crate::types::PackageManager,
    deps: &[String],
    is_dev: bool,
) -> Result<UpgradeResult> {
    let dep_type = if is_dev { "development" } else { "production" };
    let spinner = LoadingSpinner::new(&format!("Upgrading {} {} dependencies...", deps.len(), dep_type));
    
    // Generate the command that will be executed
    let executed_command = generate_manual_command(package_manager, deps, is_dev);
    
    let mut last_error = None;
    let start_time = Instant::now();
    
    for attempt in 1..=MAX_RETRIES {
        info(&format!("🔄 Attempt {} to upgrade {} dependencies...", attempt, dep_type));
        
        match upgrade_dependencies_once(package_manager, deps, is_dev).await {
            Ok(output) => {
                let total_time = start_time.elapsed();
                spinner.finish_with_success(&format!("{} dependencies upgrade successful", dep_type));
                
                // Parse the output to extract upgrade information
                let (successful_packages, warnings) = parse_upgrade_output(&output, package_manager);
                
                let result = UpgradeResult {
                    successful_packages,
                    failed_packages: Vec::new(),
                    total_time,
                    warnings,
                    executed_commands: vec![executed_command],
                };
                
                return Ok(result);
            }
            Err(e) => {
                last_error = Some(e);
                
                if attempt < MAX_RETRIES && is_retryable_error(last_error.as_ref().unwrap()) {
                    warning(&format!("Attempt {} failed, retrying... ({})", attempt, last_error.as_ref().unwrap()));
                    tokio::time::sleep(Duration::from_secs(2 * attempt as u64)).await; // Progressive delay
                } else {
                    break;
                }
            }
        }
    }
    
    // All retries failed
    let error = last_error.unwrap();
    spinner.finish_with_error(&format!("{} dependencies upgrade failed", dep_type));
    
    // Provide specific guidance based on error type
    crate::utils::log::error(&format!("Error detail: {}", error));
    match &error {
        UpgradeError::NetworkError(_) => {
            crate::utils::log::error("Tip: Check network connection or try again later");
        }
        UpgradeError::PermissionError(_) => {
            crate::utils::log::error("Tip: Check file permissions or use sudo (if appropriate)");
        }
        UpgradeError::DependencyConflict(_) => {
            crate::utils::log::error("Tip: Resolve dependency conflicts; you may need to align versions");
        }
        UpgradeError::InvalidDependency(_) => {
            crate::utils::log::error("Tip: Verify dependency names and that packages exist");
        }
        _ => {
            crate::utils::log::error("Tip: Review the error above and run the manual command");
        }
    }
    
    // Print manual command for user to run
    crate::utils::log::error(&format!("Manual command: {}", executed_command));
    
    Err(format!("Failed to upgrade {} dependencies after {} attempts", dep_type, MAX_RETRIES).into())
}

async fn upgrade_dependencies_once(
    package_manager: &crate::types::PackageManager,
    deps: &[String],
    is_dev: bool,
) -> std::result::Result<String, UpgradeError> {
    // In test/integration environments, allow simulating success to avoid real package manager calls
    if std::env::var("L_TOOL_FAKE_INSTALL").is_ok() {
        let dep_type = if is_dev { "dev" } else { "prod" };
        let simulated_output = match package_manager {
            crate::types::PackageManager::Npm => format!(
                "added {} packages for {} dependencies",
                deps.len(), dep_type
            ),
            crate::types::PackageManager::Yarn => format!(
                "success Saved {} new dependencies for {}",
                deps.len(), dep_type
            ),
            crate::types::PackageManager::Pnpm => deps
                .iter()
                .map(|d| format!("+ {}", d))
                .collect::<Vec<_>>()
                .join("\n"),
            crate::types::PackageManager::Rush => format!(
                "Successfully added {} packages",
                deps.len()
            ),
        };
        return Ok(simulated_output);
    }

    let mut args = package_manager.install_args(is_dev);
    if matches!(package_manager, crate::types::PackageManager::Pnpm) && is_pnpm_workspace_root() {
        args.push("-w");
    }
    
    match package_manager {
        crate::types::PackageManager::Rush => {
            for dep in deps {
                args.push("-p");
                args.push(dep);
            }
        }
        _ => {
            for dep in deps {
                args.push(dep);
            }
        }
    }
    
    // Run command with timeout
    let command_future = async {
        Command::new(package_manager.install_command())
            .args(&args)
            .output()
    };
    
    let output = match timeout(UPGRADE_TIMEOUT, command_future).await {
        Ok(Ok(output)) => output,
        Ok(Err(e)) => return Err(UpgradeError::Unknown(format!("Command execution failed: {}", e))),
        Err(_) => return Err(UpgradeError::Timeout("Upgrade timed out".to_string())),
    };
    
    if !output.status.success() {
        let stderr = String::from_utf8_lossy(&output.stderr);
        return Err(categorize_error(&stderr));
    }
    
    // Return stdout for parsing
    Ok(String::from_utf8_lossy(&output.stdout).to_string())
}

#[cfg(test)]
mod tests {
    use super::*;
    use tempfile::TempDir;

    #[test]
    fn test_generate_manual_command_rush() {
        let pm = crate::types::PackageManager::Rush;
        let deps = vec!["react".to_string(), "vue".to_string()];
        let cmd = generate_manual_command(&pm, &deps, false);
        assert_eq!(cmd, "rush add -p react -p vue");
        
        let cmd_dev = generate_manual_command(&pm, &deps, true);
        assert_eq!(cmd_dev, "rush add --dev -p react -p vue");
    }

    #[tokio::test]
    async fn test_upgrade_no_package_json() {
        let temp_dir = TempDir::new().unwrap();
        let _guard = std::env::set_current_dir(temp_dir.path());
        
        let result = execute(false, false).await;
        assert!(result.is_err());
        assert!(result.unwrap_err().to_string().contains("package.json not found"));
    }

    // Note: Empty dependencies test is covered by integration tests
}
