//! Interactive review workflow for promotion candidates.
//!
//! Provides CLI-based review of generated extractors before promotion.
//!
//! This module uses println! for user-facing CLI output during interactive review.
#![allow(clippy::print_stdout, clippy::print_literal)]

use std::io::{self, BufRead, Write};
use std::path::PathBuf;

use tracing::info;

use super::pipeline::PromotionPipeline;
use super::types::{PromotionCandidate, ReviewDecision};
use crate::learning::PatternStore;
use crate::AphoriaError;

/// Result of a review session.
#[derive(Debug, Default)]
pub struct ReviewResult {
    /// Number of candidates approved and promoted.
    pub approved: usize,

    /// Number of candidates rejected.
    pub rejected: usize,

    /// Number of candidates marked for regeneration.
    pub regenerated: usize,

    /// Number of candidates skipped.
    pub skipped: usize,

    /// Paths to promoted YAML files.
    pub promoted_files: Vec<PathBuf>,

    /// Errors encountered during review.
    pub errors: Vec<String>,
}

impl ReviewResult {
    /// Total candidates processed.
    pub fn total_processed(&self) -> usize {
        self.approved + self.rejected + self.regenerated + self.skipped
    }
}

/// Interactive reviewer for promotion candidates.
pub struct InteractiveReviewer<'a, S: PatternStore> {
    /// The promotion pipeline.
    pipeline: &'a PromotionPipeline<'a, S>,

    /// Maximum candidates to review in one session.
    limit: Option<usize>,

    /// Whether to auto-approve ready candidates.
    auto_approve: bool,
}

impl<'a, S: PatternStore> InteractiveReviewer<'a, S> {
    /// Create a new interactive reviewer.
    pub fn new(pipeline: &'a PromotionPipeline<'a, S>) -> Self {
        Self { pipeline, limit: None, auto_approve: false }
    }

    /// Set the maximum number of candidates to review.
    pub fn with_limit(mut self, limit: usize) -> Self {
        self.limit = Some(limit);
        self
    }

    /// Enable auto-approval of ready candidates.
    pub fn with_auto_approve(mut self, auto: bool) -> Self {
        self.auto_approve = auto;
        self
    }

    /// Run an interactive review session.
    ///
    /// For each candidate:
    /// 1. Displays the pattern and generated extractor
    /// 2. Shows validation results
    /// 3. Prompts for decision (approve/reject/regenerate/skip)
    /// 4. Takes action based on decision
    pub fn run(&self) -> Result<ReviewResult, AphoriaError> {
        let mut result = ReviewResult::default();
        let candidates = self.pipeline.get_candidates();

        if candidates.is_empty() {
            return Ok(result);
        }

        let to_review: Vec<_> = match self.limit {
            Some(n) => candidates.into_iter().take(n).collect(),
            None => candidates,
        };

        for pattern in to_review {
            match self.pipeline.generate_candidate(&pattern) {
                Ok(candidate) => {
                    if self.auto_approve && candidate.is_ready() {
                        // Auto-approve
                        match self.pipeline.promote(&candidate) {
                            Ok(path) => {
                                result.approved += 1;
                                result.promoted_files.push(path);
                                info!(
                                    pattern_id = %candidate.pattern_id(),
                                    "Auto-approved candidate"
                                );
                            }
                            Err(e) => {
                                result.errors.push(format!("Promotion failed: {}", e));
                            }
                        }
                    } else {
                        // Interactive review
                        match self.review_candidate(&candidate)? {
                            ReviewDecision::Approve => match self.pipeline.promote(&candidate) {
                                Ok(path) => {
                                    result.approved += 1;
                                    result.promoted_files.push(path);
                                }
                                Err(e) => {
                                    result.errors.push(format!("Promotion failed: {}", e));
                                }
                            },
                            ReviewDecision::Reject => {
                                result.rejected += 1;
                            }
                            ReviewDecision::Regenerate => {
                                result.regenerated += 1;
                                // Note: actual regeneration would need to be implemented
                                // with different prompts or manual intervention
                            }
                            ReviewDecision::Skip => {
                                result.skipped += 1;
                            }
                        }
                    }
                }
                Err(e) => {
                    result.errors.push(format!("Generation failed: {}", e));
                }
            }
        }

        Ok(result)
    }

    /// Review a single candidate and get the user's decision.
    fn review_candidate(
        &self,
        candidate: &PromotionCandidate,
    ) -> Result<ReviewDecision, AphoriaError> {
        // Display candidate information
        display_candidate(candidate);

        // Get user decision
        prompt_for_decision()
    }
}

/// Display a candidate for review.
pub fn display_candidate(candidate: &PromotionCandidate) {
    let pattern = &candidate.pattern;
    let extractor = &candidate.extractor_def;
    let validation = &candidate.validation;

    println!("\n{}", "=".repeat(60));
    println!("PROMOTION CANDIDATE");
    println!("{}", "=".repeat(60));

    println!("\nPattern ID: {}", pattern.id);
    println!("Projects: {} | Occurrences: {}", pattern.project_count(), pattern.occurrences);
    println!("Avg Confidence: {:.2}", pattern.avg_confidence);
    println!("Language: {:?}", pattern.language);

    println!("\n--- Example Code ---");
    println!("{}", truncate_multiline(&pattern.example_code, 200));

    println!("\n--- Normalized Pattern ---");
    println!("{}", pattern.normalized_pattern);

    println!("\n--- Generated Extractor ---");
    println!("Name: {}", extractor.name);
    println!("Regex: {}", extractor.pattern);
    println!("Subject: {}", extractor.claim.subject);
    println!("Predicate: {}", extractor.claim.predicate);

    println!("\n--- Validation ---");
    println!(
        "Passed: {} | Performance OK: {}",
        if validation.passed { "YES" } else { "NO" },
        if validation.performance_ok { "YES" } else { "NO" }
    );
    println!(
        "Compile: {}ms | Match: {}us",
        validation.compile_time_ms, validation.avg_match_time_us
    );

    if !validation.warnings.is_empty() {
        println!("\nWarnings:");
        for w in &validation.warnings {
            println!("  - {}", w);
        }
    }

    if candidate.is_ready() {
        println!("\n[READY for promotion]");
    } else {
        println!("\n[NOT READY - review warnings above]");
    }
}

/// Prompt user for a review decision.
pub fn prompt_for_decision() -> Result<ReviewDecision, AphoriaError> {
    println!("\nOptions:");
    println!("  [a]pprove  - Promote to YAML extractor");
    println!("  [r]eject   - Don't promote, mark as rejected");
    println!("  [g]enerate - Regenerate with different regex");
    println!("  [s]kip     - Skip for now");

    loop {
        print!("\nDecision: ");
        io::stdout().flush().map_err(|e| AphoriaError::Promotion(format!("IO error: {}", e)))?;

        let mut input = String::new();
        io::stdin()
            .lock()
            .read_line(&mut input)
            .map_err(|e| AphoriaError::Promotion(format!("IO error: {}", e)))?;

        if let Some(decision) = ReviewDecision::from_input(&input) {
            return Ok(decision);
        }

        println!("Invalid input. Please enter a, r, g, or s.");
    }
}

/// Display a summary of candidates without full review.
pub fn display_candidates_summary(candidates: &[PromotionCandidate]) {
    println!("\nPromotion Candidates ({} total):\n", candidates.len());
    println!("{:<36} {:>8} {:>6} {:>10} {}", "Pattern ID", "Projects", "Conf", "Ready", "Subject");
    println!("{}", "-".repeat(80));

    for candidate in candidates {
        let ready = if candidate.is_ready() { "YES" } else { "NO" };
        println!(
            "{:<36} {:>8} {:>6.2} {:>10} {}",
            candidate.pattern_id(),
            candidate.pattern.project_count(),
            candidate.pattern.avg_confidence,
            ready,
            truncate_string(&candidate.pattern.claim_template.subject_template, 25)
        );
    }
}

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

/// Truncate multiline text.
fn truncate_multiline(s: &str, max: usize) -> String {
    if s.len() <= max {
        s.to_string()
    } else {
        let truncated = &s[..max.saturating_sub(3)];
        // Find last newline to avoid cutting mid-line
        if let Some(pos) = truncated.rfind('\n') {
            format!("{}...", &truncated[..pos])
        } else {
            format!("{}...", truncated)
        }
    }
}

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

    #[test]
    fn test_review_result_total_processed() {
        let result = ReviewResult {
            approved: 2,
            rejected: 1,
            regenerated: 1,
            skipped: 3,
            ..Default::default()
        };
        assert_eq!(result.total_processed(), 7);
    }

    #[test]
    fn test_truncate_string() {
        assert_eq!(truncate_string("short", 10), "short");
        assert_eq!(truncate_string("a longer string", 10), "a longe...");
    }

    #[test]
    fn test_truncate_multiline() {
        let text = "line one\nline two\nline three";
        let truncated = truncate_multiline(text, 15);
        assert!(truncated.len() <= 15);
        assert!(truncated.ends_with("..."));
    }
}
