use chrono::Utc;
use ecotokens::metrics::report::{aggregate, Period};
use ecotokens::metrics::store::{CommandFamily, FilterMode, HookType, Interception};

fn make_interception_ago(
    seconds_ago: i64,
    family: CommandFamily,
    tokens_before: u32,
    tokens_after: u32,
) -> Interception {
    let ts = (Utc::now() - chrono::Duration::seconds(seconds_ago)).to_rfc3339();
    Interception {
        id: uuid::Uuid::new_v4().to_string(),
        timestamp: ts,
        command: "git status".into(),
        command_family: family,
        git_root: Some("/repo".into()),
        tokens_before,
        tokens_after,
        savings_pct: if tokens_before == 0 {
            0.0
        } else {
            ((1.0 - tokens_after as f64 / tokens_before as f64) * 100.0) as f32
        },
        mode: if tokens_after < tokens_before {
            FilterMode::Filtered
        } else {
            FilterMode::Passthrough
        },
        redacted: false,
        duration_ms: 5,
        content_before: None,
        content_after: None,
        hook_type: HookType::PreToolUse,
    }
}

fn make_items() -> Vec<Interception> {
    vec![
        make_interception_ago(10, CommandFamily::Git, 1000, 200), // today, 80% savings
        make_interception_ago(100, CommandFamily::Cargo, 500, 400), // today, 20% savings
        make_interception_ago(86500, CommandFamily::Git, 800, 300), // yesterday
    ]
}

#[test]
fn gain_shows_native_read_savings() {
    let mut items = make_items();
    // Add PostToolUse NativeRead interception
    let mut native_read = make_interception_ago(5, CommandFamily::NativeRead, 2000, 300);
    native_read.hook_type = HookType::PostToolUse;
    native_read.command = "src/main.rs".to_string();
    items.push(native_read);

    let report = aggregate(&items, Period::All, "claude-sonnet-4-6");

    // NativeRead should appear in by_family with key "native_read" (serde snake_case)
    assert!(
        report.by_family.contains_key("native_read"),
        "by_family should contain 'native_read', got keys: {:?}",
        report.by_family.keys().collect::<Vec<_>>()
    );
    let stats = &report.by_family["native_read"];
    assert_eq!(stats.count, 1);
    assert_eq!(stats.tokens_before, 2000);
    assert_eq!(stats.tokens_after, 300);
}

#[test]
fn aggregate_all_includes_all_items() {
    let items = make_items();
    let report = aggregate(&items, Period::All, "claude-sonnet-4-6");
    assert_eq!(report.total_interceptions, 3);
}

#[test]
fn aggregate_today_filters_to_today() {
    let items = make_items();
    let report = aggregate(&items, Period::Today, "claude-sonnet-4-6");
    assert_eq!(report.total_interceptions, 2, "only today's items");
}

#[test]
fn aggregate_week_includes_recent_items() {
    let items = make_items();
    let report = aggregate(&items, Period::Week, "claude-sonnet-4-6");
    assert_eq!(report.total_interceptions, 3, "all within a week");
}

#[test]
fn savings_pct_calculated() {
    let items = make_items();
    let report = aggregate(&items, Period::All, "claude-sonnet-4-6");
    assert!(
        report.total_savings_pct > 0.0,
        "should have positive savings"
    );
}

#[test]
fn by_family_groups_correctly() {
    let items = make_items();
    let report = aggregate(&items, Period::All, "claude-sonnet-4-6");
    assert!(
        report.by_family.contains_key("git"),
        "should have git family"
    );
    assert!(
        report.by_family.contains_key("cargo"),
        "should have cargo family"
    );
    let git_stats = &report.by_family["git"];
    assert_eq!(git_stats.count, 2, "two git interceptions");
}

#[test]
fn by_project_groups_by_git_root() {
    let items = make_items();
    let report = aggregate(&items, Period::All, "claude-sonnet-4-6");
    assert!(
        report.by_project.contains_key("/repo"),
        "should group by /repo"
    );
}

#[test]
fn by_project_blank_git_root_groups_under_unknown() {
    let mut items = make_items();
    let mut empty_root = make_interception_ago(5, CommandFamily::Git, 1200, 600);
    empty_root.git_root = Some("   ".to_string());
    items.push(empty_root);

    let report = aggregate(&items, Period::All, "claude-sonnet-4-6");
    assert!(
        !report.by_project.contains_key(""),
        "blank git_root should not create an empty-string key"
    );
    assert!(
        report.by_project.contains_key("(unknown)"),
        "blank git_root should be grouped under (unknown)"
    );
    assert_eq!(
        report.by_project.len(),
        2,
        "/repo and (unknown) should both be present in by_project"
    );
}

#[test]
fn cost_avoided_usd_positive_for_savings() {
    let items = make_items();
    let report = aggregate(&items, Period::All, "claude-sonnet-4-6");
    assert!(
        report.cost_avoided_usd > 0.0,
        "cost avoided should be positive"
    );
}

#[test]
fn json_output_is_valid() {
    let items = make_items();
    let report = aggregate(&items, Period::All, "claude-sonnet-4-6");
    let json = serde_json::to_string(&report).unwrap();
    let parsed: serde_json::Value = serde_json::from_str(&json).unwrap();
    assert!(parsed["total_interceptions"].is_number());
    assert!(parsed["cost_avoided_usd"].is_number());
}

#[test]
fn history_ordered_by_date_descending() {
    let items = make_items();
    // history is the raw items; report creation doesn't reorder, but aggregate produces from items
    let report = aggregate(&items, Period::All, "claude-sonnet-4-6");
    // simply verify the report has the correct count — ordering is the consumer's responsibility
    assert_eq!(report.total_interceptions, 3);
}
