#!/usr/bin/env python3
"""
souleyez.parsers.nikto_parser

Parses Nikto web server scanner output into structured data.
"""
import re
from typing import Dict, Any, List


def parse_nikto_output(output: str, target: str = "") -> Dict[str, Any]:
    """
    Parse Nikto output and extract findings.

    Nikto output format:
    - Nikto v2.5.0
    ---------------------------------------------------------------------------
    + Target IP:          1.2.3.4
    + Target Hostname:    example.com
    + Target Port:        80
    + Start Time:         2025-01-01 12:00:00 (GMT0)
    ---------------------------------------------------------------------------
    + Server: Apache/2.4.41 (Ubuntu)
    + /: The anti-clickjacking X-Frame-Options header is not present.
    + /admin/: Directory indexing found.
    + OSVDB-3092: /admin/: This might be interesting...
    + /config.php.bak: Backup file found.
    ---------------------------------------------------------------------------
    + 1 host(s) tested

    Returns:
        Dict with structure:
        {
            'target': str,
            'target_ip': str,
            'target_port': int,
            'server': str,
            'findings': [
                {
                    'osvdb': str or None,
                    'path': str,
                    'description': str,
                    'severity': str  # 'info', 'low', 'medium', 'high'
                },
                ...
            ],
            'stats': {
                'total': int,
                'by_severity': {'high': int, 'medium': int, 'low': int, 'info': int}
            }
        }
    """
    result = {
        'target': target,
        'target_ip': '',
        'target_hostname': '',
        'target_port': 80,
        'server': '',
        'findings': [],
        'stats': {
            'total': 0,
            'by_severity': {'high': 0, 'medium': 0, 'low': 0, 'info': 0}
        }
    }

    lines = output.split('\n')

    for line in lines:
        line = line.strip()

        # Skip empty lines and dividers
        if not line or line.startswith('---') or line.startswith('==='):
            continue

        # Parse target info
        if line.startswith('+ Target IP:'):
            match = re.search(r'\+ Target IP:\s+(.+)', line)
            if match:
                result['target_ip'] = match.group(1).strip()

        elif line.startswith('+ Target Hostname:'):
            match = re.search(r'\+ Target Hostname:\s+(.+)', line)
            if match:
                result['target_hostname'] = match.group(1).strip()

        elif line.startswith('+ Target Port:'):
            match = re.search(r'\+ Target Port:\s+(\d+)', line)
            if match:
                result['target_port'] = int(match.group(1))

        elif line.startswith('+ Server:'):
            match = re.search(r'\+ Server:\s+(.+)', line)
            if match:
                result['server'] = match.group(1).strip()

        # Parse findings (any line starting with + that isn't metadata)
        elif line.startswith('+'):
            finding = _parse_finding_line(line)
            if finding:
                result['findings'].append(finding)
                severity = finding.get('severity', 'info')
                result['stats']['by_severity'][severity] = result['stats']['by_severity'].get(severity, 0) + 1
                result['stats']['total'] += 1

    return result


def _parse_finding_line(line: str) -> Dict[str, Any]:
    """
    Parse a single Nikto finding line.

    Formats:
    + OSVDB-3092: /admin/: This might be interesting...
    + /admin/: Directory indexing found.
    + /: The anti-clickjacking X-Frame-Options header is not present.
    + Server: Apache/2.4.41 (Ubuntu)  <- Skip these
    """
    # Skip metadata lines
    if line.startswith('+ Server:') or line.startswith('+ Start Time:'):
        return None
    if line.startswith('+ Target'):
        return None
    if line.startswith('+ End Time:'):
        return None
    if line.startswith('+ No CGI'):
        return None
    if 'host(s) tested' in line:
        return None
    if 'items checked:' in line:
        return None

    try:
        # Remove leading +
        content = line.lstrip('+ ').strip()

        osvdb = None
        path = ''
        description = ''

        # Check for OSVDB reference
        osvdb_match = re.match(r'(OSVDB-\d+):\s*(.+)', content)
        if osvdb_match:
            osvdb = osvdb_match.group(1)
            content = osvdb_match.group(2)

        # Extract path and description
        # Format: /path/: Description or /path: Description
        path_match = re.match(r'(/[^:]*):?\s*(.+)', content)
        if path_match:
            path = path_match.group(1).rstrip(':').strip()
            description = path_match.group(2).strip()
        else:
            description = content

        # Skip if no meaningful content
        if not description or description.startswith('Target'):
            return None

        # Determine severity based on keywords
        severity = _determine_severity(description, osvdb)

        return {
            'osvdb': osvdb,
            'path': path,
            'description': description,
            'severity': severity
        }

    except Exception:
        return None


def _determine_severity(description: str, osvdb: str = None) -> str:
    """
    Determine finding severity based on description and OSVDB reference.
    """
    desc_lower = description.lower()

    # High severity indicators
    high_keywords = [
        'remote code execution', 'rce', 'command execution', 'shell',
        'sql injection', 'file inclusion', 'lfi', 'rfi',
        'arbitrary file', 'password', 'credentials',
        'authentication bypass', 'backdoor'
    ]
    for keyword in high_keywords:
        if keyword in desc_lower:
            return 'high'

    # Medium severity indicators
    medium_keywords = [
        'directory indexing', 'directory listing', 'backup file',
        'config file', 'sensitive', 'exposure', 'disclosure',
        'xss', 'cross-site', 'injection', 'traversal',
        'default', 'admin', '.bak', '.old', '.swp'
    ]
    for keyword in medium_keywords:
        if keyword in desc_lower:
            return 'medium'

    # Low severity indicators
    low_keywords = [
        'header', 'x-frame', 'x-content-type', 'x-xss',
        'cookie', 'httponly', 'secure flag', 'hsts',
        'content-security-policy', 'csp', 'clickjacking'
    ]
    for keyword in low_keywords:
        if keyword in desc_lower:
            return 'low'

    # OSVDB references often indicate real issues
    if osvdb:
        return 'medium'

    return 'info'


def get_findings_by_severity(parsed: Dict[str, Any], severity: str) -> List[Dict[str, Any]]:
    """
    Filter findings by severity level.
    """
    return [f for f in parsed.get('findings', []) if f.get('severity') == severity]


def get_high_value_findings(parsed: Dict[str, Any]) -> List[Dict[str, Any]]:
    """
    Get findings that are high or medium severity.
    """
    return [f for f in parsed.get('findings', []) if f.get('severity') in ('high', 'medium')]
