mod common;

use std::path::PathBuf;

use ryl::config::{
    FixRule, FixRuleSelector, Overrides, discover_config, discover_config_with,
};

#[test]
fn yaml_config_rejects_fix_table() {
    let err = discover_config(
        &[],
        &Overrides {
            config_file: None,
            config_data: Some("fix:\n  fixable: [comments]\n".to_string()),
        },
    )
    .expect_err("yaml config should reject fix table");

    assert_eq!(
        err,
        "invalid config: fix is only supported in TOML configuration"
    );
}

#[test]
fn toml_config_parses_fix_policy() {
    let cfg = PathBuf::from("/repo/.ryl.toml");
    let env = common::fake_env::FakeEnv::new().with_file(
        cfg.clone(),
        "[fix]\nfixable = ['comments']\nunfixable = ['new-lines']\n",
    );

    let ctx = discover_config_with(
        &[],
        &Overrides {
            config_file: Some(cfg),
            config_data: None,
        },
        &env,
    )
    .expect("toml config should parse");

    assert_eq!(
        ctx.config.fix().fixable(),
        [FixRuleSelector::Rule(FixRule::Comments)]
    );
    assert_eq!(ctx.config.fix().unfixable(), [FixRule::NewLines]);
    assert!(ctx.config.fix().allows_rule("comments"));
    assert!(!ctx.config.fix().allows_rule("new-lines"));
    assert!(!ctx.config.fix().allows_rule("new-line-at-end-of-file"));
}

#[test]
fn toml_config_parses_new_safe_fix_rules() {
    let cfg = PathBuf::from("/repo/.ryl.toml");
    let env = common::fake_env::FakeEnv::new().with_file(
        cfg.clone(),
        "[fix]\nfixable = ['braces', 'brackets', 'commas', 'comments-indentation']\nunfixable = ['braces']\n",
    );

    let ctx = discover_config_with(
        &[],
        &Overrides {
            config_file: Some(cfg),
            config_data: None,
        },
        &env,
    )
    .expect("toml config should parse new fix rules");

    assert_eq!(
        ctx.config.fix().fixable(),
        [
            FixRuleSelector::Rule(FixRule::Braces),
            FixRuleSelector::Rule(FixRule::Brackets),
            FixRuleSelector::Rule(FixRule::Commas),
            FixRuleSelector::Rule(FixRule::CommentsIndentation),
        ]
    );
    assert_eq!(ctx.config.fix().unfixable(), [FixRule::Braces]);
    assert!(!ctx.config.fix().allows_rule("braces"));
    assert!(ctx.config.fix().allows_rule("brackets"));
    assert!(ctx.config.fix().allows_rule("commas"));
    assert!(ctx.config.fix().allows_rule("comments-indentation"));
}

#[test]
fn toml_config_parses_exact_typed_fix_variants() {
    let cfg = PathBuf::from("/repo/.ryl.toml");
    let env = common::fake_env::FakeEnv::new().with_file(
        cfg.clone(),
        "[fix]\nfixable = ['new-line-at-end-of-file']\nunfixable = ['brackets', 'commas', 'comments-indentation']\n",
    );

    let ctx = discover_config_with(
        &[],
        &Overrides {
            config_file: Some(cfg),
            config_data: None,
        },
        &env,
    )
    .expect("toml config should parse typed fix variants");

    assert_eq!(
        ctx.config.fix().fixable(),
        [FixRuleSelector::Rule(FixRule::NewLineAtEndOfFile)]
    );
    assert_eq!(
        ctx.config.fix().unfixable(),
        [
            FixRule::Brackets,
            FixRule::Commas,
            FixRule::CommentsIndentation
        ]
    );
}

#[test]
fn toml_config_with_datetime_extra_parses_fix_policy() {
    let cfg = PathBuf::from("/repo/.ryl.toml");
    let env = common::fake_env::FakeEnv::new().with_file(
        cfg.clone(),
        "stamp = 1979-05-27T07:32:00Z\n[fix]\nfixable = ['ALL']\nunfixable = ['brackets', 'commas', 'comments-indentation']\n",
    );

    let ctx = discover_config_with(
        &[],
        &Overrides {
            config_file: Some(cfg),
            config_data: None,
        },
        &env,
    )
    .expect("TOML config with datetime extra should still parse fix policy");

    assert_eq!(ctx.config.fix().fixable(), [FixRuleSelector::All]);
    assert_eq!(
        ctx.config.fix().unfixable(),
        [
            FixRule::Brackets,
            FixRule::Commas,
            FixRule::CommentsIndentation
        ]
    );
}

#[test]
fn default_fix_policy_allows_all_rules() {
    let root = tempfile::tempdir().unwrap();
    let file = root.path().join("input.yaml");
    std::fs::write(&file, "key: value\n").unwrap();

    let ctx = discover_config(&[file], &Overrides::default()).expect("default config");
    assert!(ctx.config.fix().allows_rule("braces"));
    assert!(ctx.config.fix().allows_rule("brackets"));
    assert!(ctx.config.fix().allows_rule("commas"));
    assert!(ctx.config.fix().allows_rule("comments"));
    assert!(ctx.config.fix().allows_rule("comments-indentation"));
    assert!(ctx.config.fix().allows_rule("new-lines"));
    assert!(!ctx.config.fix().allows_rule("indentation"));
    assert_eq!(ctx.config.fix().fixable(), [FixRuleSelector::All]);
}

#[test]
fn toml_config_rejects_non_mapping_fix_table() {
    let cfg = PathBuf::from("/repo/.ryl.toml");
    let env =
        common::fake_env::FakeEnv::new().with_file(cfg.clone(), "fix = 'comments'\n");

    let err = discover_config_with(
        &[],
        &Overrides {
            config_file: Some(cfg),
            config_data: None,
        },
        &env,
    )
    .expect_err("fix should require a table");

    assert!(err.contains("failed to parse config data"));
}

#[test]
fn toml_config_rejects_unknown_fix_option() {
    let cfg = PathBuf::from("/repo/.ryl.toml");
    let env = common::fake_env::FakeEnv::new()
        .with_file(cfg.clone(), "[fix]\nunknown = ['comments']\n");

    let err = discover_config_with(
        &[],
        &Overrides {
            config_file: Some(cfg),
            config_data: None,
        },
        &env,
    )
    .expect_err("unknown fix option should fail");

    assert!(err.contains("failed to parse config data"));
}

#[test]
fn toml_config_rejects_non_list_fixable() {
    let cfg = PathBuf::from("/repo/.ryl.toml");
    let env = common::fake_env::FakeEnv::new()
        .with_file(cfg.clone(), "[fix]\nfixable = 'comments'\n");

    let err = discover_config_with(
        &[],
        &Overrides {
            config_file: Some(cfg),
            config_data: None,
        },
        &env,
    )
    .expect_err("fixable should require a list");

    assert!(err.contains("failed to parse config data"));
}

#[test]
fn toml_config_rejects_non_string_fix_rule_entries() {
    let cfg = PathBuf::from("/repo/.ryl.toml");
    let env = common::fake_env::FakeEnv::new()
        .with_file(cfg.clone(), "[fix]\nunfixable = [1]\n");

    let err = discover_config_with(
        &[],
        &Overrides {
            config_file: Some(cfg),
            config_data: None,
        },
        &env,
    )
    .expect_err("fix rule entries should require strings");

    assert!(err.contains("failed to parse config data"));
}

#[test]
fn toml_config_rejects_unknown_fixable_rule_name() {
    let cfg = PathBuf::from("/repo/.ryl.toml");
    let env = common::fake_env::FakeEnv::new()
        .with_file(cfg.clone(), "[fix]\nfixable = ['indentation']\n");

    let err = discover_config_with(
        &[],
        &Overrides {
            config_file: Some(cfg),
            config_data: None,
        },
        &env,
    )
    .expect_err("unknown fixable rule should fail");

    assert!(err.contains("failed to parse config data"));
}

#[test]
fn toml_config_rejects_all_in_unfixable() {
    let cfg = PathBuf::from("/repo/.ryl.toml");
    let env = common::fake_env::FakeEnv::new()
        .with_file(cfg.clone(), "[fix]\nunfixable = ['ALL']\n");

    let err = discover_config_with(
        &[],
        &Overrides {
            config_file: Some(cfg),
            config_data: None,
        },
        &env,
    )
    .expect_err("ALL should not be valid in unfixable");

    assert!(err.contains("failed to parse config data"));
}

#[test]
fn toml_config_rejects_non_list_unfixable() {
    let cfg = PathBuf::from("/repo/.ryl.toml");
    let env = common::fake_env::FakeEnv::new()
        .with_file(cfg.clone(), "[fix]\nunfixable = 'comments'\n");

    let err = discover_config_with(
        &[],
        &Overrides {
            config_file: Some(cfg),
            config_data: None,
        },
        &env,
    )
    .expect_err("unfixable should require a list");

    assert!(err.contains("failed to parse config data"));
}

#[test]
fn toml_config_rejects_non_string_fixable_entries() {
    let cfg = PathBuf::from("/repo/.ryl.toml");
    let env = common::fake_env::FakeEnv::new()
        .with_file(cfg.clone(), "[fix]\nfixable = [1]\n");

    let err = discover_config_with(
        &[],
        &Overrides {
            config_file: Some(cfg),
            config_data: None,
        },
        &env,
    )
    .expect_err("fixable entries should require strings");

    assert!(err.contains("failed to parse config data"));
}

#[test]
fn yaml_extends_default_keeps_default_fix_policy() {
    let cfg = ryl::config::YamlLintConfig::from_yaml_str("extends: default\n")
        .expect("extends should parse");

    assert_eq!(cfg.fix().fixable(), [FixRuleSelector::All]);
    assert!(cfg.fix().unfixable().is_empty());
}

#[test]
fn toml_config_allows_quoted_strings_in_fixable() {
    let cfg = PathBuf::from("/repo/.ryl.toml");
    let env = common::fake_env::FakeEnv::new()
        .with_file(cfg.clone(), "[fix]\nfixable = ['quoted-strings']\n");

    let ctx = discover_config_with(
        &[],
        &Overrides {
            config_file: Some(cfg),
            config_data: None,
        },
        &env,
    )
    .expect("toml config should allow quoted-strings in fixable");

    assert_eq!(
        ctx.config.fix().fixable(),
        [FixRuleSelector::Rule(FixRule::QuotedStrings)]
    );
    assert!(ctx.config.fix().allows_rule("quoted-strings"));
}

#[test]
fn to_toml_string_round_trips_quoted_strings_fix_config() {
    let cfg = PathBuf::from("/repo/.ryl.toml");
    let env = common::fake_env::FakeEnv::new().with_file(
        cfg.clone(),
        "[fix]\nfixable = ['quoted-strings']\nunfixable = ['quoted-strings', 'comments']\n\n[rules.quoted-strings]\nquote-type = 'single'\nrequired = 'only-when-needed'\n",
    );

    let ctx = discover_config_with(
        &[],
        &Overrides {
            config_file: Some(cfg),
            config_data: None,
        },
        &env,
    )
    .expect("toml config should parse");

    let toml_output = ctx.config.to_toml_string();
    assert!(toml_output.contains("quoted-strings"));
}

#[test]
fn to_toml_string_round_trips_trailing_spaces_fix_config() {
    let cfg = PathBuf::from("/repo/.ryl.toml");
    let env = common::fake_env::FakeEnv::new().with_file(
        cfg.clone(),
        "[fix]\nfixable = ['trailing-spaces']\nunfixable = ['trailing-spaces']\n",
    );

    let ctx = discover_config_with(
        &[],
        &Overrides {
            config_file: Some(cfg),
            config_data: None,
        },
        &env,
    )
    .expect("toml config should parse trailing-spaces fix selectors");

    assert_eq!(
        ctx.config.fix().fixable(),
        [FixRuleSelector::Rule(FixRule::TrailingSpaces)]
    );
    assert_eq!(ctx.config.fix().unfixable(), [FixRule::TrailingSpaces]);
    let toml_output = ctx.config.to_toml_string();
    assert!(toml_output.contains("trailing-spaces"));
}

#[test]
fn to_toml_string_round_trips_document_and_empty_lines_fix_config() {
    let cfg = PathBuf::from("/repo/.ryl.toml");
    let env = common::fake_env::FakeEnv::new().with_file(
        cfg.clone(),
        "[fix]\nfixable = ['document-start', 'document-end', 'empty-lines']\nunfixable = ['document-start', 'document-end', 'empty-lines']\n",
    );

    let ctx = discover_config_with(
        &[],
        &Overrides {
            config_file: Some(cfg),
            config_data: None,
        },
        &env,
    )
    .expect("toml config should parse new partial-safe-fix selectors");

    assert_eq!(
        ctx.config.fix().fixable(),
        [
            FixRuleSelector::Rule(FixRule::DocumentStart),
            FixRuleSelector::Rule(FixRule::DocumentEnd),
            FixRuleSelector::Rule(FixRule::EmptyLines),
        ]
    );
    assert_eq!(
        ctx.config.fix().unfixable(),
        [
            FixRule::DocumentStart,
            FixRule::DocumentEnd,
            FixRule::EmptyLines,
        ]
    );
    let toml_output = ctx.config.to_toml_string();
    assert!(toml_output.contains("document-start"));
    assert!(toml_output.contains("document-end"));
    assert!(toml_output.contains("empty-lines"));
}
