mod api;
mod archive;
mod claude_session;
mod config;
mod error;
mod github;
mod session_selector;

use std::time::Duration;

use anyhow::Result;
use api::{ReviewApiClient, ReviewStatus, StartRequest};
use clap::Parser;
use error::ReviewError;
use github::{checkout_commit, clone_repo, get_pr_info, parse_pr_url};
use indicatif::{ProgressBar, ProgressStyle};
use tempfile::TempDir;
use tracing::debug;
use tracing_subscriber::EnvFilter;

const DEFAULT_API_URL: &str = "https://api.vibekanban.com";
const POLL_INTERVAL: Duration = Duration::from_secs(10);
const TIMEOUT: Duration = Duration::from_secs(600); // 10 minutes

const BANNER: &str = r#"
██████╗ ███████╗██╗   ██╗██╗███████╗██╗    ██╗   ███████╗ █████╗ ███████╗████████╗
██╔══██╗██╔════╝██║   ██║██║██╔════╝██║    ██║   ██╔════╝██╔══██╗██╔════╝╚══██╔══╝
██████╔╝█████╗  ██║   ██║██║█████╗  ██║ █╗ ██║   █████╗  ███████║███████╗   ██║   
██╔══██╗██╔══╝  ╚██╗ ██╔╝██║██╔══╝  ██║███╗██║   ██╔══╝  ██╔══██║╚════██║   ██║   
██║  ██║███████╗ ╚████╔╝ ██║███████╗╚███╔███╔╝██╗██║     ██║  ██║███████║   ██║   
╚═╝  ╚═╝╚══════╝  ╚═══╝  ╚═╝╚══════╝ ╚══╝╚══╝ ╚═╝╚═╝     ╚═╝  ╚═╝╚══════╝   ╚═╝   

"#;

#[derive(Parser, Debug)]
#[command(name = "review")]
#[command(
    about = "Vibe-Kanban Review helps you review GitHub pull requests by turning them into a clear, story-driven summary instead of a wall of diffs. You provide a pull request URL, optionally link a Claude Code project for additional context, and it builds a narrative that highlights key events and important decisions, helping you prioritise what actually needs attention. It's particularly useful when reviewing large amounts of AI-generated code. Note that code is uploaded to and processed on Vibe-Kanban servers using AI."
)]
#[command(version)]
struct Args {
    /// GitHub PR URL (e.g., https://github.com/owner/repo/pull/123)
    pr_url: String,

    /// Enable verbose output
    #[arg(short, long, default_value_t = false)]
    verbose: bool,

    /// API base URL
    #[arg(long, env = "REVIEW_API_URL", default_value = DEFAULT_API_URL)]
    api_url: String,
}

fn show_disclaimer() {
    println!();
    println!(
        "DISCLAIMER: Your code will be processed on our secure remote servers, all artefacts (code, AI logs, etc...) will be deleted after 14 days."
    );
    println!();
    println!("Full terms and conditions and privacy policy: https://review.fast/terms");
    println!();
    println!("Press Enter to accept and continue...");

    let mut input = String::new();
    std::io::stdin().read_line(&mut input).ok();
}

fn prompt_email(config: &mut config::Config) -> String {
    use dialoguer::Input;

    let mut input: Input<String> =
        Input::new().with_prompt("Email address (we'll send a link to the review here, no spam)");

    if let Some(ref saved_email) = config.email {
        input = input.default(saved_email.clone());
    }

    let email: String = input.interact_text().expect("Failed to read email");

    // Save email for next time
    config.email = Some(email.clone());
    if let Err(e) = config.save() {
        debug!("Failed to save config: {}", e);
    }

    email
}

fn create_spinner(message: &str) -> ProgressBar {
    let spinner = ProgressBar::new_spinner();
    spinner.set_style(
        ProgressStyle::default_spinner()
            .template("{spinner:.green} {msg}")
            .expect("Invalid spinner template"),
    );
    spinner.set_message(message.to_string());
    spinner.enable_steady_tick(Duration::from_millis(100));
    spinner
}

#[tokio::main]
async fn main() -> Result<()> {
    // Install rustls crypto provider before any TLS operations
    rustls::crypto::aws_lc_rs::default_provider()
        .install_default()
        .expect("Failed to install rustls crypto provider");

    let args = Args::parse();

    // Initialize tracing
    let filter = if args.verbose {
        EnvFilter::new("debug")
    } else {
        EnvFilter::new("warn")
    };
    tracing_subscriber::fmt().with_env_filter(filter).init();

    println!("{}", BANNER);

    show_disclaimer();

    debug!("Args: {:?}", args);

    // Run the main flow and handle errors
    if let Err(e) = run(args).await {
        eprintln!("Error: {e}");
        std::process::exit(1);
    }

    Ok(())
}

async fn run(args: Args) -> Result<(), ReviewError> {
    // 1. Load config and prompt for email
    let mut config = config::Config::load();
    let email = prompt_email(&mut config);

    // 2. Parse PR URL
    let spinner = create_spinner("Parsing PR URL...");
    let (owner, repo, pr_number) = parse_pr_url(&args.pr_url)?;
    spinner.finish_with_message(format!("PR: {owner}/{repo}#{pr_number}"));

    // 3. Get PR info
    let spinner = create_spinner("Fetching PR information...");
    let pr_info = get_pr_info(&owner, &repo, pr_number)?;
    spinner.finish_with_message(format!("PR: {}", pr_info.title));

    // 4. Select Claude Code session (optional)
    let session_files = match session_selector::select_session(&pr_info.head_ref_name) {
        Ok(session_selector::SessionSelection::Selected(files)) => {
            println!("  Selected {} session file(s)", files.len());
            Some(files)
        }
        Ok(session_selector::SessionSelection::Skipped) => {
            println!("  Skipping project attachment");
            None
        }
        Err(e) => {
            debug!("Session selection error: {}", e);
            println!("  No sessions found");
            None
        }
    };

    // 5. Clone repository to temp directory
    let temp_dir = TempDir::new().map_err(|e| ReviewError::CloneFailed(e.to_string()))?;
    let repo_dir = temp_dir.path().join(&repo);

    let spinner = create_spinner("Cloning repository...");
    clone_repo(&owner, &repo, &repo_dir)?;
    spinner.finish_with_message("Repository cloned");

    // 6. Checkout PR head commit
    let spinner = create_spinner("Checking out PR...");
    checkout_commit(&pr_info.head_commit, &repo_dir)?;
    spinner.finish_with_message("PR checked out");

    // 7. Create tarball (with optional session data)
    let spinner = create_spinner("Creating archive...");

    // If sessions were selected, write .agent-messages.json to repo root
    if let Some(ref files) = session_files {
        let json_content = claude_session::concatenate_sessions_to_json(files)?;
        let agent_messages_path = repo_dir.join(".agent-messages.json");
        std::fs::write(&agent_messages_path, json_content)
            .map_err(|e| ReviewError::ArchiveFailed(e.to_string()))?;
    }

    let payload = archive::create_tarball(&repo_dir)?;
    let size_mb = payload.len() as f64 / 1_048_576.0;
    spinner.finish_with_message(format!("Archive created ({size_mb:.2} MB)"));

    // 8. Initialize review
    let client = ReviewApiClient::new(args.api_url.clone());
    let spinner = create_spinner("Initializing review...");
    let init_response = client.init(&args.pr_url, &email, &pr_info.title).await?;
    spinner.finish_with_message(format!("Review ID: {}", init_response.review_id));

    // 9. Upload archive
    let spinner = create_spinner("Uploading archive...");
    client.upload(&init_response.upload_url, payload).await?;
    spinner.finish_with_message("Upload complete");

    // 10. Start review
    let spinner = create_spinner("Starting review...");
    let codebase_url = format!("r2://{}", init_response.object_key);
    client
        .start(StartRequest {
            id: init_response.review_id.to_string(),
            title: pr_info.title,
            description: pr_info.description,
            org: pr_info.owner,
            repo: pr_info.repo,
            codebase_url,
            base_commit: pr_info.base_commit,
        })
        .await?;
    spinner.finish_with_message(format!("Review started, we'll send you an email at {} when the review is ready. This can take a few minutes, you may now close the terminal", email));

    // 11. Poll for completion
    let spinner = create_spinner("Review in progress...");
    let start_time = std::time::Instant::now();

    loop {
        tokio::time::sleep(POLL_INTERVAL).await;

        // Check for timeout
        if start_time.elapsed() > TIMEOUT {
            spinner.finish_with_message("Timed out");
            return Err(ReviewError::Timeout);
        }

        let status = client
            .poll_status(&init_response.review_id.to_string())
            .await?;

        match status.status {
            ReviewStatus::Completed => {
                spinner.finish_with_message("Review completed!");
                break;
            }
            ReviewStatus::Failed => {
                spinner.finish_with_message("Review failed");
                let error_msg = status.error.unwrap_or_else(|| "Unknown error".to_string());
                return Err(ReviewError::ReviewFailed(error_msg));
            }
            _ => {
                let progress = status.progress.unwrap_or_else(|| status.status.to_string());
                spinner.set_message(format!("Review in progress: {progress}"));
            }
        }
    }

    // 12. Print result URL
    let review_url = client.review_url(&init_response.review_id.to_string());
    println!("\nReview available at:");
    println!("  {review_url}");

    Ok(())
}
