"""Edge-case tests for plugin interactions and special scenarios."""

import textwrap


def test_implied_by_all_ordering(pytester):
    """implied_by_all waits for all providers before checking."""
    pytester.makepyfile(textwrap.dedent("""\
        import pytest

        @pytest.mark.implies("tok_a")
        def test_provider_a():
            assert True

        @pytest.mark.implies("tok_b")
        def test_provider_b():
            assert True

        @pytest.mark.implied_by_all("tok_a", "tok_b")
        def test_consumer():
            raise RuntimeError("should not run")
    """))
    result = pytester.runpytest("-v")
    result.assert_outcomes(passed=3)
    assert "IMPLIED" in result.stdout.str()


def test_transitive_propagation(pytester):
    """Tokens propagate transitively through implied tests."""
    pytester.makepyfile(textwrap.dedent("""\
        import pytest

        order = []

        @pytest.mark.implies("tok_a")
        def test_root():
            order.append("root")

        @pytest.mark.implied_by("tok_a")
        @pytest.mark.implies("tok_b")
        def test_middle():
            order.append("middle")
            raise RuntimeError("should not run")

        @pytest.mark.implied_by("tok_b")
        def test_leaf():
            order.append("leaf")
            raise RuntimeError("should not run")

        def test_check():
            assert order == ["root"]
    """))
    result = pytester.runpytest("-v")
    result.assert_outcomes(passed=4)
    lines = result.stdout.str()
    assert lines.count("IMPLIED") >= 2


def test_monotonic_with_multiple_parametrize_axes(pytester):
    """Monotonic works correctly with additional parametrize axes."""
    pytester.makepyfile(textwrap.dedent("""\
        import pytest

        @pytest.mark.monotonic("n")
        @pytest.mark.parametrize("n", [1, 5, 10])
        @pytest.mark.parametrize("mode", ["fast", "slow"])
        def test_multi(n, mode):
            assert True
    """))
    result = pytester.runpytest("-v")
    result.assert_outcomes(passed=6)
    lines = result.stdout.str()
    # Each mode group: n=10 passes → n=5 and n=1 implied (4 implied total)
    assert lines.count("IMPLIED") >= 4


def test_class_based_monotonic(pytester):
    """Monotonic works on class-based tests."""
    pytester.makepyfile(textwrap.dedent("""\
        import pytest

        class TestStress:
            @pytest.mark.monotonic("n")
            @pytest.mark.parametrize("n", [1, 5, 10])
            def test_load(self, n):
                assert True
    """))
    result = pytester.runpytest("-v")
    result.assert_outcomes(passed=3)
    assert "IMPLIED" in result.stdout.str()


def test_xfail_strict_false_records_tokens(pytester):
    """An xfail(strict=False) test that passes (xpass) records tokens."""
    pytester.makepyfile(textwrap.dedent("""\
        import pytest

        @pytest.mark.xfail(reason="expected to fail", strict=False)
        @pytest.mark.implies("tok_a")
        def test_surprise():
            assert True  # xpass

        @pytest.mark.implied_by("tok_a")
        def test_consumer():
            raise RuntimeError("should not run")
    """))
    result = pytester.runpytest("-v")
    # xpass with strict=False is still 'passed' — token is recorded
    assert "IMPLIED" in result.stdout.str()


def test_xfail_strict_true_does_not_record_tokens(pytester):
    """An xfail(strict=True) test that passes (xpass) does NOT record tokens."""
    pytester.makepyfile(textwrap.dedent("""\
        import pytest

        @pytest.mark.xfail(reason="expected to fail", strict=True)
        @pytest.mark.implies("tok_a")
        def test_surprise():
            assert True  # xpass → treated as failure with strict=True

        @pytest.mark.implied_by("tok_a")
        def test_consumer():
            assert True  # must run since tok_a not recorded
    """))
    result = pytester.runpytest("-v")
    result.assert_outcomes(passed=1, failed=1)
    assert "IMPLIED" not in result.stdout.str()


def test_skip_implier_does_not_record_tokens(pytester):
    """A skipped implier does not record tokens."""
    pytester.makepyfile(textwrap.dedent("""\
        import pytest

        @pytest.mark.skip(reason="not today")
        @pytest.mark.implies("tok_a")
        def test_skipper():
            pass

        @pytest.mark.implied_by("tok_a")
        def test_consumer():
            assert True
    """))
    result = pytester.runpytest("-v")
    result.assert_outcomes(passed=1, skipped=1)
    assert "IMPLIED" not in result.stdout.str()


def test_maxfail_with_implied(pytester):
    """-x / --maxfail interacts correctly with implied tests."""
    pytester.makepyfile(textwrap.dedent("""\
        import pytest

        @pytest.mark.implies("tok_a")
        def test_provider():
            assert True

        @pytest.mark.implied_by("tok_a")
        def test_implied():
            raise RuntimeError("should not run")

        def test_after():
            assert True
    """))
    result = pytester.runpytest("-v", "--maxfail=1")
    result.assert_outcomes(passed=3)


def test_no_imply_preserves_original_order(pytester):
    """--no-imply does not reorder tests."""
    pytester.makepyfile(textwrap.dedent("""\
        import pytest

        order = []

        @pytest.mark.implied_by("tok_a")
        def test_consumer():
            order.append("consumer")

        @pytest.mark.implies("tok_a")
        def test_provider():
            order.append("provider")

        def test_check():
            # With --no-imply, original file order is preserved
            assert order == ["consumer", "provider"]
    """))
    result = pytester.runpytest("-v", "--no-imply")
    result.assert_outcomes(passed=3)


def test_terminal_summary_count(pytester):
    """Terminal summary shows the implied count."""
    pytester.makepyfile(textwrap.dedent("""\
        import pytest

        @pytest.mark.implies("tok")
        def test_a():
            assert True

        @pytest.mark.implied_by("tok")
        def test_b():
            raise RuntimeError("should not run")
    """))
    result = pytester.runpytest("-v")
    result.assert_outcomes(passed=2)
    assert "1 test implied" in result.stdout.str()


def test_strict_markers_accepted(pytester):
    """All markers are accepted under --strict-markers."""
    pytester.makepyfile(textwrap.dedent("""\
        import pytest

        @pytest.mark.implies("tok")
        def test_a():
            pass

        @pytest.mark.implied_by("tok")
        def test_b():
            pass

        @pytest.mark.implied_by_any("tok", "tok2")
        def test_c():
            pass

        @pytest.mark.implied_by_all("tok", "tok2")
        def test_d():
            pass

        @pytest.mark.monotonic("n")
        @pytest.mark.parametrize("n", [1, 2])
        def test_e(n):
            pass
    """))
    result = pytester.runpytest("-v", "--strict-markers")
    assert result.ret == 0


def test_stacked_implies_markers(pytester):
    """Multiple stacked @implies markers all record their tokens."""
    pytester.makepyfile(textwrap.dedent("""\
        import pytest

        @pytest.mark.implies("tok_a")
        @pytest.mark.implies("tok_b")
        def test_provider():
            assert True

        @pytest.mark.implied_by("tok_a")
        def test_consumer_a():
            raise RuntimeError("should not run")

        @pytest.mark.implied_by("tok_b")
        def test_consumer_b():
            raise RuntimeError("should not run")
    """))
    result = pytester.runpytest("-v")
    result.assert_outcomes(passed=3)
    assert result.stdout.str().count("IMPLIED") >= 2


def test_stacked_implied_by_markers(pytester):
    """Multiple stacked @implied_by markers: test is implied if any matches."""
    pytester.makepyfile(textwrap.dedent("""\
        import pytest

        @pytest.mark.implies("tok_a")
        def test_a():
            assert True

        @pytest.mark.implies("tok_b")
        def test_b():
            assert False  # fails — tok_b not recorded

        @pytest.mark.implied_by("tok_a")
        @pytest.mark.implied_by("tok_b")
        def test_consumer():
            raise RuntimeError("should not run")
    """))
    result = pytester.runpytest("-v")
    # tok_a is recorded, so the first @implied_by("tok_a") triggers
    result.assert_outcomes(passed=2, failed=1)
    assert "IMPLIED" in result.stdout.str()


def test_monotonic_with_implies_propagation(pytester):
    """A monotonic test that also has @implies propagates tokens when implied."""
    pytester.makepyfile(textwrap.dedent("""\
        import pytest

        @pytest.mark.monotonic("n")
        @pytest.mark.implies("stress_ok")
        @pytest.mark.parametrize("n", [1, 5, 10])
        def test_stress(n):
            assert True

        @pytest.mark.implied_by("stress_ok")
        def test_downstream():
            raise RuntimeError("should not run")
    """))
    result = pytester.runpytest("-v")
    # n=10 runs and passes (records stress_ok + monotonic)
    # n=5, n=1 implied by monotonic (propagate stress_ok)
    # test_downstream implied by stress_ok
    lines = result.stdout.str()
    assert lines.count("IMPLIED") >= 3


def test_orphan_token_warning(pytester):
    """A token referenced by implied_by but never provided triggers a warning."""
    pytester.makepyfile(textwrap.dedent("""\
        import pytest

        @pytest.mark.implied_by("nonexistent_token")
        def test_consumer():
            assert True
    """))
    result = pytester.runpytest("-v")
    result.assert_outcomes(passed=1)
    result.stdout.fnmatch_lines(["*no test provides*nonexistent_token*"])


def test_cycle_emits_warning(pytester):
    """A dependency cycle emits a warning but doesn't crash."""
    pytester.makepyfile(textwrap.dedent("""\
        import pytest

        @pytest.mark.implies("tok_a")
        @pytest.mark.implied_by("tok_b")
        def test_x():
            assert True

        @pytest.mark.implies("tok_b")
        @pytest.mark.implied_by("tok_a")
        def test_y():
            assert True
    """))
    result = pytester.runpytest("-v")
    result.assert_outcomes(passed=2)
    result.stdout.fnmatch_lines(["*dependency cycle detected*"])


def test_self_referential_token_warns(pytester):
    """A test that both provides and consumes the same token triggers a warning."""
    pytester.makepyfile(textwrap.dedent("""\
        import pytest

        @pytest.mark.implies("tok_a")
        @pytest.mark.implied_by("tok_a")
        def test_self():
            assert True
    """))
    result = pytester.runpytest("-v")
    result.stdout.fnmatch_lines(["*both provides and consumes*tok_a*"])


def test_cycle_disables_implication_for_involved_tests(pytester):
    """Tests in a dependency cycle run normally — implication is disabled for them."""
    pytester.makepyfile(textwrap.dedent("""\
        import pytest

        @pytest.mark.implies("tok_a")
        @pytest.mark.implied_by("tok_b")
        def test_x():
            assert True

        @pytest.mark.implies("tok_b")
        @pytest.mark.implied_by("tok_a")
        def test_y():
            assert True
    """))
    result = pytester.runpytest("-v")
    result.assert_outcomes(passed=2)
    # Both tests must PASS (actually run), not be IMPLIED
    assert "IMPLIED" not in result.stdout.str()
    result.stdout.fnmatch_lines(["*dependency cycle detected*"])


def test_session_scoped_fixture_warning(pytester):
    """An implied test using a session-scoped fixture triggers a warning."""
    pytester.makepyfile(
        conftest=textwrap.dedent("""\
            import pytest

            @pytest.fixture(scope="session")
            def db_conn():
                return "connection"
        """),
        test_it=textwrap.dedent("""\
            import pytest

            @pytest.mark.implies("tok")
            def test_provider():
                assert True

            @pytest.mark.implied_by("tok")
            def test_consumer(db_conn):
                assert db_conn == "connection"
        """),
    )
    result = pytester.runpytest("-v")
    result.stdout.fnmatch_lines(["*session-scoped fixture*db_conn*"])


def test_warning_is_pytest_imply_warning_class(pytester):
    """PytestImplyWarning can be filtered with -W."""
    pytester.makepyfile(textwrap.dedent("""\
        import pytest

        @pytest.mark.implied_by("nonexistent_token")
        def test_consumer():
            assert True
    """))
    # Suppress all PytestImplyWarning — orphan warning should not appear
    result = pytester.runpytest(
        "-v", "-W", "ignore::pytest_imply.PytestImplyWarning",
    )
    result.assert_outcomes(passed=1)


def test_monotonic_asc_reason_says_lower(pytester):
    """direction='asc' reason string says 'lower' not 'higher'."""
    pytester.makepyfile(textwrap.dedent("""\
        import pytest

        @pytest.mark.monotonic("threshold", direction="asc")
        @pytest.mark.parametrize("threshold", [1, 5, 10])
        def test_precision(threshold):
            assert True
    """))
    result = pytester.runpytest("-v")
    result.assert_outcomes(passed=3)
    lines = result.stdout.str()
    assert "lower threshold passed" in lines
    assert "higher" not in lines


def test_ini_imply_enabled_false(pytester):
    """imply_enabled = false in ini disables implication."""
    pytester.makeini("""\
[pytest]
imply_enabled = false
""")
    pytester.makepyfile(textwrap.dedent("""\
        import pytest

        ran = []

        @pytest.mark.implies("tok")
        def test_provider():
            ran.append("provider")

        @pytest.mark.implied_by("tok")
        def test_consumer():
            ran.append("consumer")

        def test_check():
            assert ran == ["provider", "consumer"]
    """))
    result = pytester.runpytest("-v")
    result.assert_outcomes(passed=3)
    assert "IMPLIED" not in result.stdout.str()
    assert "no test provides" not in result.stdout.str()
