//! Extractor command handlers (stats, candidates, review, promote, auto-promote, versioning)

use std::process::ExitCode;

use aphoria::{
    learning::learning_store_dir,
    promotion::{compute_metrics_delta, ChangelogEntry, VersionStore},
    AphoriaConfig, LocalPatternStore, ShadowStore,
};

use crate::cli::ExtractorCommands;

use super::utils::truncate_for_display;

pub async fn handle_extractor_command(
    command: ExtractorCommands,
    config: &AphoriaConfig,
) -> ExitCode {
    // Open pattern store
    let store_dir = learning_store_dir();
    let store = match LocalPatternStore::new(&store_dir) {
        Ok(s) => s,
        Err(e) => {
            eprintln!("Failed to open pattern store: {e}");
            return ExitCode::from(3);
        }
    };

    match command {
        ExtractorCommands::Stats => handle_extractor_stats(&store, config),

        ExtractorCommands::Candidates { verbose } => {
            handle_extractor_candidates(&store, config, verbose)
        }

        ExtractorCommands::Review { limit, auto } => {
            handle_extractor_review(&store, config, limit, auto).await
        }

        ExtractorCommands::Promote { pattern_id, force } => {
            handle_extractor_promote(&store, config, &pattern_id, force).await
        }

        ExtractorCommands::AutoPromote { dry_run, min_confidence, min_projects } => {
            handle_auto_promote(&store, config, dry_run, min_confidence, min_projects).await
        }

        ExtractorCommands::ShadowStatus { verbose } => {
            super::shadow::handle_shadow_status(config, verbose)
        }

        ExtractorCommands::Feedback { test, limit } => {
            super::shadow::handle_shadow_feedback(config, &test, limit)
        }

        ExtractorCommands::Graduate { test, force } => {
            super::shadow::handle_shadow_graduate(config, &test, force)
        }

        ExtractorCommands::Rollback { test, reason } => {
            super::shadow::handle_shadow_rollback(config, &test, &reason)
        }

        ExtractorCommands::AutoCheck => super::shadow::handle_shadow_auto_check(config),

        ExtractorCommands::Versions { name } => handle_versions(&name, config),

        ExtractorCommands::Compare { name, version_a, version_b } => {
            handle_compare(&name, version_a, version_b, config)
        }

        ExtractorCommands::RollbackVersion { name, version, reason } => {
            handle_rollback_version(&name, version, &reason, config)
        }
    }
}

fn handle_extractor_stats(store: &LocalPatternStore, config: &AphoriaConfig) -> ExitCode {
    use aphoria::PromotionPipeline;

    let pipeline = match PromotionPipeline::new(store, None, &config.learning.promotion, None) {
        Ok(p) => p,
        Err(e) => {
            eprintln!("Failed to create pipeline: {e}");
            return ExitCode::from(3);
        }
    };

    let stats = pipeline.stats();

    println!("Pattern Learning Statistics");
    println!("===========================");
    println!();
    println!("Total patterns:    {}", stats.total_patterns);
    println!("Eligible for promotion: {}", stats.eligible_patterns);
    println!("Already promoted:  {}", stats.promoted_patterns);
    println!("Pending review:    {}", stats.pending_review);
    println!();

    if stats.eligible_patterns > 0 {
        println!("Eligible pattern averages:");
        println!("  Confidence: {:.2}", stats.avg_confidence);
        println!("  Projects:   {:.1}", stats.avg_projects);
    }

    println!();
    println!("Promotion thresholds (from config):");
    println!("  Min projects:   {}", config.learning.promotion.min_projects);
    println!("  Min confidence: {:.2}", config.learning.promotion.min_confidence);
    println!("  Auto-promote:   {}", config.learning.promotion.auto_promote);

    ExitCode::SUCCESS
}

fn handle_extractor_candidates(
    store: &LocalPatternStore,
    config: &AphoriaConfig,
    verbose: bool,
) -> ExitCode {
    use aphoria::PromotionPipeline;

    let pipeline = match PromotionPipeline::new(store, None, &config.learning.promotion, None) {
        Ok(p) => p,
        Err(e) => {
            eprintln!("Failed to create pipeline: {e}");
            return ExitCode::from(3);
        }
    };

    let candidates = pipeline.get_candidates();

    if candidates.is_empty() {
        println!("No patterns eligible for promotion.");
        println!();
        println!("Patterns become eligible when:");
        println!("  - Seen in {}+ projects", config.learning.promotion.min_projects);
        println!("  - Average confidence >= {:.2}", config.learning.promotion.min_confidence);
        return ExitCode::SUCCESS;
    }

    println!("Patterns eligible for promotion ({} total):\n", candidates.len());
    println!("{:<36} {:>8} {:>6} Subject", "Pattern ID", "Projects", "Conf");
    println!("{}", "-".repeat(70));

    for pattern in &candidates {
        let subject = if pattern.claim_template.subject_template.len() > 25 {
            format!("{}...", &pattern.claim_template.subject_template[..22])
        } else {
            pattern.claim_template.subject_template.clone()
        };

        println!(
            "{:<36} {:>8} {:>6.2} {}",
            pattern.id,
            pattern.project_count(),
            pattern.avg_confidence,
            subject
        );

        if verbose {
            println!("    Language: {}", pattern.language);
            println!("    Example: {}", truncate_for_display(&pattern.example_code, 60));
            println!("    Normalized: {}", pattern.normalized_pattern);
            println!();
        }
    }

    println!();
    println!("To promote a pattern, run:");
    println!("  aphoria extractors promote <PATTERN_ID>");
    println!();
    println!("For interactive review:");
    println!("  aphoria extractors review");

    ExitCode::SUCCESS
}

async fn handle_extractor_review(
    store: &LocalPatternStore,
    config: &AphoriaConfig,
    limit: Option<usize>,
    auto: bool,
) -> ExitCode {
    use aphoria::{llm::GeminiClient, InteractiveReviewer, PromotionPipeline};

    // Create LLM client
    let client = match GeminiClient::new(&config.llm) {
        Ok(Some(c)) => c,
        Ok(None) => {
            eprintln!("LLM not configured. Cannot generate regex patterns.");
            eprintln!();
            eprintln!("To configure LLM, add this to your aphoria.toml:");
            eprintln!();
            eprintln!("  [llm]");
            eprintln!("  enabled = true");
            eprintln!("  api_key_env = \"GEMINI_API_KEY\"");
            return ExitCode::from(3);
        }
        Err(e) => {
            eprintln!("Failed to create LLM client: {e}");
            return ExitCode::from(3);
        }
    };

    let output_dir = config.learning.promotion.output_dir.clone();
    let pipeline = match PromotionPipeline::new(
        store,
        Some(&client),
        &config.learning.promotion,
        Some(output_dir),
    ) {
        Ok(p) => p,
        Err(e) => {
            eprintln!("Failed to create pipeline: {e}");
            return ExitCode::from(3);
        }
    };

    let mut reviewer = InteractiveReviewer::new(&pipeline).with_auto_approve(auto);
    if let Some(n) = limit {
        reviewer = reviewer.with_limit(n);
    }

    match reviewer.run() {
        Ok(result) => {
            println!();
            println!("Review session complete:");
            println!("  Approved:    {}", result.approved);
            println!("  Rejected:    {}", result.rejected);
            println!("  Regenerated: {}", result.regenerated);
            println!("  Skipped:     {}", result.skipped);

            if !result.promoted_files.is_empty() {
                println!();
                println!("Promoted extractors written to:");
                for path in &result.promoted_files {
                    println!("  {}", path.display());
                }
            }

            if !result.errors.is_empty() {
                println!();
                println!("Errors:");
                for err in &result.errors {
                    println!("  - {}", err);
                }
            }

            ExitCode::SUCCESS
        }
        Err(e) => {
            eprintln!("Review error: {e}");
            ExitCode::from(3)
        }
    }
}

async fn handle_extractor_promote(
    store: &LocalPatternStore,
    config: &AphoriaConfig,
    pattern_id: &str,
    _force: bool,
) -> ExitCode {
    use aphoria::{llm::GeminiClient, PromotionPipeline};
    use uuid::Uuid;

    // Parse pattern ID
    let id = match Uuid::parse_str(pattern_id) {
        Ok(id) => id,
        Err(_) => {
            eprintln!("Invalid pattern ID format. Expected UUID.");
            return ExitCode::from(3);
        }
    };

    // Create LLM client
    let client = match GeminiClient::new(&config.llm) {
        Ok(Some(c)) => c,
        Ok(None) => {
            eprintln!("LLM not configured. Cannot generate regex patterns.");
            return ExitCode::from(3);
        }
        Err(e) => {
            eprintln!("Failed to create LLM client: {e}");
            return ExitCode::from(3);
        }
    };

    let output_dir = config.learning.promotion.output_dir.clone();
    let pipeline = match PromotionPipeline::new(
        store,
        Some(&client),
        &config.learning.promotion,
        Some(output_dir),
    ) {
        Ok(p) => p,
        Err(e) => {
            eprintln!("Failed to create pipeline: {e}");
            return ExitCode::from(3);
        }
    };

    match pipeline.promote_by_id(&id) {
        Ok(path) => {
            println!("Pattern promoted successfully!");
            println!("  Extractor written to: {}", path.display());
            ExitCode::SUCCESS
        }
        Err(e) => {
            eprintln!("Promotion failed: {e}");
            ExitCode::from(3)
        }
    }
}

async fn handle_auto_promote(
    store: &LocalPatternStore,
    config: &AphoriaConfig,
    dry_run: bool,
    min_confidence: Option<f32>,
    min_projects: Option<usize>,
) -> ExitCode {
    use aphoria::{llm::GeminiClient, PromotionPipeline};

    // Build autonomous config with overrides
    let mut auto_config = config.autonomous.clone();
    if let Some(conf) = min_confidence {
        auto_config.min_confidence = conf;
    }
    if let Some(proj) = min_projects {
        auto_config.min_projects = proj;
    }

    // For dry run, temporarily enable autonomous mode
    if dry_run {
        auto_config.enabled = true;
    }

    // Check if autonomous promotion is enabled
    if !auto_config.enabled && !dry_run {
        println!("Autonomous promotion is disabled.");
        println!();
        println!("To enable, add this to your aphoria.toml:");
        println!();
        println!("  [autonomous]");
        println!("  enabled = true");
        println!("  min_confidence = 0.95");
        println!("  min_projects = 10");
        return ExitCode::SUCCESS;
    }

    // Create LLM client
    let client = match GeminiClient::new(&config.llm) {
        Ok(Some(c)) => c,
        Ok(None) => {
            eprintln!("LLM not configured. Cannot generate regex patterns.");
            eprintln!();
            eprintln!("To configure LLM, add this to your aphoria.toml:");
            eprintln!();
            eprintln!("  [llm]");
            eprintln!("  enabled = true");
            eprintln!("  api_key_env = \"GEMINI_API_KEY\"");
            return ExitCode::from(3);
        }
        Err(e) => {
            eprintln!("Failed to create LLM client: {e}");
            return ExitCode::from(3);
        }
    };

    let output_dir = config.learning.promotion.output_dir.clone();
    let pipeline = match PromotionPipeline::new(
        store,
        Some(&client),
        &config.learning.promotion,
        Some(output_dir),
    ) {
        Ok(p) => p,
        Err(e) => {
            eprintln!("Failed to create pipeline: {e}");
            return ExitCode::from(3);
        }
    };

    if dry_run {
        // Preview mode: check what would be promoted without making changes
        println!("Autonomous Promotion Preview (dry run)");
        println!("======================================");
        println!();
        println!("Thresholds:");
        println!("  Min confidence: {:.2}", auto_config.min_confidence);
        println!("  Min projects:   {}", auto_config.min_projects);
        println!("  Zero failures:  {}", auto_config.require_zero_failures);
        println!("  Zero warnings:  {}", auto_config.require_zero_warnings);
        println!();

        let candidates = pipeline.get_candidates();
        if candidates.is_empty() {
            println!("No patterns eligible for promotion.");
            return ExitCode::SUCCESS;
        }

        let mut would_promote = 0;
        let mut needs_review = 0;

        for pattern in &candidates {
            // Create a mock candidate to check eligibility
            match pipeline.generate_candidate(pattern) {
                Ok(candidate) => {
                    if candidate.should_auto_promote(&auto_config) {
                        would_promote += 1;
                        println!(
                            "[WOULD AUTO-PROMOTE] {} (conf: {:.2}, projects: {})",
                            pattern.id,
                            pattern.avg_confidence,
                            pattern.project_count()
                        );
                    } else {
                        needs_review += 1;
                        let blockers = candidate.auto_promotion_blockers(&auto_config);
                        println!("[NEEDS REVIEW] {} - {}", pattern.id, blockers.join(", "));
                    }
                }
                Err(e) => {
                    println!("[ERROR] {} - {}", pattern.id, e);
                }
            }
        }

        println!();
        println!("Summary:");
        println!("  Would auto-promote: {}", would_promote);
        println!("  Needs review:       {}", needs_review);
        println!();
        println!("To run for real, remove --dry-run flag.");
    } else {
        // Real mode: actually promote
        println!("Running Autonomous Promotion");
        println!("============================");
        println!();
        println!("Thresholds:");
        println!("  Min confidence: {:.2}", auto_config.min_confidence);
        println!("  Min projects:   {}", auto_config.min_projects);
        println!();

        match pipeline.smart_auto_promote_all(&auto_config) {
            Ok(result) => {
                println!("Results:");
                println!("  Auto-promoted:   {}", result.auto_promoted);
                println!("  Requires review: {}", result.requires_review);
                println!("  Errors:          {}", result.errors.len());

                if !result.promoted_files.is_empty() {
                    println!();
                    println!("Promoted extractors written to:");
                    for path in &result.promoted_files {
                        println!("  {}", path.display());
                    }
                }

                if !result.errors.is_empty() {
                    println!();
                    println!("Errors:");
                    for err in &result.errors {
                        println!("  - {}", err);
                    }
                }

                // Print audit log location
                let audit_dir = auto_config.get_audit_dir();
                println!();
                println!("Audit log: {}/autonomous-decisions.jsonl", audit_dir.display());
            }
            Err(e) => {
                eprintln!("Auto-promotion failed: {e}");
                return ExitCode::from(3);
            }
        }
    }

    ExitCode::SUCCESS
}

// ============================================================================
// Version Command Handlers
// ============================================================================

/// Handle the `extractors versions` command.
fn handle_versions(name: &str, config: &AphoriaConfig) -> ExitCode {
    let extractors_dir = config.learning.promotion.output_dir.clone();
    let version_store = match VersionStore::new(&extractors_dir) {
        Ok(s) => s,
        Err(e) => {
            eprintln!("Failed to open version store: {e}");
            return ExitCode::from(3);
        }
    };

    let changelog = match version_store.read_changelog(name) {
        Ok(c) => c,
        Err(e) => {
            eprintln!("Failed to read changelog for {}: {e}", name);
            return ExitCode::from(3);
        }
    };

    if changelog.entries.is_empty() {
        println!("No version history found for '{}'.", name);
        println!();
        println!("Version history is created when extractors are promoted");
        println!("using the versioned promotion system.");
        return ExitCode::SUCCESS;
    }

    println!("Version History: {}", name);
    println!("Current version: {}", changelog.current_version);
    println!();
    println!("{:<8} {:<12} Changes", "Version", "Date");
    println!("{}", "-".repeat(60));

    // Show entries newest first
    for entry in changelog.entries.iter().rev() {
        let changes = if entry.changes.len() > 40 {
            format!("{}...", &entry.changes[..37])
        } else {
            entry.changes.clone()
        };

        println!("{:<8} {:<12} {}", entry.version, entry.date, changes);

        if let Some(ref metrics) = entry.metrics {
            println!(
                "         {:<12} Matches: {}, FP: {}",
                "", metrics.matches, metrics.false_positives
            );
        }
    }

    println!();
    println!("To compare versions:");
    println!("  aphoria extractors compare {} -a 1 -b 2", name);
    println!();
    println!("To rollback to a previous version:");
    println!("  aphoria extractors rollback-version {} --version 1 --reason \"...\"", name);

    ExitCode::SUCCESS
}

/// Handle the `extractors compare` command.
fn handle_compare(name: &str, version_a: u32, version_b: u32, config: &AphoriaConfig) -> ExitCode {
    // Open shadow store for metrics
    let shadow_dir = config.shadow.get_shadow_dir();
    let shadow_store = match ShadowStore::new(&shadow_dir) {
        Ok(s) => s,
        Err(e) => {
            eprintln!("Failed to open shadow store: {e}");
            return ExitCode::from(3);
        }
    };

    println!("Comparison: {} v{} vs v{}", name, version_a, version_b);
    println!();

    match compute_metrics_delta(&shadow_store, name, version_a, version_b) {
        Ok(Some(delta)) => {
            println!("{:<20} {}", "Matches", delta.matches);
            println!("{:<20} {}", "False Positives", delta.false_positives);
        }
        Ok(None) => {
            println!("Insufficient metrics data available for comparison.");
            println!();
            println!("Metrics are collected during shadow mode testing.");
            println!("Ensure the extractor has been through shadow mode with");
            println!("sufficient feedback before comparing versions.");
        }
        Err(e) => {
            eprintln!("Failed to compute metrics: {e}");
            return ExitCode::from(3);
        }
    }

    ExitCode::SUCCESS
}

/// Handle the `extractors rollback-version` command.
fn handle_rollback_version(
    name: &str,
    version: u32,
    reason: &str,
    config: &AphoriaConfig,
) -> ExitCode {
    let extractors_dir = config.learning.promotion.output_dir.clone();
    let version_store = match VersionStore::new(&extractors_dir) {
        Ok(s) => s,
        Err(e) => {
            eprintln!("Failed to open version store: {e}");
            return ExitCode::from(3);
        }
    };

    // Check that the version exists
    let versions = match version_store.list_versions(name) {
        Ok(v) => v,
        Err(e) => {
            eprintln!("Failed to list versions: {e}");
            return ExitCode::from(3);
        }
    };

    if !versions.contains(&version) {
        eprintln!("Version {} not found for '{}'.", version, name);
        if versions.is_empty() {
            eprintln!("No archived versions available.");
        } else {
            eprintln!("Available versions: {:?}", versions);
        }
        return ExitCode::from(3);
    }

    // Perform the rollback
    let path = match version_store.restore_version(name, version, &extractors_dir) {
        Ok(p) => p,
        Err(e) => {
            eprintln!("Failed to restore version: {e}");
            return ExitCode::from(3);
        }
    };

    // Record rollback in changelog
    let new_version = match version_store.next_version(name) {
        Ok(v) => v,
        Err(e) => {
            eprintln!("Warning: Failed to determine new version number: {e}");
            0
        }
    };

    let rollback_entry =
        ChangelogEntry::new(new_version, format!("Rollback to v{}: {}", version, reason));

    if let Err(e) = version_store.append_changelog(name, rollback_entry) {
        eprintln!("Warning: Failed to update changelog: {e}");
    }

    println!("Rolled back {} to v{}", name, version);
    println!("Restored as: {}", path.display());
    println!();
    println!("Reason: {}", reason);
    println!();
    println!("A new changelog entry has been created documenting this rollback.");

    ExitCode::SUCCESS
}
