use std::path::PathBuf;

use clap::Args;

use diffsift_core::config::{self, DiffSiftConfig};
use diffsift_core::context;
use diffsift_core::languages;

#[derive(Args)]
pub struct ReviewArgs {
    /// Path to the git repository
    #[arg(long, default_value = ".")]
    pub repo: PathBuf,

    /// Base ref (commit, branch, or tag)
    #[arg(long)]
    pub base: String,

    /// Head ref (commit, branch, or tag)
    #[arg(long)]
    pub head: String,

    /// Path to config file
    #[arg(long)]
    pub config: Option<PathBuf>,

    /// Dry-run: show what would be reviewed without calling LLM or posting
    #[arg(long)]
    pub dry_run: bool,

    /// Output format
    #[arg(long, default_value = "summary")]
    pub format: ReviewFormat,
}

#[derive(Clone, Debug, clap::ValueEnum)]
pub enum ReviewFormat {
    Summary,
    Json,
}

pub async fn run(args: ReviewArgs) -> miette::Result<()> {
    // 1. Load config
    let config = config::load_config(args.config.as_deref(), Some(&args.repo))
        .map_err(|e| miette::miette!("{e}"))?;

    // 2. Classify the diff (with config for churn detection)
    let classified = diffsift_core::DiffSift::builder()
        .config(config.clone())
        .build()
        .classify_git(&args.repo, &args.base, &args.head)?;

    tracing::info!(
        "Classified {} hunks across {} files in {}ms",
        classified.summary.total_hunks,
        classified.summary.total_files,
        classified.classification_time_ms
    );

    // 3. Filter to reviewable hunks
    let reviewable: Vec<_> = classified
        .files
        .iter()
        .flat_map(|f| {
            f.hunks
                .iter()
                .filter(|h| config.review.review_priorities.contains(&h.priority))
                .map(move |h| (f, h))
        })
        .collect();

    tracing::info!(
        "{} hunks eligible for LLM review (priorities: {:?})",
        reviewable.len(),
        config.review.review_priorities
    );

    // 4. If dry-run, print what would be reviewed and exit
    if args.dry_run {
        print_dry_run(&classified, &reviewable, &config);
        return Ok(());
    }

    // 5. Assemble context for each reviewable hunk
    let diff_files = diffsift_core::differ::compute_diff(&args.repo, &args.base, &args.head)?;

    // Generate cross-file change scope summary for multi-file PRs
    let change_scope = generate_change_scope(&classified);

    let mut contexts = Vec::new();
    for (file, hunk) in &reviewable {
        if let Some(diff_file) = diff_files
            .iter()
            .find(|df| df.path.display().to_string() == file.path)
        {
            if let Some(syntax) = languages::detect_language(&diff_file.path) {
                match context::assemble_context(hunk, diff_file, syntax.as_ref(), &file.path) {
                    Ok(mut ctx) => {
                        ctx.change_scope = change_scope.clone();
                        // Populate churn note from CHURN-01 pattern if present
                        ctx.churn_note = hunk
                            .patterns
                            .iter()
                            .find(|p| p.pattern_id == "CHURN-01")
                            .and_then(|p| p.confidence_notes.first().cloned());
                        contexts.push(ctx);
                    }
                    Err(e) => tracing::warn!(
                        "Failed to assemble context for {}:{}: {e}",
                        file.path,
                        hunk.start_line
                    ),
                }
            }
        }
    }

    // 6. Create LLM provider
    let router =
        diffsift_llm::ModelRouter::from_config(&config.llm).map_err(|e| miette::miette!("{e}"))?;
    let template_engine = diffsift_llm::TemplateEngine::with_config(&config.prompts);
    let filter = diffsift_llm::PreCheckFilter::new();

    // 6b. Small-diff routing based on config.prompts.small_diff_mode
    let total_hunks = classified.summary.total_hunks;
    let has_high_cross_hunk = classified.files.iter().any(|f| {
        f.hunks.iter().any(|h| {
            h.priority >= diffsift_core::taxonomy::Priority::High
                && h.patterns.iter().any(|p| p.pattern_id.starts_with("X-"))
        })
    });
    let use_small_diff = match config.prompts.small_diff_mode {
        diffsift_core::config::SmallDiffMode::Classify => false,
        diffsift_core::config::SmallDiffMode::Custom => {
            if config.prompts.template_dir.is_none() {
                tracing::warn!(
                    "small_diff_mode is 'custom' but no template_dir configured — using full pipeline"
                );
                false
            } else {
                config.review.small_diff_threshold > 0
                    && total_hunks <= config.review.small_diff_threshold
                    && !has_high_cross_hunk
            }
        }
        diffsift_core::config::SmallDiffMode::Passthrough => {
            config.review.small_diff_threshold > 0
                && total_hunks <= config.review.small_diff_threshold
                && !has_high_cross_hunk
        }
    };

    if use_small_diff {
        tracing::info!(
            "Small diff ({} hunks ≤ threshold {}), using simplified prompt",
            total_hunks,
            config.review.small_diff_threshold
        );
        let system_prompt = template_engine
            .render_small_diff_system()
            .map_err(|e| miette::miette!("{e}"))?;
        let file_diffs: Vec<(String, String)> = diff_files
            .iter()
            .map(|df| (df.path.display().to_string(), df.diff_text.clone()))
            .collect();
        let user_prompt = template_engine
            .render_small_diff(&file_diffs)
            .map_err(|e| miette::miette!("{e}"))?;

        let model = router.model_for_priority(diffsift_core::taxonomy::Priority::High);
        match router
            .provider()
            .review(&system_prompt, &user_prompt, model)
            .await
        {
            Ok(response) => {
                if filter.should_keep(&response.content) {
                    println!("{}", response.content);
                }
            }
            Err(e) => tracing::warn!("LLM review failed: {e}"),
        }
        return Ok(());
    }

    let system_prompt = template_engine
        .render_system()
        .map_err(|e| miette::miette!("{e}"))?;

    // 7. Group hunks by file, review sequentially, track cost
    let mut review_results = Vec::new();
    let mut total_input_tokens: u32 = 0;
    let mut total_output_tokens: u32 = 0;

    // Group contexts by file path (preserving order)
    let mut file_groups: Vec<(String, Vec<usize>)> = Vec::new();
    for (i, ctx) in contexts.iter().enumerate() {
        if let Some(group) = file_groups.iter_mut().find(|(p, _)| *p == ctx.file_path) {
            group.1.push(i);
        } else {
            file_groups.push((ctx.file_path.clone(), vec![i]));
        }
    }

    let mut cost_exceeded = false;
    for (file_path, indices) in &file_groups {
        if cost_exceeded {
            break;
        }

        if indices.len() >= 2 {
            // Multi-hunk file: batch into a single prompt
            let hunks: Vec<&diffsift_core::context::HunkContext> =
                indices.iter().map(|&i| &contexts[i]).collect();
            let language = &contexts[indices[0]].language;
            let max_priority = hunks.iter().map(|h| h.priority).max().unwrap();
            let model = router.model_for_priority(max_priority);

            let user_prompt = template_engine
                .render_review_file(
                    file_path,
                    language,
                    &hunks,
                    contexts[indices[0]].change_scope.as_deref(),
                )
                .map_err(|e| miette::miette!("{e}"))?;

            tracing::info!(
                "Reviewing {} ({} hunks, {:?}) with model {}",
                file_path,
                hunks.len(),
                max_priority,
                model
            );

            match router
                .provider()
                .review(&system_prompt, &user_prompt, model)
                .await
            {
                Ok(response) => {
                    total_input_tokens += response.input_tokens;
                    total_output_tokens += response.output_tokens;

                    let estimated_cost_cents = (total_input_tokens as f64 * 0.0003
                        + total_output_tokens as f64 * 0.0015)
                        as u32;
                    if estimated_cost_cents > config.review.max_cost_cents {
                        tracing::warn!(
                            "Cost limit reached (~{} cents), stopping review",
                            estimated_cost_cents
                        );
                        cost_exceeded = true;
                    }

                    if filter.should_keep(&response.content) {
                        // Attribute the response to the highest-priority hunk in the group
                        let best = hunks.iter().max_by_key(|h| h.priority).unwrap();
                        review_results.push((*best, response.content));
                    }
                }
                Err(e) => {
                    tracing::warn!("LLM review failed for {}: {e}", file_path);
                }
            }
        } else {
            // Single-hunk file: use lightweight or standard template
            let ctx = &contexts[indices[0]];
            let model = router.model_for_priority(ctx.priority);

            let changed_lines = ctx
                .hunk_diff
                .lines()
                .filter(|l| l.starts_with('+') || l.starts_with('-'))
                .count();
            let user_prompt = if changed_lines <= 3 {
                template_engine
                    .render_review_lightweight(ctx)
                    .map_err(|e| miette::miette!("{e}"))?
            } else {
                template_engine
                    .render_review(ctx)
                    .map_err(|e| miette::miette!("{e}"))?
            };

            tracing::info!(
                "Reviewing {}:{}-{} ({:?}) with model {}",
                ctx.file_path,
                ctx.start_line,
                ctx.end_line,
                ctx.category,
                model
            );

            match router
                .provider()
                .review(&system_prompt, &user_prompt, model)
                .await
            {
                Ok(response) => {
                    total_input_tokens += response.input_tokens;
                    total_output_tokens += response.output_tokens;

                    let estimated_cost_cents = (total_input_tokens as f64 * 0.0003
                        + total_output_tokens as f64 * 0.0015)
                        as u32;
                    if estimated_cost_cents > config.review.max_cost_cents {
                        tracing::warn!(
                            "Cost limit reached (~{} cents), stopping review",
                            estimated_cost_cents
                        );
                        cost_exceeded = true;
                    }

                    if filter.should_keep(&response.content) {
                        review_results.push((ctx, response.content));
                    } else {
                        tracing::debug!(
                            "Filtered out vague response for {}:{}",
                            ctx.file_path,
                            ctx.start_line
                        );
                    }
                }
                Err(e) => {
                    tracing::warn!(
                        "LLM review failed for {}:{}: {e}",
                        ctx.file_path,
                        ctx.start_line
                    );
                }
            }
        }
    }

    // 8. Build summary and inline comments
    let focus_areas: Vec<(String, String, String)> = review_results
        .iter()
        .map(|(ctx, _)| {
            (
                ctx.file_path.clone(),
                format!("{:?}", ctx.category),
                ctx.patterns.first().cloned().unwrap_or_default(),
            )
        })
        .collect();

    let summary = diffsift_vcs::build_summary_markdown(&classified.summary, &focus_areas);

    let inline_comments: Vec<diffsift_vcs::InlineComment> = review_results
        .iter()
        .map(|(ctx, content)| {
            let body = diffsift_vcs::build_inline_comment_body(
                &format!("{:?}", ctx.category),
                &format!("{:?}", ctx.priority).to_lowercase(),
                ctx.patterns.first().map(|s| s.as_str()).unwrap_or(""),
                content,
            );
            diffsift_vcs::InlineComment {
                path: ctx.file_path.clone(),
                line: ctx.end_line,
                body,
                commit_id: args.head.clone(),
            }
        })
        .collect();

    // 9. Post to CI platform or print to stdout
    match diffsift_vcs::detect_ci() {
        Ok(detected) => {
            tracing::info!("Detected {} CI environment", detected.provider_name);
            let results = diffsift_vcs::ReviewResults {
                summary,
                inline_comments,
            };
            detected
                .poster
                .post_review_results(&results, config.review.max_comments)
                .await
                .map_err(|e| miette::miette!("{e}"))?;
            tracing::info!(
                "Posted {} review comments to {}",
                review_results.len(),
                detected.provider_name
            );
        }
        Err(_) => {
            // Not in CI, print to stdout
            println!("{summary}");
            for (ctx, content) in &review_results {
                println!(
                    "\n--- {}:{}-{} ({:?}) ---\n{}",
                    ctx.file_path, ctx.start_line, ctx.end_line, ctx.category, content
                );
            }
        }
    }

    Ok(())
}

fn print_dry_run(
    classified: &diffsift_core::ClassifiedDiff,
    reviewable: &[(
        &diffsift_core::ClassifiedFile,
        &diffsift_core::ClassifiedHunk,
    )],
    config: &DiffSiftConfig,
) {
    println!(
        "Analyzed {} hunks across {} files in {}ms\n",
        classified.summary.total_hunks,
        classified.summary.total_files,
        classified.classification_time_ms
    );

    println!("Priority  | Hunks");
    println!("----------|------");
    println!("HIGH      | {}", classified.summary.priority_counts.high);
    println!(
        "MODERATE  | {}",
        classified.summary.priority_counts.moderate
    );
    println!("LOW       | {}", classified.summary.priority_counts.low);
    println!(
        "FILTERED  | {}",
        classified.summary.priority_counts.filtered
    );
    println!();

    println!(
        "Would review {} hunks (priorities: {:?}):",
        reviewable.len(),
        config.review.review_priorities
    );

    for (file, hunk) in reviewable {
        let pattern = hunk
            .patterns
            .first()
            .map(|p| p.pattern_name.as_str())
            .unwrap_or("unknown");
        let symbol = hunk.enclosing_node.as_deref().unwrap_or("top-level");
        println!(
            "  {}:{}-{} — {:?} ({:?}) in {} [{}]",
            file.path,
            hunk.start_line,
            hunk.end_line,
            hunk.category,
            hunk.priority,
            symbol,
            pattern
        );
    }

    // Estimate token cost
    let total_diff_chars: usize = reviewable.iter().map(|(_, h)| h.diff_text.len()).sum();
    let est_input_tokens = total_diff_chars / 4; // ~4 chars per token
    let est_output_tokens = reviewable.len() * 200; // ~200 tokens per review response
                                                    // Sonnet pricing: $3/M input, $15/M output
    let est_cost_cents =
        (est_input_tokens as f64 * 0.0003 + est_output_tokens as f64 * 0.0015) as u32;

    println!(
        "\nEstimated cost: ~{}c ({} input tokens, {} output tokens)",
        est_cost_cents.max(1),
        est_input_tokens,
        est_output_tokens
    );
    println!(
        "Config: provider={}, max_comments={}, max_cost={}c",
        config.llm.provider, config.review.max_comments, config.review.max_cost_cents
    );
    println!("\nUse without --dry-run to actually call LLM and post comments.");
}

/// Generate a one-line-per-file change scope summary for multi-file PRs.
/// Returns None for single-file diffs (no cross-file context needed).
fn generate_change_scope(classified: &diffsift_core::ClassifiedDiff) -> Option<String> {
    if classified.files.len() <= 1 {
        return None;
    }

    let mut lines = Vec::new();
    lines.push(format!(
        "This PR modifies {} files:",
        classified.files.len()
    ));

    for file in classified.files.iter().take(10) {
        // Find dominant category and HIGH hunk count
        let high_count = file
            .hunks
            .iter()
            .filter(|h| h.priority >= diffsift_core::taxonomy::Priority::High)
            .count();
        let top_category = file
            .hunks
            .iter()
            .max_by_key(|h| h.priority)
            .map(|h| format!("{:?}", h.category))
            .unwrap_or_else(|| "Unknown".to_string());

        // Summarize top patterns
        let top_pattern = file
            .hunks
            .iter()
            .flat_map(|h| h.patterns.first())
            .next()
            .map(|p| p.pattern_name.as_str())
            .unwrap_or("general");

        let high_label = if high_count > 0 {
            format!(" ({} HIGH)", high_count)
        } else {
            String::new()
        };

        let is_churning = file
            .hunks
            .iter()
            .any(|h| h.patterns.iter().any(|p| p.pattern_id == "CHURN-01"));
        let churn_label = if is_churning { " [CHURN]" } else { "" };

        lines.push(format!(
            "  {} — {}{}: {}{}",
            file.path, top_category, high_label, top_pattern, churn_label
        ));
    }

    if classified.files.len() > 10 {
        lines.push(format!(
            "  ... and {} more files",
            classified.files.len() - 10
        ));
    }

    Some(lines.join("\n"))
}
