use fp_checker_core::{
    check_path, discover_config, load_config_from_path, CheckerConfig, OutputFormat, RuleId,
    Severity,
};
use std::collections::BTreeSet;
use std::path::PathBuf;

fn fixture_path(relative: &str) -> PathBuf {
    PathBuf::from(env!("CARGO_MANIFEST_DIR"))
        .join("../../tests/fixtures")
        .join(relative)
}

#[test]
fn discover_config_finds_dedicated_config_file_from_nested_path() {
    let target = fixture_path("standalone_config/src/module/app.py");
    let discovered = discover_config(&target).unwrap();

    assert_eq!(
        discovered.file_name().and_then(|name| name.to_str()),
        Some(".fp-checker.toml")
    );
}

#[test]
fn dedicated_config_file_is_loaded_correctly() {
    let config = load_config_from_path(fixture_path("standalone_config/.fp-checker.toml")).unwrap();

    assert!(matches!(config.format, OutputFormat::Text));
    assert_eq!(config.ignore_decorator_name, "custom_ignore");
    assert!(config.is_rule_enabled(&RuleId::DirectIo));
    assert!(!config.is_rule_enabled(&RuleId::MutableGlobalState));
}

#[test]
fn merge_overrides_existing_values() {
    let base = CheckerConfig::default();
    let override_file =
        load_config_from_path(fixture_path("standalone_config/.fp-checker.toml")).unwrap();

    assert_ne!(
        base.ignore_decorator_name,
        override_file.ignore_decorator_name
    );
}

#[test]
fn config_loader_rejects_unknown_keys() {
    let error = load_config_from_path(fixture_path("invalid_config.toml")).unwrap_err();

    assert!(error.to_string().contains("unknown field `unknown_option`"));
}

#[test]
fn include_patterns_are_resolved_from_config_base_dir_for_directory_targets() {
    let config = load_config_from_path(fixture_path("standalone_config/.fp-checker.toml")).unwrap();
    let report = check_path(fixture_path("standalone_config"), &config).unwrap();

    assert_eq!(report.summary.files, 1);
    assert_eq!(report.summary.checked_functions, 1);
}

#[test]
fn include_patterns_are_resolved_from_config_base_dir_for_file_targets() {
    let config = load_config_from_path(fixture_path("standalone_config/.fp-checker.toml")).unwrap();
    let report = check_path(fixture_path("standalone_config/src/module/app.py"), &config).unwrap();

    assert_eq!(report.summary.files, 1);
    assert_eq!(report.summary.checked_functions, 1);
}

#[test]
fn custom_rules_are_loaded_and_executed_from_config_file() {
    let config = load_config_from_path(fixture_path("custom_rules/.fp-checker.toml")).unwrap();
    let report = check_path(fixture_path("custom_rules"), &config).unwrap();

    assert_eq!(config.custom_rules.len(), 3);
    assert!(config.is_rule_enabled(&"no_eval".parse::<RuleId>().unwrap()));
    assert_eq!(report.summary.files, 1);
    assert_eq!(report.summary.diagnostics, 3);

    let diagnostics = &report.files[0].diagnostics;
    let rule_ids = diagnostics
        .iter()
        .map(|diagnostic| diagnostic.rule_id.as_str().to_owned())
        .collect::<BTreeSet<_>>();

    assert_eq!(
        rule_ids,
        BTreeSet::from([
            "no_context_write".to_owned(),
            "no_env_reads".to_owned(),
            "no_eval".to_owned(),
        ])
    );
    assert!(diagnostics
        .iter()
        .all(|diagnostic| diagnostic.fix_hint.is_some()));
    assert!(diagnostics
        .iter()
        .any(|diagnostic| diagnostic.rule_id.as_str() == "no_env_reads"
            && diagnostic.severity == Severity::Error));
}

#[test]
fn config_loader_rejects_builtin_rule_id_for_custom_rule() {
    let error = load_config_from_path(fixture_path("invalid_custom_rule.toml")).unwrap_err();

    assert!(error
        .to_string()
        .contains("conflicts with a built-in rule id"));
}

#[test]
fn validation_rejects_unknown_custom_rule_selection() {
    let config = CheckerConfig {
        enabled_rules: Some(BTreeSet::from(["no_eval".parse::<RuleId>().unwrap()])),
        ..CheckerConfig::default()
    };

    let error = config.validate().unwrap_err();
    assert!(error.to_string().contains("unknown rule selection"));
}
