//! ccstat - Analyze Claude Code usage data from local JSONL files

use ccstat::{
    aggregation::{
        Aggregator, BillingBlockParams, Totals, create_and_filter_billing_blocks,
        filter_monthly_data,
    },
    cli::{Cli, Command, parse_date_filter},
    cost_calculator::CostCalculator,
    data_loader::DataLoader,
    error::Result,
    filters::{MonthFilter, UsageFilter},
    live_monitor::{CommandType, LiveMonitor},
    output::get_formatter,
    pricing_fetcher::PricingFetcher,
    timezone::TimezoneConfig,
};
use chrono::Datelike;
use clap::Parser;
use std::sync::Arc;
use tracing::info;
use tracing_subscriber::{layer::SubscriberExt, util::SubscriberInitExt};

/// Approximate maximum tokens for a 5-hour billing block
/// This is an empirical value based on typical usage patterns
const APPROX_MAX_TOKENS_PER_BLOCK: f64 = 10_000_000.0;

/// Helper function to create an aggregator with timezone configuration
fn create_aggregator_with_timezone(
    cost_calculator: Arc<CostCalculator>,
    show_progress: bool,
    timezone: Option<&str>,
    utc: bool,
) -> Result<Aggregator> {
    let tz_config = TimezoneConfig::from_cli(timezone, utc)?;
    info!("Using timezone: {}", tz_config.display_name());

    Ok(Aggregator::new(cost_calculator, tz_config).with_progress(show_progress))
}

/// Helper function to initialize a DataLoader with common configuration
async fn init_data_loader(show_progress: bool, intern: bool, arena: bool) -> Result<DataLoader> {
    Ok(DataLoader::new()
        .await?
        .with_progress(show_progress)
        .with_interning(intern)
        .with_arena(arena))
}

/// Helper function to build a UsageFilter from CLI arguments
fn build_usage_filter(cli: &Cli, aggregator: &Aggregator) -> Result<UsageFilter> {
    let mut filter = UsageFilter::new();

    if let Some(since_str) = &cli.since {
        let since_date = parse_date_filter(since_str)?;
        filter = filter.with_since(since_date);
    }
    if let Some(until_str) = &cli.until {
        let until_date = parse_date_filter(until_str)?;
        filter = filter.with_until(until_date);
    }
    if let Some(project_name) = &cli.project {
        filter = filter.with_project(project_name.clone());
    }
    // Apply timezone to filter
    filter = filter.with_timezone(aggregator.timezone_config().tz);

    Ok(filter)
}

#[tokio::main]
async fn main() -> Result<()> {
    // Parse CLI arguments
    let cli = Cli::parse();

    // Skip logging initialization for statusline command
    let is_statusline = matches!(cli.command, Some(Command::Statusline { .. }));

    if !is_statusline {
        // Initialize logging. Default is quiet (warn level), --verbose enables info level.
        // RUST_LOG environment variable can override these defaults.
        let default_level = if cli.verbose { "ccstat=info" } else { "warn" };
        let filter = tracing_subscriber::EnvFilter::try_from_default_env()
            .unwrap_or_else(|_| tracing_subscriber::EnvFilter::new(default_level));

        tracing_subscriber::registry()
            .with(filter)
            .with(tracing_subscriber::fmt::layer())
            .init();
    }

    // Handle commands - default to daily if no command specified
    match &cli.command {
        Some(Command::Daily {
            instances,
            detailed,
        }) => {
            handle_daily_command(&cli, *instances, *detailed).await?;
        }

        Some(Command::Monthly) => {
            info!("Running monthly usage report");

            // Initialize components with progress bars enabled for terminal output
            let show_progress =
                !cli.json && !cli.watch && is_terminal::is_terminal(std::io::stdout());
            let data_loader =
                Arc::new(init_data_loader(show_progress, cli.intern, cli.arena).await?);
            let pricing_fetcher = Arc::new(PricingFetcher::new(false).await);
            let cost_calculator = Arc::new(CostCalculator::new(pricing_fetcher));
            let aggregator = Arc::new(create_aggregator_with_timezone(
                cost_calculator,
                show_progress,
                cli.timezone.as_deref(),
                cli.utc,
            )?);

            // Build month filter
            let mut month_filter = MonthFilter::new();

            if let Some(since_str) = &cli.since {
                let since_date = parse_date_filter(since_str)?;
                month_filter = month_filter.with_since(since_date.year(), since_date.month());
            }
            if let Some(until_str) = &cli.until {
                let until_date = parse_date_filter(until_str)?;
                month_filter = month_filter.with_until(until_date.year(), until_date.month());
            }

            // Check if we're in watch mode
            if cli.watch {
                info!("Starting live monitoring mode");
                // Create empty filter for monthly (we'll filter after aggregation)
                let filter = UsageFilter::new();
                let monitor = LiveMonitor::new(
                    data_loader,
                    aggregator,
                    filter,
                    Some(month_filter), // Pass month filter
                    cli.mode,
                    cli.json,
                    CommandType::Monthly,
                    cli.interval,
                    cli.full_model_names,
                );
                monitor.run().await?;
            } else {
                // Load entries and aggregate data
                let entries = Box::pin(data_loader.load_usage_entries_parallel());
                let daily_data = aggregator.aggregate_daily(entries, cli.mode).await?;
                let mut monthly_data = Aggregator::aggregate_monthly(&daily_data);

                // Apply month filter to aggregated monthly data
                filter_monthly_data(&mut monthly_data, &month_filter);

                let totals = Totals::from_monthly(&monthly_data);

                // Format and output
                let formatter = get_formatter(cli.json, cli.full_model_names);
                println!("{}", formatter.format_monthly(&monthly_data, &totals));
            }
        }

        Some(Command::Session) => {
            info!("Running session usage report");

            // Initialize components with progress bars enabled for terminal output
            let show_progress =
                !cli.json && !cli.watch && is_terminal::is_terminal(std::io::stdout());
            let data_loader =
                Arc::new(init_data_loader(show_progress, cli.intern, cli.arena).await?);
            let pricing_fetcher = Arc::new(PricingFetcher::new(false).await);
            let cost_calculator = Arc::new(CostCalculator::new(pricing_fetcher));
            let aggregator = Arc::new(create_aggregator_with_timezone(
                cost_calculator,
                show_progress,
                cli.timezone.as_deref(),
                cli.utc,
            )?);

            // Build filter
            let filter = build_usage_filter(&cli, &aggregator)?;

            // Check if we're in watch mode
            if cli.watch {
                info!("Starting live monitoring mode");
                let monitor = LiveMonitor::new(
                    data_loader,
                    aggregator.clone(),
                    filter,
                    None, // No month filter for sessions
                    cli.mode,
                    cli.json,
                    CommandType::Session,
                    cli.interval,
                    cli.full_model_names,
                );
                monitor.run().await?;
            } else {
                // Load, filter and aggregate data
                let entries = Box::pin(data_loader.load_usage_entries_parallel());
                let filtered_entries = filter.filter_stream(entries).await;
                let session_data = aggregator
                    .aggregate_sessions(filtered_entries, cli.mode)
                    .await?;
                let totals = Totals::from_sessions(&session_data);

                // Format and output
                let formatter = get_formatter(cli.json, cli.full_model_names);
                println!(
                    "{}",
                    formatter.format_sessions(
                        &session_data,
                        &totals,
                        &aggregator.timezone_config().tz
                    )
                );
            }
        }

        Some(Command::Blocks {
            active,
            recent,
            token_limit,
            session_duration,
        }) => {
            info!("Running billing blocks report");

            // Initialize components with progress bars enabled for terminal output
            let show_progress =
                !cli.json && !cli.watch && is_terminal::is_terminal(std::io::stdout());
            let data_loader =
                Arc::new(init_data_loader(show_progress, cli.intern, cli.arena).await?);
            let pricing_fetcher = Arc::new(PricingFetcher::new(false).await);
            let cost_calculator = Arc::new(CostCalculator::new(pricing_fetcher));
            let aggregator = Arc::new(create_aggregator_with_timezone(
                cost_calculator,
                show_progress,
                cli.timezone.as_deref(),
                cli.utc,
            )?);

            // Build filter for date ranges
            let filter = build_usage_filter(&cli, &aggregator)?;

            // Check if we're in watch mode
            if cli.watch {
                info!("Starting live monitoring mode");
                let monitor = LiveMonitor::new(
                    data_loader,
                    aggregator,
                    filter,
                    None, // No month filter for blocks
                    cli.mode,
                    cli.json,
                    CommandType::Blocks {
                        active: *active,
                        recent: *recent,
                        token_limit: token_limit.clone(),
                        session_duration: *session_duration,
                    },
                    cli.interval,
                    cli.full_model_names,
                );
                monitor.run().await?;
            } else {
                // Reuse the already-parsed dates from the filter
                let since_date = filter.since_date;
                let until_date = filter.until_date;

                let params = BillingBlockParams {
                    data_loader: &data_loader,
                    aggregator: &aggregator,
                    cost_mode: cli.mode,
                    session_duration_hours: *session_duration,
                    project: cli.project.as_deref(),
                    since_date,
                    until_date,
                    active: *active,
                    recent: *recent,
                    token_limit: token_limit.as_deref(),
                    approx_max_tokens: APPROX_MAX_TOKENS_PER_BLOCK,
                };
                let blocks = create_and_filter_billing_blocks(params).await?;

                // Format and output
                let formatter = get_formatter(cli.json, cli.full_model_names);
                println!(
                    "{}",
                    formatter.format_blocks(&blocks, &aggregator.timezone_config().tz)
                );
            }
        }

        Some(Command::Statusline {
            monthly_fee,
            no_color,
            show_date,
            show_git,
        }) => {
            // Run statusline handler
            ccstat::statusline::run(*monthly_fee, *no_color, *show_date, *show_git).await?;
        }

        None => {
            // Default to daily report
            info!("No command specified, defaulting to daily report");
            handle_daily_command(&cli, false, false).await?;
        }
    }

    Ok(())
}

/// Handle daily command logic - shared between explicit daily command and default behavior
async fn handle_daily_command(cli: &Cli, instances: bool, detailed: bool) -> Result<()> {
    info!("Running daily usage report");

    // Initialize components with progress bars enabled for terminal output
    let show_progress = !cli.json && !cli.watch && is_terminal::is_terminal(std::io::stdout());
    let data_loader = Arc::new(init_data_loader(show_progress, cli.intern, cli.arena).await?);
    let pricing_fetcher = Arc::new(PricingFetcher::new(false).await);
    let cost_calculator = Arc::new(CostCalculator::new(pricing_fetcher));
    let aggregator = Arc::new(create_aggregator_with_timezone(
        cost_calculator,
        show_progress,
        cli.timezone.as_deref(),
        cli.utc,
    )?);

    // Build filter
    let filter = build_usage_filter(cli, &aggregator)?;

    // Check if we're in watch mode
    if cli.watch {
        info!("Starting live monitoring mode");
        let monitor = LiveMonitor::new(
            data_loader,
            aggregator,
            filter,
            None, // No month filter for daily
            cli.mode,
            cli.json,
            CommandType::Daily {
                instances,
                detailed,
            },
            cli.interval,
            cli.full_model_names,
        );
        monitor.run().await?;
    } else {
        // Handle instances flag
        if instances {
            // Load and filter entries, then group by instance
            let entries = Box::pin(data_loader.load_usage_entries_parallel());
            let filtered_entries = filter.filter_stream(entries).await;
            let instance_data = aggregator
                .aggregate_daily_by_instance(filtered_entries, cli.mode)
                .await?;
            let totals = Totals::from_daily_instances(&instance_data);
            let formatter = get_formatter(cli.json, cli.full_model_names);
            println!(
                "{}",
                formatter.format_daily_by_instance(&instance_data, &totals)
            );
        } else {
            // Load and filter entries, then aggregate normally
            let entries = Box::pin(data_loader.load_usage_entries_parallel());
            let filtered_entries = filter.filter_stream(entries).await;
            let daily_data = aggregator
                .aggregate_daily_detailed(filtered_entries, cli.mode, detailed)
                .await?;
            let totals = Totals::from_daily(&daily_data);
            let formatter = get_formatter(cli.json, cli.full_model_names);
            println!("{}", formatter.format_daily(&daily_data, &totals));
        }
    }

    Ok(())
}
