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

Parses smbmap SMB share enumeration output into structured data.
"""

import re
from typing import Any, Dict, List


def parse_smbmap_output(output: str, target: str = "") -> Dict[str, Any]:
    """
    Parse smbmap output and extract SMB shares with permissions.

    smbmap output format:
    [+] IP: 10.0.0.82:445	Name: 10.0.0.82           	Status: Authenticated
        Disk                                                  	Permissions	Comment
        ----                                                  	-----------	-------
        print$                                            	NO ACCESS	Printer Drivers
        tmp                                               	READ, WRITE	oh noes!
        opt                                               	NO ACCESS
        IPC$                                              	NO ACCESS	IPC Service

    Args:
        output: Raw smbmap output text
        target: Target IP/hostname from job

    Returns:
        Dict with structure:
        {
            'target': str,
            'status': str,  # Authenticated, Guest, etc.
            'shares': [
                {
                    'name': str,
                    'type': str,  # Disk, IPC, etc.
                    'permissions': str,  # READ, WRITE, READ/WRITE, NO ACCESS
                    'comment': str,
                    'readable': bool,
                    'writable': bool
                },
                ...
            ],
            'files': [  # If -R was used
                {
                    'share': str,
                    'path': str,
                    'size': int,
                    'timestamp': str
                },
                ...
            ],
            'smb_detected': bool,  # True if SMB service was detected
            'hosts_count': int,    # Number of hosts serving SMB
            'error': str           # Error message if tool crashed
        }
    """
    result = {
        "target": target,
        "status": None,
        "shares": [],
        "files": [],
        "smb_detected": False,
        "hosts_count": 0,
        "error": None,
    }

    # Check for SMB detection (even if tool crashes later)
    # [*] Detected 1 hosts serving SMB
    smb_detected_match = re.search(
        r"\[\*\]\s*Detected\s+(\d+)\s+hosts?\s+serving\s+SMB", output
    )
    if smb_detected_match:
        result["smb_detected"] = True
        result["hosts_count"] = int(smb_detected_match.group(1))

    # Check for Python traceback (tool crash)
    if "Traceback (most recent call last):" in output:
        # Extract error message from traceback
        error_match = re.search(
            r'(?:Error|Exception).*?[\'"]([^\'"]+)[\'"]', output, re.DOTALL
        )
        if error_match:
            result["error"] = error_match.group(1)
        else:
            # Try to get the last line of the traceback
            traceback_lines = (
                output.split("Traceback (most recent call last):")[-1]
                .strip()
                .split("\n")
            )
            for line in reversed(traceback_lines):
                line = line.strip()
                if (
                    line
                    and not line.startswith("File")
                    and not line.startswith("raise")
                ):
                    result["error"] = line[:200]  # Limit length
                    break

    lines = output.split("\n")
    in_share_table = False
    current_share = None

    for i, line in enumerate(lines):
        # Remove ANSI color codes and control characters more thoroughly
        line = re.sub(r"\x1b\[[0-9;]*[a-zA-Z]", "", line)  # All ANSI escape sequences
        line = re.sub(r"\x1b\].*?\x07", "", line)  # OSC sequences
        # Only remove leading progress indicators, not all brackets
        line = re.sub(r"^[\[\]\|/\\-]+\s*", "", line)
        line = line.strip()

        # Extract target and status
        # [+] IP: 10.0.0.82:445	Name: 10.0.0.82           	Status: Authenticated
        if line.startswith("+") and "IP:" in line and "Status:" in line:
            status_match = re.search(r"Status:\s+(\w+)", line)
            if status_match:
                result["status"] = status_match.group(1)

            # Extract target IP if not provided
            if not result["target"]:
                ip_match = re.search(r"IP:\s+([\d\.]+)", line)
                if ip_match:
                    result["target"] = ip_match.group(1)

        # Detect share table header
        # Disk                                                  	Permissions	Comment
        elif "Disk" in line and "Permissions" in line and "Comment" in line:
            in_share_table = True
            continue

        # Skip separator line
        elif line.startswith("----") or line.startswith("==="):
            continue

        # Parse share entries
        elif (
            in_share_table
            and line
            and not line.startswith("*")
            and not line.startswith("Closed")
        ):
            # Try to parse share line
            # Format: sharename <tabs/spaces> permissions <tabs/spaces> comment
            # tmp                                               	READ, WRITE	oh noes!

            share_name = None
            permissions = None
            comment = ""

            # Try tab split first
            parts = re.split(r"\t+", line)
            if len(parts) >= 2:
                share_name = parts[0].strip()
                # Find permissions in remaining parts
                for p in parts[1:]:
                    p = p.strip().upper()
                    if any(x in p for x in ["READ", "WRITE", "NO ACCESS", "NOACCESS"]):
                        permissions = p
                        break
                # Comment is everything after permissions
                if permissions and len(parts) > 2:
                    perm_idx = next(
                        (i for i, p in enumerate(parts) if permissions in p.upper()), -1
                    )
                    if perm_idx >= 0 and perm_idx + 1 < len(parts):
                        comment = " ".join(parts[perm_idx + 1 :]).strip()

            # No tabs or tab parse failed - try space-based parsing
            if not permissions:
                # Match patterns with flexible spacing and permission variations
                permission_patterns = [
                    r"^\s*(\S+)\s{2,}(READ,?\s*WRITE|READ\s*ONLY|WRITE\s*ONLY|NO\s*ACCESS|READ|WRITE)(?:\s{2,}(.*))?$",
                    r"^\s*(\S+)\s+(READ,?\s*WRITE|READ\s*ONLY|WRITE\s*ONLY|NO\s*ACCESS|READ|WRITE)\s*(.*)$",
                ]
                for pattern in permission_patterns:
                    match = re.match(pattern, line, re.IGNORECASE)
                    if match:
                        share_name = match.group(1).strip()
                        permissions = match.group(2).strip().upper()
                        comment = match.group(3).strip() if match.group(3) else ""
                        break

            if not share_name or not permissions:
                continue

            # Skip empty lines or non-share lines
            if not share_name or share_name in ["Disk", "IPC", "", "*"]:
                continue

            # Skip separator lines (----, ===, etc.)
            if re.match(r"^[\-=]+$", share_name):
                continue

            # Determine share type (Disk vs IPC)
            share_type = (
                "IPC" if share_name.endswith("$") and "IPC" in comment else "Disk"
            )

            # Parse permissions
            readable = "READ" in permissions.upper()
            writable = "WRITE" in permissions.upper()

            share_info = {
                "name": share_name,
                "type": share_type,
                "permissions": permissions,
                "comment": comment,
                "readable": readable,
                "writable": writable,
            }

            result["shares"].append(share_info)
            current_share = share_name

        # Parse file listings (if -R was used)
        # dr--r--r--                0 Sat May 16 14:06:55 2009	.
        # dr--r--r--                0 Sat May 16 14:06:55 2009	..
        # fr--r--r--              512 Sat May 16 14:06:55 2009	script.sh
        elif current_share and re.match(r"^[df]r", line):
            # File listing format: permissions size timestamp filename
            file_match = re.match(r"^([df]r[\-rwx]+)\s+(\d+)\s+(.+?)\s{2,}(.+)$", line)
            if file_match:
                perms, size, timestamp, filename = file_match.groups()

                # Skip . and .. entries
                if filename in [".", ".."]:
                    continue

                file_info = {
                    "share": current_share,
                    "path": filename,
                    "size": int(size),
                    "timestamp": timestamp.strip(),
                    "is_directory": perms.startswith("d"),
                }
                result["files"].append(file_info)

        # Detect end of output
        elif "Closed" in line and "connections" in line:
            in_share_table = False
            current_share = None

    return result


def extract_findings(parsed_data: Dict[str, Any]) -> List[Dict[str, Any]]:
    """
    Extract security findings from parsed smbmap data.

    Returns:
        List of finding dictionaries with:
        {
            'title': str,
            'severity': str,  # high, medium, low, info
            'description': str,
            'evidence': str
        }
    """
    findings = []

    # Finding 1: Writable shares (HIGH severity)
    writable_shares = [s for s in parsed_data["shares"] if s["writable"]]
    if writable_shares:
        share_names = ", ".join([s["name"] for s in writable_shares])
        findings.append(
            {
                "title": "Writable SMB Shares Detected",
                "severity": "high",
                "description": f"Found {len(writable_shares)} SMB share(s) with WRITE permissions: {share_names}. "
                "Writable shares can be exploited to upload malicious files, plant ransomware, "
                "or exfiltrate sensitive data.",
                "evidence": "\n".join(
                    [
                        f"- {s['name']}: {s['permissions']} ({s['comment']})"
                        for s in writable_shares
                    ]
                ),
            }
        )

    # Finding 2: Readable shares (MEDIUM severity)
    readable_shares = [
        s
        for s in parsed_data["shares"]
        if s["readable"] and not s["writable"] and s["type"] == "Disk"
    ]
    if readable_shares:
        share_names = ", ".join([s["name"] for s in readable_shares])
        findings.append(
            {
                "title": "Readable SMB Shares Detected",
                "severity": "medium",
                "description": f"Found {len(readable_shares)} SMB share(s) with READ permissions: {share_names}. "
                "These shares may contain sensitive information accessible without proper authentication.",
                "evidence": "\n".join(
                    [
                        f"- {s['name']}: {s['permissions']} ({s['comment']})"
                        for s in readable_shares
                    ]
                ),
            }
        )

    # Finding 3: Anonymous access (MEDIUM severity)
    if parsed_data.get("status") in ["Guest", "Anonymous"]:
        accessible_shares = [
            s for s in parsed_data["shares"] if s["readable"] or s["writable"]
        ]
        if accessible_shares:
            findings.append(
                {
                    "title": "Anonymous SMB Access Allowed",
                    "severity": "medium",
                    "description": f"Anonymous/guest access is permitted, allowing access to {len(accessible_shares)} share(s) "
                    "without authentication. This violates the principle of least privilege.",
                    "evidence": f"Authentication Status: {parsed_data.get('status')}\n"
                    + "\n".join(
                        [
                            f"- {s['name']}: {s['permissions']}"
                            for s in accessible_shares
                        ]
                    ),
                }
            )

    # Finding 4: Sensitive files exposed (if files were enumerated)
    sensitive_patterns = [
        (r"\.config$", "Configuration files"),
        (r"password|passwd|pwd", "Password files"),
        (r"\.key|\.pem|\.crt", "Cryptographic keys/certificates"),
        (r"backup|\.bak|\.old", "Backup files"),
        (r"\.sql|\.db|\.sqlite", "Database files"),
        (r"id_rsa|id_dsa|\.ssh", "SSH private keys"),
    ]

    for pattern, desc in sensitive_patterns:
        matching_files = [
            f
            for f in parsed_data["files"]
            if re.search(pattern, f["path"], re.IGNORECASE)
        ]
        if matching_files:
            findings.append(
                {
                    "title": f"Sensitive Files Exposed: {desc}",
                    "severity": "high",
                    "description": f"Found {len(matching_files)} potentially sensitive file(s) ({desc}) "
                    "accessible via SMB shares.",
                    "evidence": "\n".join(
                        [
                            f"- {f['share']}/{f['path']} ({f['size']} bytes)"
                            for f in matching_files[:10]  # Limit to first 10
                        ]
                    )
                    + (
                        f"\n... and {len(matching_files) - 10} more"
                        if len(matching_files) > 10
                        else ""
                    ),
                }
            )

    # Finding 5: Info - All shares enumerated
    if parsed_data["shares"]:
        findings.append(
            {
                "title": "SMB Share Enumeration Successful",
                "severity": "info",
                "description": f"Successfully enumerated {len(parsed_data['shares'])} SMB share(s) on target.",
                "evidence": "\n".join(
                    [
                        f"- {s['name']} ({s['type']}): {s['permissions']}"
                        for s in parsed_data["shares"]
                    ]
                ),
            }
        )

    return findings
