//! Pattern command handlers for cross-project learning.

use std::process::ExitCode;

use aphoria::{
    bridge::generate_signing_key,
    community::CommunityExtractorLoader,
    community::PatternSyncer,
    evidence::EvidenceLevel,
    hosted::HostedClient,
    learning::{learning_store_dir, LearnedPattern},
    scope::{OverridePolicy, ScopeLevel, ScopeResolver},
    AphoriaConfig, LocalPatternStore, PatternStore,
};

use crate::cli::PatternCommands;

pub async fn handle_pattern_command(command: PatternCommands, config: &AphoriaConfig) -> ExitCode {
    match command {
        PatternCommands::Sync { dry_run } => handle_pattern_sync(config, dry_run),
        PatternCommands::Status => handle_pattern_status(config),
        PatternCommands::PullCommunity { min_projects, dry_run } => {
            handle_pull_community(config, min_projects, dry_run)
        }
        PatternCommands::Show {
            id,
            evidence,
            eligible,
            format,
            scope,
            only_local,
            show_inheritance,
        } => handle_pattern_show(
            config,
            id,
            evidence,
            eligible,
            format,
            scope,
            only_local,
            show_inheritance,
        ),
    }
}

fn handle_pattern_sync(config: &AphoriaConfig, dry_run: bool) -> ExitCode {
    // Check if hosted mode is configured
    if config.hosted.url.is_none() {
        eprintln!("Hosted mode not configured.");
        eprintln!();
        eprintln!("To configure, add this to your aphoria.toml:");
        eprintln!();
        eprintln!("  [hosted]");
        eprintln!("  url = \"https://your-hosted-server\"");
        return ExitCode::from(1);
    }

    // Check if cross-project pattern contribution is enabled
    if !config.cross_project.contribute_patterns {
        eprintln!("Cross-project pattern contribution is disabled.");
        eprintln!();
        eprintln!("To enable, add this to your aphoria.toml:");
        eprintln!();
        eprintln!("  [cross_project]");
        eprintln!("  contribute_patterns = true");
        return ExitCode::from(1);
    }

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

    // Create hosted client
    let signing_key = generate_signing_key();
    let project_name = config.project.name.as_deref().unwrap_or("unknown");
    let client = match HostedClient::new(&config.hosted, &signing_key, project_name) {
        Ok(Some(c)) => c,
        Ok(None) => {
            eprintln!("Hosted client not configured");
            return ExitCode::from(1);
        }
        Err(e) => {
            eprintln!("Failed to create hosted client: {e}");
            return ExitCode::from(3);
        }
    };

    // Create syncer
    let syncer = PatternSyncer::new(&client, &config.cross_project);

    if dry_run {
        // Preview mode
        let patterns = syncer.get_shareable_patterns(&store);

        println!("Pattern Sync Preview (dry run)");
        println!("==============================");
        println!();
        println!("Configuration:");
        println!("  Min local projects:   {}", config.cross_project.min_local_projects);
        println!("  Min local confidence: {:.2}", config.cross_project.min_local_confidence);
        println!("  Excluded subjects:    {}", config.cross_project.exclude_subjects.len());
        println!();

        if patterns.is_empty() {
            println!("No patterns eligible for sharing.");
            println!();
            println!("Patterns become eligible when:");
            println!("  - Seen in {}+ local projects", config.cross_project.min_local_projects);
            println!("  - Average confidence >= {:.2}", config.cross_project.min_local_confidence);
            println!("  - Not in exclude list");
        } else {
            println!("Patterns that would be synced ({} total):", patterns.len());
            println!();
            println!("{:<64} {:>8} {:>6} Language", "Pattern Hash", "Projects", "Conf");
            println!("{}", "-".repeat(90));

            for pattern in &patterns {
                let hash_short = if pattern.pattern_hash.len() > 16 {
                    format!("{}...", &pattern.pattern_hash[..16])
                } else {
                    pattern.pattern_hash.clone()
                };
                println!(
                    "{:<64} {:>8} {:>6.2} {}",
                    hash_short, pattern.project_count, pattern.avg_confidence, pattern.language
                );
            }
        }

        println!();
        println!("To sync for real, remove --dry-run flag.");
    } else {
        // Real sync
        println!("Syncing patterns to hosted server...");
        println!();

        match syncer.sync(&store) {
            Ok(response) => {
                println!("Sync complete:");
                println!("  Accepted:     {}", response.accepted);
                println!("  Merged:       {}", response.merged);
                println!("  Deduplicated: {}", response.deduplicated);
            }
            Err(e) => {
                eprintln!("Sync failed: {e}");
                return ExitCode::from(3);
            }
        }
    }

    ExitCode::SUCCESS
}

fn handle_pattern_status(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);
        }
    };

    println!("Pattern Learning Status");
    println!("=======================");
    println!();

    // Local store stats
    println!("Local Pattern Store:");
    println!("  Location:     {}", store_dir.display());
    println!("  Total:        {}", store.pattern_count());

    // Eligible for sharing
    let eligible = store.get_promotion_candidates(
        config.cross_project.min_local_projects,
        config.cross_project.min_local_confidence,
    );
    let eligible_not_promoted = eligible.iter().filter(|p| !p.promoted).count();
    println!("  Eligible:     {}", eligible_not_promoted);

    println!();

    // Cross-project config
    println!("Cross-Project Configuration:");
    println!("  Contribute patterns: {}", config.cross_project.contribute_patterns);
    println!("  Receive community:   {}", config.cross_project.receive_community);
    println!("  Min local projects:  {}", config.cross_project.min_local_projects);
    println!("  Min local confidence: {:.2}", config.cross_project.min_local_confidence);
    println!("  Sync interval:       {} seconds", config.cross_project.sync_interval_secs);

    if !config.cross_project.exclude_subjects.is_empty() {
        println!("  Excluded subjects:");
        for subject in &config.cross_project.exclude_subjects {
            println!("    - {}", subject);
        }
    }

    println!();

    // Hosted status
    println!("Hosted Server:");
    if let Some(ref url) = config.hosted.url {
        println!("  URL: {}", url);
    } else {
        println!("  Not configured");
    }

    ExitCode::SUCCESS
}

fn handle_pull_community(config: &AphoriaConfig, min_projects: u64, dry_run: bool) -> ExitCode {
    // Check if hosted mode is configured
    if config.hosted.url.is_none() {
        eprintln!("Hosted mode not configured.");
        eprintln!();
        eprintln!("To configure, add this to your aphoria.toml:");
        eprintln!();
        eprintln!("  [hosted]");
        eprintln!("  url = \"https://your-hosted-server\"");
        return ExitCode::from(1);
    }

    // Check if receiving community extractors is enabled
    if !config.cross_project.receive_community {
        eprintln!("Receiving community extractors is disabled.");
        eprintln!();
        eprintln!("To enable, add this to your aphoria.toml:");
        eprintln!();
        eprintln!("  [cross_project]");
        eprintln!("  receive_community = true");
        return ExitCode::from(1);
    }

    // Create hosted client
    let signing_key = generate_signing_key();
    let project_name = config.project.name.as_deref().unwrap_or("unknown");
    let client = match HostedClient::new(&config.hosted, &signing_key, project_name) {
        Ok(Some(c)) => c,
        Ok(None) => {
            eprintln!("Hosted client not configured");
            return ExitCode::from(1);
        }
        Err(e) => {
            eprintln!("Failed to create hosted client: {e}");
            return ExitCode::from(3);
        }
    };

    // Create loader
    let loader = CommunityExtractorLoader::new(&client, &config.cross_project);

    println!("Pulling Community Extractors");
    println!("============================");
    println!();
    println!("Min projects threshold: {}", min_projects);
    println!("Existing extractors:    {}", loader.existing_count());
    println!();

    // Pull extractors
    let extractors = match loader.pull(min_projects) {
        Ok(e) => e,
        Err(e) => {
            eprintln!("Failed to pull extractors: {e}");
            return ExitCode::from(3);
        }
    };

    if extractors.is_empty() {
        println!("No new community extractors available.");
        return ExitCode::SUCCESS;
    }

    println!("New extractors available ({}):", extractors.len());
    println!();
    println!("{:<30} {:>8} {:>8} {:>6}", "Name", "Orgs", "Projects", "Conf");
    println!("{}", "-".repeat(60));

    for ext in &extractors {
        println!(
            "{:<30} {:>8} {:>8} {:>6.2}",
            truncate(&ext.name, 30),
            ext.provenance.organization_count,
            ext.provenance.total_project_count,
            ext.confidence
        );
    }

    if dry_run {
        println!();
        println!("Dry run - no extractors saved.");
        println!();
        println!("To save, remove --dry-run flag.");
    } else {
        println!();
        match loader.save(&extractors) {
            Ok(paths) => {
                println!("Saved {} extractors to:", paths.len());
                println!("  {}", loader.output_dir().display());
            }
            Err(e) => {
                eprintln!("Failed to save extractors: {e}");
                return ExitCode::from(3);
            }
        }
    }

    ExitCode::SUCCESS
}

/// Truncate a string for display.
fn truncate(s: &str, max_len: usize) -> String {
    if s.len() <= max_len {
        s.to_string()
    } else {
        format!("{}...", &s[..max_len.saturating_sub(3)])
    }
}

#[allow(clippy::too_many_arguments)]
fn handle_pattern_show(
    config: &AphoriaConfig,
    id: Option<String>,
    evidence_filter: Option<String>,
    eligible_only: bool,
    format: String,
    scope_filter: Option<String>,
    only_local: bool,
    show_inheritance: bool,
) -> 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);
        }
    };

    // Parse evidence filter if provided
    let evidence_level_filter = match evidence_filter.as_ref() {
        Some(s) => match s.parse::<EvidenceLevel>() {
            Ok(level) => Some(level),
            Err(e) => {
                eprintln!("{e}");
                return ExitCode::from(1);
            }
        },
        None => None,
    };

    // Parse scope filter if provided
    let scope_level_filter = match scope_filter.as_ref() {
        Some(s) => match s.parse::<ScopeLevel>() {
            Ok(level) => Some(level),
            Err(e) => {
                eprintln!("{e}");
                return ExitCode::from(1);
            }
        },
        None => None,
    };

    // Get patterns based on filters
    let patterns: Vec<LearnedPattern> = if let Some(ref id_str) = id {
        // Specific pattern by ID
        match uuid::Uuid::parse_str(id_str) {
            Ok(uuid) => store.get_pattern_by_id(&uuid).into_iter().collect(),
            Err(_) => {
                eprintln!("Invalid UUID: {}", id_str);
                return ExitCode::from(1);
            }
        }
    } else {
        // All patterns, optionally filtered
        let mut patterns = store.get_all_patterns();

        // Filter by evidence level if specified
        if let Some(level) = evidence_level_filter {
            patterns.retain(|p| p.evidence.effective_level() == level);
        }

        // Filter by scope level if specified
        if let Some(level) = scope_level_filter {
            patterns.retain(|p| p.scope_level == level);
        }

        // Apply scope resolver if only_local is set
        if only_local {
            let scope_ctx = config.scope.to_context();
            let resolver = ScopeResolver::with_policy(scope_ctx, OverridePolicy::NoInherit);
            let filtered: Vec<_> =
                resolver.resolve_all_patterns(&patterns).into_iter().cloned().collect();
            patterns = filtered;
        }

        // Filter to eligible only if requested
        if eligible_only {
            patterns.retain(|p| {
                p.is_promotion_candidate(
                    config.cross_project.min_local_projects,
                    config.cross_project.min_local_confidence,
                )
            });
        }

        // Sort by evidence level (highest first), then by project count
        patterns.sort_by(|a, b| {
            b.evidence
                .effective_level()
                .cmp(&a.evidence.effective_level())
                .then_with(|| b.project_count().cmp(&a.project_count()))
        });

        patterns
    };

    // Output based on format
    match format.as_str() {
        "json" => match serde_json::to_string_pretty(&patterns) {
            Ok(json) => println!("{}", json),
            Err(e) => {
                eprintln!("Failed to serialize patterns: {e}");
                return ExitCode::from(3);
            }
        },
        _ => {
            // Table format
            print_patterns_table(&patterns, &store, config, show_inheritance);
        }
    }

    ExitCode::SUCCESS
}

fn print_patterns_table(
    patterns: &[LearnedPattern],
    store: &LocalPatternStore,
    config: &AphoriaConfig,
    show_inheritance: bool,
) {
    let total = store.pattern_count();

    if patterns.is_empty() {
        println!("No patterns found.");
        return;
    }

    println!("Learned Patterns ({} shown, {} total)", patterns.len(), total);
    println!();

    if show_inheritance {
        println!(
            "{:<36} {:>8} {:>6} {:>10} {:>12} Subject",
            "ID", "Projects", "Conf", "Evidence", "Scope"
        );
        println!("{}", "-".repeat(95));
    } else {
        println!("{:<36} {:>8} {:>6} {:>10} Subject", "ID", "Projects", "Conf", "Evidence");
        println!("{}", "-".repeat(80));
    }

    for pattern in patterns {
        let id_short = &pattern.id.to_string()[..8];
        let evidence_badge = pattern.evidence.effective_level().badge();
        let subject = &pattern.claim_template.subject_template;

        // Check if eligible for promotion
        let eligible = pattern.is_promotion_candidate(
            config.cross_project.min_local_projects,
            config.cross_project.min_local_confidence,
        );
        let eligibility_marker = if eligible { " *" } else { "" };

        if show_inheritance {
            let scope_info =
                format!("{}:{}", pattern.scope_level, pattern.scope_id.as_deref().unwrap_or("-"));
            println!(
                "{:<36} {:>8} {:>6.2} {:>10} {:>12} {}{}",
                format!("{}...", id_short),
                pattern.project_count(),
                pattern.avg_confidence,
                evidence_badge,
                truncate(&scope_info, 12),
                truncate(subject, 25),
                eligibility_marker
            );
        } else {
            println!(
                "{:<36} {:>8} {:>6.2} {:>10} {}{}",
                format!("{}...", id_short),
                pattern.project_count(),
                pattern.avg_confidence,
                evidence_badge,
                truncate(subject, 30),
                eligibility_marker
            );
        }

        // Show evidence sources on next line(s)
        for source in &pattern.evidence.sources {
            println!("    -> {}", source.display());
        }
    }

    println!();
    println!("* = eligible for promotion");
}
