//! Test cases adapted from the PyArg library.
//!
//! Original source: <https://github.com/DaphneO/PyArg>
//! Copyright 2023 Daphne Odekerken (MIT License)
//!
//! Permission is hereby granted, free of charge, to any person obtaining a copy
//! of this software and associated documentation files (the "Software"), to deal
//! in the Software without restriction, including without limitation the rights
//! to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
//! copies of the Software, and to permit persons to whom the Software is
//! furnished to do so, subject to the following conditions:
//!
//! The above copyright notice and this permission notice shall be included in
//! all copies or substantial portions of the Software.
//!
//! THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
//! IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
//! FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
//! AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
//! LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
//! OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN
//! THE SOFTWARE.
//!
//! These tests validate that the pickaxe-rs Rust implementation produces
//! equivalent results to PyArg on shared scenarios.

use std::collections::BTreeSet;

use pickaxe_rs::formula;
use pickaxe_rs::prelude::*;

mod common;

use common::{build_af, EqualOrdering};

// --- AF Complete Semantics ---
// Adapted from: test_af_complete_semantics.py::test_af_complete_semantics
//
// AF: args {b, c, d}, defeats {b→c, c→d, d→c}
// Expected: 1 complete extension = {b, d}

#[test]
fn pyarg_af_complete_semantics() {
    // b=0, c=1, d=2
    let af = build_af(3, &[(0, 1), (1, 2), (2, 1)]);

    let exts = NaiveEnumerationSolver.extensions(&af, Semantics::Complete);

    assert_eq!(exts.len(), 1, "expected exactly 1 complete extension");

    let expected: BTreeSet<ArgumentId> = [ArgumentId(0), ArgumentId(2)].into_iter().collect();
    assert_eq!(exts[0], expected, "complete extension should be {{b, d}}");
}

// --- AF From Scratch ---
// Adapted from: test_af_from_scratch.py::test_three_argument_example
//
// AF: args {a, b, c}, defeats {a→b, b→a, b→c}
// Expected: attackers_of(a) = {b}, attackers_of(b) = {a}, attackers_of(c) = {b}

#[test]
fn pyarg_af_from_scratch() {
    // a=0, b=1, c=2
    let af = build_af(3, &[(0, 1), (1, 0), (1, 2)]);

    let attackers_a: BTreeSet<ArgumentId> =
        af.attackers_of(ArgumentId(0)).iter().copied().collect();
    let attackers_b: BTreeSet<ArgumentId> =
        af.attackers_of(ArgumentId(1)).iter().copied().collect();
    let attackers_c: BTreeSet<ArgumentId> =
        af.attackers_of(ArgumentId(2)).iter().copied().collect();

    assert_eq!(
        attackers_a,
        [ArgumentId(1)].into_iter().collect::<BTreeSet<_>>(),
        "attackers of a should be {{b}}"
    );
    assert_eq!(
        attackers_b,
        [ArgumentId(0)].into_iter().collect::<BTreeSet<_>>(),
        "attackers of b should be {{a}}"
    );
    assert_eq!(
        attackers_c,
        [ArgumentId(1)].into_iter().collect::<BTreeSet<_>>(),
        "attackers of c should be {{b}}"
    );
}

// --- Modgil & Prakken AIJ 2013 running example helpers ---
//
// Adapted from: py_arg_tests/modgil_prakken_aij_tests.py::get_argumentation_theory
//
// Base system:
//   Language: a, p, q, r, s, t (with classical negation and NAF ~s)
//   Kn = {} (no axioms)
//   Kp = {a, r, -r, ~s}      (ordinary premises)
//   Rs = {s1: t, q → -p}
//   Rd = {d1: ~s ⇒ t,  d2: r ⇒ q,  d3: a ⇒ p}
//
// Extended system (include_d, include_e):
//   + Kp: ~d
//   + Rd: d4: ~d ⇒ s
//   + Rs: s2: r → -d3

fn modgil_prakken_aij_base() -> AspicSystem {
    let (system, _warnings) = AspicSystemBuilder::new()
        .with_classical_negation()
        .ordinary_premise(formula!(a))
        .ordinary_premise(formula!(r))
        .ordinary_premise(formula!(!r))
        .ordinary_premise(formula!(naf_s))
        .contrary(formula!(naf_s), formula!(s))
        .strict_rule("s1", vec![formula!(t), formula!(q)], formula!(!p))
        .named_defeasible_rule("d1", formula!(d1), vec![formula!(naf_s)], formula!(t))
        .named_defeasible_rule("d2", formula!(d2), vec![formula!(r)], formula!(q))
        .named_defeasible_rule("d3", formula!(d3), vec![formula!(a)], formula!(p))
        .build();
    system
}

fn modgil_prakken_aij_extended() -> AspicSystem {
    let (system, _warnings) = AspicSystemBuilder::new()
        .with_classical_negation()
        .ordinary_premise(formula!(a))
        .ordinary_premise(formula!(r))
        .ordinary_premise(formula!(!r))
        .ordinary_premise(formula!(naf_s))
        .ordinary_premise(formula!(naf_d))
        .contrary(formula!(naf_s), formula!(s))
        .contrary(formula!(naf_d), formula!(d))
        .strict_rule("s1", vec![formula!(t), formula!(q)], formula!(!p))
        .strict_rule("s2", vec![formula!(r)], formula!(!d3))
        .named_defeasible_rule("d1", formula!(d1), vec![formula!(naf_s)], formula!(t))
        .named_defeasible_rule("d2", formula!(d2), vec![formula!(r)], formula!(q))
        .named_defeasible_rule("d3", formula!(d3), vec![formula!(a)], formula!(p))
        .named_defeasible_rule("d4", formula!(d4), vec![formula!(naf_d)], formula!(s))
        .build();
    system
}

// --- Argument Count ---
// Adapted from: test_create_argumentation_framework_from_argumentation_theory.py
//               ::test_get_nr_of_arguments
//
// Base system should produce exactly 8 arguments:
//   prem(a), prem(r), prem(-r), prem(~s),
//   [a; d3]→p, [~s; d1]→t, [r; d2]→q, [t,q; s1]→-p

#[test]
fn pyarg_modgil_prakken_aij_argument_count() {
    let pipeline = AspicPipeline::classical(modgil_prakken_aij_base(), Box::new(EqualOrdering));
    let result = pipeline.evaluate(Semantics::Grounded).unwrap();

    assert_eq!(
        result.store.len(),
        8,
        "M&P AIJ base example should produce exactly 8 arguments"
    );
}

// --- Defeat Count ---
// Adapted from: test_create_argumentation_framework_from_argumentation_theory.py
//               ::test_get_nr_of_defeats
//
// Base system with no preferences (all attacks succeed): 5 defeats.
//   1. [t,q;s1]→-p rebuts [a;d3]→p
//   2. prem(-r) undermines prem(r)
//   3. prem(-r) undermines [r;d2]→q   (via prem r)
//   4. prem(-r) undermines [t,q;s1]→-p (via prem r through d2)
//   5. prem(r) undermines prem(-r)

#[test]
fn pyarg_modgil_prakken_aij_defeat_count() {
    let pipeline = AspicPipeline::classical(modgil_prakken_aij_base(), Box::new(EqualOrdering));
    let result = pipeline.evaluate(Semantics::Grounded).unwrap();

    let defeats = compute_defeats(&result.attacks, &result.store, &EqualOrdering);
    let af = AbstractFramework::from_defeats(&result.store, &defeats);

    assert_eq!(
        af.defeats.len(),
        5,
        "M&P AIJ base example should produce exactly 5 defeats"
    );
}

// --- Attack Count (Extended System) ---
// Adapted from: test_create_argumentation_framework_from_argumentation_theory.py
//               ::test_get_nr_of_attacks
//
// Extended system (include_d=True, include_e=True) should yield 10 attacks.

#[test]
fn pyarg_modgil_prakken_aij_attack_count_extended() {
    let pipeline = AspicPipeline::classical(modgil_prakken_aij_extended(), Box::new(EqualOrdering));
    let result = pipeline.evaluate(Semantics::Grounded).unwrap();

    assert_eq!(
        result.attacks.len(),
        10,
        "M&P AIJ extended example should produce exactly 10 attacks"
    );
}

// --- AF from ASPARTIX format ---
// Adapted from: test_af_from_apx.py::test_af_from_apx
//
// Parses: arg(A). arg(B). arg(C). att(A,B). att(B,C). att(B,A).
// Expected: 3 arguments, 3 defeats.

#[test]
fn pyarg_af_from_aspartix() {
    let apx = "arg(A).\n\
               arg(B).\n\
               arg(C).\n\
               att(A, B).\n\
               att(B, C).\n\
               att(B, A).\n";

    let (af, name_map) = AbstractFramework::from_aspartix(apx).unwrap();

    assert_eq!(af.arguments.len(), 3, "should have 3 arguments");
    assert_eq!(af.defeats.len(), 3, "should have 3 defeats");

    assert!(name_map.contains_key("A"));
    assert!(name_map.contains_key("B"));
    assert!(name_map.contains_key("C"));

    let a = name_map["A"];
    let b = name_map["B"];

    assert!(af.defeats(a, b), "A should defeat B");
    assert!(af.defeats(b, a), "B should defeat A");
}
