#!/usr/bin/env python3
"""
Performance benchmark tests for SoulEyez.

Run benchmarks:
    pytest tests/test_benchmarks.py --benchmark-only

Run with comparison:
    pytest tests/test_benchmarks.py --benchmark-only --benchmark-compare

These tests verify performance characteristics for:
- Nmap XML parsing (small and large scans)
- Database queries with many hosts
- Finding queries by severity

Note: Requires pytest-benchmark (dev dependency). Tests are skipped if not installed.
"""

import pytest

# Skip all tests in this module if pytest-benchmark is not installed
pytest.importorskip("pytest_benchmark")

import tempfile
from pathlib import Path


# ============================================================================
# Fixture Generators - Create large test data programmatically
# ============================================================================


def generate_nmap_output(num_hosts: int, ports_per_host: int = 10) -> str:
    """Generate nmap text output with specified number of hosts."""
    lines = [f"Starting Nmap 7.94 scan"]

    for i in range(num_hosts):
        ip = f"192.168.{i // 256}.{i % 256}"
        lines.append(f"\nNmap scan report for host-{i}.local ({ip})")
        lines.append("Host is up (0.001s latency).")

        for p in range(ports_per_host):
            port = 20 + p
            service = ["ssh", "http", "https", "ftp", "smtp", "mysql", "rdp", "smb", "telnet", "dns"][p % 10]
            lines.append(f"{port}/tcp   open  {service}   Test Service 1.0")

    lines.append(f"\nNmap done: {num_hosts} IP addresses ({num_hosts} hosts up)")
    return "\n".join(lines)


def generate_nmap_text(num_hosts: int, ports_per_host: int = 5) -> str:
    """Generate nmap text output with specified number of hosts."""
    lines = [f"Starting Nmap 7.94 scan"]

    for i in range(num_hosts):
        ip = f"192.168.{i // 256}.{i % 256}"
        lines.append(f"\nNmap scan report for {ip}")
        lines.append("Host is up (0.001s latency).")

        for p in range(ports_per_host):
            port = 20 + p
            service = ["ssh", "http", "https", "ftp", "smtp"][p % 5]
            lines.append(f"{port}/tcp   open  {service}")

    lines.append(f"\nNmap done: {num_hosts} IP addresses ({num_hosts} hosts up)")
    return "\n".join(lines)


# ============================================================================
# Pytest Fixtures
# ============================================================================


@pytest.fixture
def small_nmap_output():
    """Generate small nmap output (10 hosts)."""
    return generate_nmap_output(10, ports_per_host=5)


@pytest.fixture
def medium_nmap_output():
    """Generate medium nmap output (100 hosts)."""
    return generate_nmap_output(100, ports_per_host=10)


@pytest.fixture
def large_nmap_output():
    """Generate large nmap output (1000 hosts)."""
    return generate_nmap_output(1000, ports_per_host=15)


@pytest.fixture
def small_nmap_text():
    """Generate small nmap text (10 hosts)."""
    return generate_nmap_text(10)


@pytest.fixture
def large_nmap_text():
    """Generate large nmap text (500 hosts)."""
    return generate_nmap_text(500)


@pytest.fixture
def engagement_with_hosts(isolated_db):
    """Create engagement with configurable number of hosts."""
    # Create engagement directly via SQL
    conn = isolated_db.get_connection()
    cursor = conn.cursor()
    cursor.execute(
        "INSERT INTO engagements (name, description) VALUES (?, ?)",
        ("benchmark_engagement", "Benchmark test engagement"),
    )
    eng_id = cursor.lastrowid
    conn.commit()

    def _create_hosts(num_hosts: int):
        conn = isolated_db.get_connection()
        cursor = conn.cursor()

        for i in range(num_hosts):
            ip = f"10.0.{i // 256}.{i % 256}"
            cursor.execute(
                "INSERT INTO hosts (engagement_id, ip_address, hostname, os_name) VALUES (?, ?, ?, ?)",
                (eng_id, ip, f"host-{i}.local", "Linux"),
            )

            # Add some services per host
            host_id = cursor.lastrowid
            for p in range(5):
                cursor.execute(
                    "INSERT INTO services (host_id, port, protocol, service_name, state) VALUES (?, ?, ?, ?, ?)",
                    (host_id, 20 + p, "tcp", "test_service", "open"),
                )

        conn.commit()
        return eng_id

    return _create_hosts, isolated_db


@pytest.fixture
def engagement_with_findings(isolated_db):
    """Create engagement with findings of various severities."""
    # Create engagement directly via SQL
    conn = isolated_db.get_connection()
    cursor = conn.cursor()
    cursor.execute(
        "INSERT INTO engagements (name, description) VALUES (?, ?)",
        ("findings_benchmark", "Benchmark findings queries"),
    )
    eng_id = cursor.lastrowid
    conn.commit()

    def _create_findings(num_findings: int):
        conn = isolated_db.get_connection()
        cursor = conn.cursor()

        # Create a host first
        cursor.execute(
            "INSERT INTO hosts (engagement_id, ip_address) VALUES (?, ?)",
            (eng_id, "10.0.0.1"),
        )
        host_id = cursor.lastrowid

        severities = ["critical", "high", "medium", "low", "info"]
        for i in range(num_findings):
            severity = severities[i % 5]
            cursor.execute(
                "INSERT INTO findings (engagement_id, host_id, title, description, severity, finding_type) VALUES (?, ?, ?, ?, ?, ?)",
                (eng_id, host_id, f"Finding {i}", f"Description {i}", severity, "test"),
            )

        conn.commit()
        return eng_id

    return _create_findings, isolated_db


# ============================================================================
# Parser Benchmarks
# ============================================================================


class TestNmapParserBenchmarks:
    """Benchmark nmap parsing performance."""

    def test_benchmark_parse_small(self, benchmark, small_nmap_output):
        """Benchmark: Parse small nmap output (10 hosts)."""
        from souleyez.parsers.nmap_parser import parse_nmap_output

        result = benchmark(parse_nmap_output, small_nmap_output)
        assert "hosts" in result
        assert len(result.get("hosts", [])) >= 10

    def test_benchmark_parse_medium(self, benchmark, medium_nmap_output):
        """Benchmark: Parse medium nmap output (100 hosts)."""
        from souleyez.parsers.nmap_parser import parse_nmap_output

        result = benchmark(parse_nmap_output, medium_nmap_output)
        assert "hosts" in result
        assert len(result.get("hosts", [])) >= 100

    def test_benchmark_parse_large(self, benchmark, large_nmap_output):
        """Benchmark: Parse large nmap output (1000 hosts)."""
        from souleyez.parsers.nmap_parser import parse_nmap_output

        result = benchmark(parse_nmap_output, large_nmap_output)
        assert "hosts" in result
        # Should have at least 1000 hosts
        assert len(result.get("hosts", [])) >= 1000

    def test_benchmark_parse_text_small(self, benchmark, small_nmap_text):
        """Benchmark: Parse small nmap text (10 hosts)."""
        from souleyez.parsers.nmap_parser import parse_nmap_text

        result = benchmark(parse_nmap_text, small_nmap_text)
        assert "hosts" in result
        assert len(result.get("hosts", [])) >= 10

    def test_benchmark_parse_text_large(self, benchmark, large_nmap_text):
        """Benchmark: Parse large nmap text (500 hosts)."""
        from souleyez.parsers.nmap_parser import parse_nmap_text

        result = benchmark(parse_nmap_text, large_nmap_text)
        assert "hosts" in result
        assert len(result.get("hosts", [])) >= 500


# ============================================================================
# Database Query Benchmarks
# ============================================================================


class TestDatabaseBenchmarks:
    """Benchmark database query performance."""

    def test_benchmark_query_hosts_small(self, benchmark, engagement_with_hosts):
        """Benchmark: Query engagement with 50 hosts."""
        create_hosts, db = engagement_with_hosts
        eng_id = create_hosts(50)

        def query_hosts():
            conn = db.get_connection()
            cursor = conn.cursor()
            cursor.execute(
                "SELECT * FROM hosts WHERE engagement_id = ?", (eng_id,)
            )
            return cursor.fetchall()

        result = benchmark(query_hosts)
        assert len(result) == 50

    def test_benchmark_query_hosts_large(self, benchmark, engagement_with_hosts):
        """Benchmark: Query engagement with 1000 hosts."""
        create_hosts, db = engagement_with_hosts
        eng_id = create_hosts(1000)

        def query_hosts():
            conn = db.get_connection()
            cursor = conn.cursor()
            cursor.execute(
                "SELECT * FROM hosts WHERE engagement_id = ?", (eng_id,)
            )
            return cursor.fetchall()

        result = benchmark(query_hosts)
        assert len(result) == 1000

    def test_benchmark_query_services_with_join(self, benchmark, engagement_with_hosts):
        """Benchmark: Query services with host join (500 hosts)."""
        create_hosts, db = engagement_with_hosts
        eng_id = create_hosts(500)

        def query_services():
            conn = db.get_connection()
            cursor = conn.cursor()
            cursor.execute(
                """
                SELECT s.*, h.ip_address
                FROM services s
                JOIN hosts h ON s.host_id = h.id
                WHERE h.engagement_id = ?
                """,
                (eng_id,),
            )
            return cursor.fetchall()

        result = benchmark(query_services)
        # 500 hosts * 5 services each = 2500
        assert len(result) == 2500

    def test_benchmark_query_findings_by_severity(self, benchmark, engagement_with_findings):
        """Benchmark: Query findings filtered by severity (1000 findings)."""
        create_findings, db = engagement_with_findings
        eng_id = create_findings(1000)

        def query_critical():
            conn = db.get_connection()
            cursor = conn.cursor()
            cursor.execute(
                "SELECT * FROM findings WHERE engagement_id = ? AND severity = ?",
                (eng_id, "critical"),
            )
            return cursor.fetchall()

        result = benchmark(query_critical)
        # 1000 findings / 5 severities = 200 critical
        assert len(result) == 200


# ============================================================================
# Large Scale Tests (Stress Tests)
# ============================================================================


class TestLargeScalePerformance:
    """Test performance at scale to ensure no regressions."""

    def test_parse_large_scan(self, benchmark):
        """Stress test: Parse large nmap scan (2000 hosts)."""
        # 2000 hosts with 20 ports each
        output_content = generate_nmap_output(2000, ports_per_host=20)

        # Verify content is substantial
        size_kb = len(output_content) / 1024
        assert size_kb >= 500, f"Generated output is only {size_kb:.0f}KB, expected >500KB"

        from souleyez.parsers.nmap_parser import parse_nmap_output

        result = benchmark(parse_nmap_output, output_content)
        assert "hosts" in result
        # 2000 hosts
        assert len(result.get("hosts", [])) >= 2000

    def test_engagement_with_1000_plus_hosts(self, engagement_with_hosts):
        """Stress test: Create and query engagement with 1000+ hosts."""
        create_hosts, db = engagement_with_hosts
        eng_id = create_hosts(1500)

        # Query all hosts
        conn = db.get_connection()
        cursor = conn.cursor()
        cursor.execute("SELECT COUNT(*) FROM hosts WHERE engagement_id = ?", (eng_id,))
        count = cursor.fetchone()[0]
        assert count == 1500

        # Query with complex join
        cursor.execute(
            """
            SELECT h.ip_address, COUNT(s.id) as service_count
            FROM hosts h
            LEFT JOIN services s ON h.id = s.host_id
            WHERE h.engagement_id = ?
            GROUP BY h.id
            """,
            (eng_id,),
        )
        results = cursor.fetchall()
        assert len(results) == 1500
