"""Integration tests for the pytest-gremlins plugin.

These tests verify the end-to-end plugin behavior using pytester.
"""

from __future__ import annotations

from pathlib import Path
import re

import pytest


@pytest.mark.medium
class DescribePluginBasicFunctionality:
    """Test basic plugin functionality."""

    def it_gremlins_flag_enables_mutation_testing(self, pytester_with_markers: pytest.Pytester):
        """Verify that --gremlins flag enables mutation testing."""
        pytester_with_markers.makepyfile(
            target_module="""
def is_adult(age):
    return age >= 18
""",
        )
        pytester_with_markers.makepyfile(
            test_target="""
from target_module import is_adult

def test_is_adult_true_for_21():
    assert is_adult(21) is True

def test_is_adult_false_for_10():
    assert is_adult(10) is False
""",
        )

        result = pytester_with_markers.runpytest('--gremlins', '-v')
        result.assert_outcomes(passed=2)
        assert 'pytest-gremlins mutation report' in result.stdout.str()

    def it_gremlins_flag_generates_gremlins(self, pytester_with_markers: pytest.Pytester):
        """Verify that gremlins are generated from source code."""
        pytester_with_markers.makepyfile(
            target_module="""
def is_adult(age):
    return age >= 18
""",
        )
        pytester_with_markers.makepyfile(
            test_target="""
from target_module import is_adult

def test_is_adult():
    assert is_adult(21) is True
""",
        )

        result = pytester_with_markers.runpytest('--gremlins', '--gremlin-targets=target_module.py', '-v')
        result.assert_outcomes(passed=1)
        # Should have generated gremlins
        output = result.stdout.str()
        assert 'Zapped:' in output or 'Survived:' in output

    def it_mutation_score_displayed(self, pytester_with_markers: pytest.Pytester):
        """Verify that mutation score is displayed at the end."""
        pytester_with_markers.makepyfile(
            target_module="""
def add(x, y):
    return x + y
""",
        )
        pytester_with_markers.makepyfile(
            test_target="""
from target_module import add

def test_add():
    assert add(2, 3) == 5
""",
        )

        result = pytester_with_markers.runpytest('--gremlins', '-v')
        result.assert_outcomes(passed=1)
        output = result.stdout.str()
        assert '%' in output  # Mutation score percentage


@pytest.mark.medium
class DescribePluginWithoutGremlinsFlag:
    """Test plugin behavior when --gremlins is not used."""

    def it_skips_mutation_testing_without_gremlins_flag(self, pytester_with_markers: pytest.Pytester):
        """Verify that tests run normally without --gremlins flag."""
        pytester_with_markers.makepyfile(
            target_module="""
def is_adult(age):
    return age >= 18
""",
        )
        pytester_with_markers.makepyfile(
            test_target="""
from target_module import is_adult

def test_is_adult():
    assert is_adult(21) is True
""",
        )

        result = pytester_with_markers.runpytest('-v')
        result.assert_outcomes(passed=1)
        # Should not have mutation report
        assert 'pytest-gremlins mutation report' not in result.stdout.str()


@pytest.mark.medium
class DescribePluginOperatorSelection:
    """Test operator selection via command line."""

    def it_specific_operators_via_command_line(self, pytester_with_markers: pytest.Pytester):
        """Verify that specific operators can be selected."""
        pytester_with_markers.makepyfile(
            target_module="""
def is_adult(age):
    return age >= 18
""",
        )
        pytester_with_markers.makepyfile(
            test_target="""
from target_module import is_adult

def test_is_adult():
    assert is_adult(21) is True
""",
        )

        result = pytester_with_markers.runpytest('--gremlins', '--gremlin-operators=comparison', '-v')
        result.assert_outcomes(passed=1)
        output = result.stdout.str()
        assert 'pytest-gremlins mutation report' in output


@pytest.mark.medium
class DescribePluginReportFormats:
    """Test different report format options."""

    def it_generates_console_report_by_default(self, pytester_with_markers: pytest.Pytester):
        """Verify console report is generated by default."""
        pytester_with_markers.makepyfile(
            target_module="""
def is_adult(age):
    return age >= 18
""",
        )
        pytester_with_markers.makepyfile(
            test_target="""
from target_module import is_adult

def test_is_adult():
    assert is_adult(21) is True
""",
        )

        result = pytester_with_markers.runpytest('--gremlins', '--gremlin-targets=target_module.py', '-v')
        result.assert_outcomes(passed=1)
        output = result.stdout.str()
        assert 'pytest-gremlins mutation report' in output
        assert 'Zapped:' in output or 'Survived:' in output


@pytest.mark.medium
class DescribeMutationSwitching:
    """Test that mutations are actually activated via import hooks."""

    def it_mutation_actually_kills_when_test_covers_it(self, pytester_with_markers: pytest.Pytester):
        """Verify that a mutation is actually killed when a test would catch it.

        This test creates a function with a >= comparison and tests that cover
        both the boundary (age=18) and non-boundary cases. If mutation switching
        is working, the >= to > mutation should be killed because test_boundary
        will fail when age=18 returns False instead of True.
        """
        pytester_with_markers.makepyfile(
            target_module="""
def is_adult(age):
    return age >= 18
""",
        )
        pytester_with_markers.makepyfile(
            test_target="""
from target_module import is_adult

def test_boundary():
    # This test should kill the >= to > mutation
    # If mutation is active, is_adult(18) returns False (18 > 18 is False)
    assert is_adult(18) is True

def test_above_boundary():
    assert is_adult(21) is True

def test_below_boundary():
    assert is_adult(10) is False
""",
        )

        result = pytester_with_markers.runpytest('--gremlins', '--gremlin-targets=target_module.py', '-v')
        result.assert_outcomes(passed=3)
        output = result.stdout.str()

        # With proper mutation switching, at least one mutation should be zapped
        # The >= to > mutation should definitely be killed by test_boundary
        assert 'Zapped:' in output
        # Extract the zapped count
        match = re.search(r'Zapped: (\d+)', output)
        assert match is not None, 'Could not find Zapped count in output'
        zapped_count = int(match.group(1))
        assert zapped_count >= 1, f'Expected at least 1 zapped gremlin, got {zapped_count}'


@pytest.mark.medium
class DescribeHtmlReportIntegration:
    """Test HTML report generation via CLI."""

    def it_writes_html_report_when_specified(self, pytester_with_markers: pytest.Pytester):
        """Verify that HTML report is written when --gremlin-report=html is specified."""
        pytester_with_markers.makepyfile(
            target_module="""
def is_adult(age):
    return age >= 18
""",
        )
        pytester_with_markers.makepyfile(
            test_target="""
from target_module import is_adult

def test_is_adult():
    assert is_adult(21) is True
""",
        )

        result = pytester_with_markers.runpytest(
            '--gremlins',
            '--gremlin-targets=target_module.py',
            '--gremlin-report=html',
            '-v',
        )
        result.assert_outcomes(passed=1)

        # The HTML report should be written to the default location (coverage/gremlins/index.html)
        report_path = pytester_with_markers.path / 'coverage' / 'gremlins' / 'index.html'
        assert report_path.exists(), f'HTML report not found at {report_path}'

        content = report_path.read_text()
        assert '<!DOCTYPE html>' in content
        assert 'pytest-gremlins' in content.lower() or 'mutation' in content.lower()

    def it_prints_score_summary_when_html_report_write_fails(
        self, pytester_with_markers: pytest.Pytester, tmp_path: Path
    ):
        # Force a write failure by pointing --gremlins-html-dir at a read-only directory.
        # The console mutation summary must still appear even when the HTML write fails.
        read_only_dir = tmp_path / 'no-write'
        read_only_dir.mkdir()
        read_only_dir.chmod(0o444)

        pytester_with_markers.makepyfile(
            target_module="""
def is_adult(age):
    return age >= 18
""",
        )
        pytester_with_markers.makepyfile(
            test_target="""
from target_module import is_adult

def test_is_adult():
    assert is_adult(21) is True
""",
        )

        result = pytester_with_markers.runpytest(
            '--gremlins',
            '--gremlin-targets=target_module.py',
            '--gremlin-report=html',
            f'--gremlins-html-dir={read_only_dir}',
            '-v',
        )

        # The score summary is printed even though the HTML write failed.
        result.stdout.fnmatch_lines(['*mutation report*'])
        # No unhandled exception traceback in the output.
        result.stdout.no_fnmatch_line('*Traceback (most recent call last)*')

    def it_writes_html_report_to_custom_dir_when_gremlins_html_dir_given(self, pytester_with_markers: pytest.Pytester):
        """Verify that --gremlins-html-dir routes the HTML report to the specified directory."""
        pytester_with_markers.makepyfile(
            target_module="""
def is_adult(age):
    return age >= 18
""",
        )
        pytester_with_markers.makepyfile(
            test_target="""
from target_module import is_adult

def test_is_adult():
    assert is_adult(21) is True
""",
        )

        result = pytester_with_markers.runpytest(
            '--gremlins',
            '--gremlin-targets=target_module.py',
            '--gremlin-report=html',
            '--gremlins-html-dir=custom-reports',
            '-v',
        )
        result.assert_outcomes(passed=1)

        # Report must land in custom-reports/index.html, not the default path.
        custom_report = pytester_with_markers.path / 'custom-reports' / 'index.html'
        assert custom_report.exists(), f'HTML report not found at {custom_report}'
        assert not (pytester_with_markers.path / 'coverage' / 'gremlins' / 'index.html').exists()

        content = custom_report.read_text()
        assert '<!DOCTYPE html>' in content
