use fp_checker_core::{check_code, CheckerConfig, RuleId};
use std::collections::BTreeSet;

fn run_rule(rule_id: &RuleId, source: &str) -> Vec<fp_checker_core::Diagnostic> {
    let config = CheckerConfig {
        enabled_rules: Some(BTreeSet::from([rule_id.clone()])),
        ..CheckerConfig::default()
    };
    let report = check_code(source, "memory.py", &config);
    report
        .files
        .into_iter()
        .flat_map(|file| file.diagnostics)
        .filter(|diagnostic| diagnostic.rule_id.as_str() == rule_id.as_str())
        .collect()
}

#[test]
fn mutable_global_state_rule_detects_reference() {
    let diagnostics = run_rule(
        &RuleId::MutableGlobalState,
        "STATE = []\n\ndef f():\n    return STATE\n",
    );
    assert_eq!(diagnostics.len(), 1);
}

#[test]
fn assignment_overuse_rule_detects_multiple_assignments() {
    let diagnostics = run_rule(
        &RuleId::AssignmentOveruse,
        "def f(value):\n    a = value\n    b = a\n    c = b\n    d = c\n    return d\n",
    );
    assert_eq!(diagnostics.len(), 1);
}

#[test]
fn direct_io_rule_detects_print_and_open() {
    let diagnostics = run_rule(
        &RuleId::DirectIo,
        "def f(path):\n    print(path)\n    return open(path)\n",
    );
    assert_eq!(diagnostics.len(), 2);
}

#[test]
fn non_deterministic_rule_detects_random_and_time() {
    let diagnostics = run_rule(
        &RuleId::NonDeterministicUse,
        "import random\nimport time\n\ndef f():\n    return random.random() + time.time()\n",
    );
    assert_eq!(diagnostics.len(), 2);
}

#[test]
fn external_state_side_effect_rule_detects_attribute_write() {
    let diagnostics = run_rule(
        &RuleId::ExternalStateSideEffect,
        "def f(obj):\n    obj.value = 1\n    obj.save()\n    return obj\n",
    );
    assert!(diagnostics.len() >= 2);
}

#[test]
fn mutable_default_argument_rule_detects_mutable_defaults() {
    let diagnostics = run_rule(
        &RuleId::MutableDefaultArgument,
        "def f(items=[], mapping={}):\n    return items, mapping\n",
    );
    assert_eq!(diagnostics.len(), 2);
}

#[test]
fn direct_effectful_call_rule_detects_logging() {
    let diagnostics = run_rule(
        &RuleId::DirectEffectfulCall,
        "import logging\n\ndef f():\n    logging.info('hello')\n",
    );
    assert_eq!(diagnostics.len(), 1);
}

#[test]
fn global_nonlocal_rule_detects_global_and_nonlocal() {
    let diagnostics = run_rule(
        &RuleId::GlobalNonlocalUsage,
        "STATE = 0\n\ndef outer():\n    value = 0\n    def inner():\n        nonlocal value\n        global STATE\n        STATE = value\n    return inner\n",
    );
    assert_eq!(diagnostics.len(), 2);
}

#[test]
fn hidden_mutation_rule_detects_mutating_parameter_call() {
    let diagnostics = run_rule(
        &RuleId::HiddenMutation,
        "def f(items):\n    items.append(1)\n    return items\n",
    );
    assert_eq!(diagnostics.len(), 1);
}

#[test]
fn loop_side_effect_rule_detects_effectful_calls_inside_loop() {
    let diagnostics = run_rule(
        &RuleId::LoopSideEffect,
        "def f(items):\n    for item in items:\n        print(item)\n    return len(items)\n",
    );
    assert_eq!(diagnostics.len(), 1);
}

#[test]
fn exception_control_flow_rule_detects_try_and_raise_usage() {
    let diagnostics = run_rule(
        &RuleId::ExceptionControlFlow,
        "def f(flag):\n    try:\n        if flag:\n            raise ValueError('x')\n    except ValueError:\n        return 1\n    return 0\n",
    );
    assert_eq!(diagnostics.len(), 1);
}

#[test]
fn class_method_side_effect_rule_detects_self_mutation() {
    let diagnostics = run_rule(
        &RuleId::ClassMethodSideEffect,
        "class Service:\n    def set_value(self, value):\n        self.value = value\n        return value\n",
    );
    assert_eq!(diagnostics.len(), 1);
}

#[test]
fn referential_transparency_break_rule_detects_ambient_dependencies() {
    let diagnostics = run_rule(
        &RuleId::ReferentialTransparencyBreak,
        "import os\nimport sys\n\ndef f(expr):\n    value = eval(expr)\n    return value, os.environ['TOKEN'], sys.argv[0]\n",
    );
    assert_eq!(diagnostics.len(), 3);
}

#[test]
fn deprecated_api_use_rule_detects_known_calls() {
    let diagnostics = run_rule(
        &RuleId::DeprecatedApiUse,
        "import datetime\nimport locale\nimport logging\n\ndef f():\n    logging.warn('x')\n    now = datetime.datetime.utcnow()\n    return now, locale.getdefaultlocale()\n",
    );
    assert_eq!(diagnostics.len(), 3);
}

#[test]
fn loop_side_effect_rule_escalates_nested_effects() {
    let diagnostics = run_rule(
        &RuleId::LoopSideEffect,
        "def f(items):\n    for group in items:\n        for item in group:\n            print(item)\n    return len(items)\n",
    );
    assert_eq!(diagnostics.len(), 1);
    assert_eq!(diagnostics[0].severity.to_string(), "error");
    assert!(diagnostics[0].message.contains("weight=3"));
}
