#!/usr/bin/env python3
"""
Handler for kerbrute Kerberos enumeration tool.
"""

import logging
import os
import re
from typing import Any, Dict, List, Optional

import click

from souleyez.engine.job_status import (
    STATUS_DONE,
    STATUS_ERROR,
    STATUS_NO_RESULTS,
    STATUS_WARNING,
)
from souleyez.handlers.base import BaseToolHandler

logger = logging.getLogger(__name__)


class KerbruteHandler(BaseToolHandler):
    """Handler for kerbrute Kerberos enumeration."""

    tool_name = "kerbrute"
    display_name = "Kerbrute"

    has_error_handler = True
    has_warning_handler = True
    has_no_results_handler = True
    has_done_handler = True

    # Patterns for valid users
    VALID_USER_PATTERN = r"VALID USERNAME:\s*(\S+@\S+|\S+)"

    # Patterns for successful password spray
    VALID_CRED_PATTERN = r"VALID LOGIN:\s*(\S+):(\S+)"

    # Error patterns
    ERROR_PATTERNS = [
        (r"KDC_ERR_WRONG_REALM", "Wrong realm/domain specified"),
        (r"KDC_ERR_C_PRINCIPAL_UNKNOWN", "Principal unknown"),
        (r"error getting AS-REP", "AS-REP error"),
        (r"connection refused", "Connection refused"),
        (r"no such host", "Host not found"),
    ]

    def parse_job(
        self,
        engagement_id: int,
        log_path: str,
        job: Dict[str, Any],
        host_manager: Optional[Any] = None,
        findings_manager: Optional[Any] = None,
        credentials_manager: Optional[Any] = None,
    ) -> Dict[str, Any]:
        """Parse kerbrute results."""
        try:
            # Guard against empty output
            if not os.path.exists(log_path) or os.path.getsize(log_path) == 0:
                return {
                    "tool": self.tool_name,
                    "status": STATUS_NO_RESULTS,
                    "summary": "Empty output (tool produced no output)",
                }

            from souleyez.engine.result_handler import detect_tool_error

            target = job.get("target", "")

            if not log_path or not os.path.exists(log_path):
                return {
                    "tool": "kerbrute",
                    "status": STATUS_ERROR,
                    "error": "Log file not found",
                    "summary": "Log file not found",
                }

            with open(log_path, "r", encoding="utf-8", errors="replace") as f:
                log_content = f.read()

            # Strip ANSI color codes
            log_content = re.sub(r"\x1b\[[0-9;]*m", "", log_content)

            # Check for errors first
            for pattern, error_msg in self.ERROR_PATTERNS:
                if re.search(pattern, log_content, re.IGNORECASE):
                    # Don't return error for KDC_ERR_C_PRINCIPAL_UNKNOWN - that's expected
                    if "PRINCIPAL_UNKNOWN" in pattern:
                        continue
                    return {
                        "tool": "kerbrute",
                        "status": STATUS_ERROR,
                        "target": target,
                        "error": error_msg,
                        "summary": error_msg,
                    }

            # Parse valid usernames
            valid_users = re.findall(self.VALID_USER_PATTERN, log_content)
            # Clean up usernames (remove @domain if present for consistency)
            users = []
            for user in valid_users:
                if "@" in user:
                    username = user.split("@")[0]
                else:
                    username = user
                if username and username not in users:
                    users.append(username)

            # Parse valid credentials (from password spray)
            valid_creds = re.findall(self.VALID_CRED_PATTERN, log_content)
            credentials = []
            for username, password in valid_creds:
                credentials.append(
                    {
                        "username": username,
                        "password": password,
                        "source": "kerbrute_spray",
                    }
                )

            # Store credentials and usernames if found
            if credentials_manager:
                if host_manager is None:
                    from souleyez.storage.hosts import HostManager

                    host_manager = HostManager()

                host = host_manager.get_host_by_ip(engagement_id, target)
                if host:
                    # Store valid credentials from password spray
                    for cred in credentials:
                        try:
                            credentials_manager.add_credential(
                                engagement_id=engagement_id,
                                host_id=host["id"],
                                username=cred["username"],
                                password=cred["password"],
                                service="kerberos",
                                credential_type="password",
                                tool="kerbrute",
                                status="valid",
                            )
                            logger.warning(f"CREDENTIAL FOUND: {cred['username']}")
                        except Exception:
                            pass

                    # Store enumerated usernames (without password)
                    for username in users:
                        try:
                            credentials_manager.add_credential(
                                engagement_id=engagement_id,
                                host_id=host["id"],
                                username=username,
                                password="",
                                service="kerberos",
                                credential_type="username",
                                tool="kerbrute",
                                status="untested",
                            )
                        except Exception:
                            pass  # Skip duplicates
                    if users:
                        logger.info(
                            f"kerbrute: Stored {len(users)} enumerated usernames"
                        )

            # Check for tool-level errors (supplement to handler's own checks)
            tool_error = detect_tool_error(log_content, "kerbrute")

            # Findings ALWAYS take priority over error detection
            if users or credentials:
                logger.info(
                    f"kerbrute: Found {len(users)} valid user(s), {len(credentials)} credential(s)"
                )
                # Build summary
                summary_parts = []
                if users:
                    summary_parts.append(f"{len(users)} valid user(s)")
                if credentials:
                    summary_parts.append(f"{len(credentials)} credential(s)")
                summary = " | ".join(summary_parts)

                return {
                    "tool": "kerbrute",
                    "status": STATUS_DONE,
                    "target": target,
                    "summary": summary,
                    "users": users,
                    "users_found": len(users),
                    "credentials_found": credentials,
                    "credentials_added": len(credentials),
                }
            elif tool_error:
                return {
                    "tool": self.tool_name,
                    "status": STATUS_ERROR,
                    "target": target,
                    "error": tool_error,
                    "summary": f"Tool error: {tool_error}",
                }

            return {
                "tool": "kerbrute",
                "status": STATUS_NO_RESULTS,
                "target": target,
                "summary": "No valid users found",
            }

        except Exception as e:
            logger.error(f"Error parsing kerbrute job: {e}")
            return {
                "tool": "kerbrute",
                "status": STATUS_ERROR,
                "error": str(e),
                "summary": f"Error: {str(e)}",
            }

    def display_done(
        self,
        job: Dict[str, Any],
        log_path: str,
        show_all: bool = False,
        show_passwords: bool = False,
    ) -> None:
        """Display successful kerbrute results."""
        click.echo()
        click.echo(click.style("=" * 70, fg="green"))
        click.echo(
            click.style("KERBRUTE ENUMERATION SUCCESSFUL", fg="green", bold=True)
        )
        click.echo(click.style("=" * 70, fg="green"))
        click.echo()

        try:
            with open(log_path, "r", encoding="utf-8", errors="replace") as f:
                log_content = f.read()

            # Strip ANSI color codes
            log_content = re.sub(r"\x1b\[[0-9;]*m", "", log_content)

            # Parse and display valid users
            valid_users = re.findall(self.VALID_USER_PATTERN, log_content)
            if valid_users:
                click.echo(
                    click.style(
                        f"  VALID USERS ({len(valid_users)})", bold=True, fg="cyan"
                    )
                )
                for user in valid_users:
                    click.echo(f"    {click.style(user, fg='green')}")
                click.echo()

            # Parse and display valid credentials
            valid_creds = re.findall(self.VALID_CRED_PATTERN, log_content)
            if valid_creds:
                click.echo(
                    click.style(
                        f"  VALID CREDENTIALS ({len(valid_creds)})", bold=True, fg="red"
                    )
                )
                for username, password in valid_creds:
                    if show_passwords:
                        click.echo(
                            f"    {click.style(f'{username}:{password}', fg='red', bold=True)}"
                        )
                    else:
                        click.echo(f"    {click.style(f'{username}:***', fg='red')}")
                click.echo()

        except Exception as e:
            click.echo(f"  Error reading log: {e}")

        click.echo()

    def display_error(
        self,
        job: Dict[str, Any],
        log_path: str,
        show_all: bool = False,
    ) -> None:
        """Display kerbrute error."""
        click.echo()
        click.echo(click.style("=" * 70, fg="red"))
        click.echo(click.style("KERBRUTE FAILED", fg="red", bold=True))
        click.echo(click.style("=" * 70, fg="red"))
        click.echo()

        try:
            with open(log_path, "r", encoding="utf-8", errors="replace") as f:
                log_content = f.read()

            for pattern, error_msg in self.ERROR_PATTERNS:
                if re.search(pattern, log_content, re.IGNORECASE):
                    click.echo(f"  Error: {error_msg}")
                    break
            else:
                click.echo("  Kerbrute failed - check log for details")

        except Exception:
            click.echo("  Could not read error details")

        click.echo()

    def display_warning(
        self,
        job: Dict[str, Any],
        log_path: str,
        show_all: bool = False,
    ) -> None:
        """Display kerbrute warning."""
        click.echo()
        click.echo(click.style("=" * 70, fg="yellow"))
        click.echo(click.style("KERBRUTE - PARTIAL RESULTS", fg="yellow", bold=True))
        click.echo(click.style("=" * 70, fg="yellow"))
        click.echo()
        click.echo("  Enumeration completed with warnings")
        click.echo()

    def display_no_results(
        self,
        job: Dict[str, Any],
        log_path: str,
        show_all: bool = False,
    ) -> None:
        """Display kerbrute no results."""
        click.echo()
        click.echo(click.style("=" * 70, fg="yellow"))
        click.echo(
            click.style("KERBRUTE - NO VALID USERS FOUND", fg="yellow", bold=True)
        )
        click.echo(click.style("=" * 70, fg="yellow"))
        click.echo()
        click.echo("  No valid usernames were identified.")
        click.echo()
        click.echo(click.style("  Tips:", dim=True))
        click.echo("    - Try a different username wordlist")
        click.echo("    - Verify the domain name is correct")
        click.echo("    - Check if Kerberos (port 88) is accessible")
        click.echo()

    def generate_next_steps(
        self,
        job: Dict[str, Any],
        context: Any,
    ) -> List[Any]:
        """
        Generate SMART next steps based on Kerbrute enumeration results.

        Smart suggestions:
        - AS-REP roasting with hashcat command for enumerated users
        - Password spraying with common patterns
        - SMB/WinRM verification when credentials found
        - Kerberoasting and BloodHound when authenticated
        """
        from souleyez.core.next_steps import create_next_step

        steps = []
        parse_result = job.get("parse_result", {})
        if not isinstance(parse_result, dict):
            return steps

        target = job.get("target", "")
        users = parse_result.get("users", [])
        credentials = parse_result.get("credentials_found", [])

        # Extract domain from job args (-d flag)
        domain = self._extract_domain_from_args(job.get("args", ""))

        # If valid users found - suggest AS-REP roasting
        if users:
            user_sample = ", ".join(users[:3])
            steps.append(
                create_next_step(
                    title=f"AS-REP roasting on {len(users)} enumerated users",
                    reason=f"Valid users: {user_sample}... - check for no preauth",
                    commands=[
                        f"GetNPUsers.py {domain}/ -dc-ip {target} -usersfile users.txt -format hashcat -outputfile asrep.txt",
                        "# Crack captured hashes:",
                        "hashcat -m 18200 asrep.txt /usr/share/wordlists/rockyou.txt",
                    ],
                    priority=1,
                    category="kerberos",
                    target=target,
                    tool="impacket_getnpusers",
                )
            )

            # Suggest password spraying with seasonal/common patterns
            steps.append(
                create_next_step(
                    title="Password spray with seasonal patterns",
                    reason=f"{len(users)} valid user(s) - try seasonal/company patterns",
                    commands=[
                        f"kerbrute passwordspray --dc {target} -d {domain} users.txt 'Winter2024!'",
                        f"kerbrute passwordspray --dc {target} -d {domain} users.txt 'Company123!'",
                        "# Be careful with lockout policies!",
                    ],
                    priority=2,
                    category="kerberos",
                    target=target,
                    tool="kerbrute",
                )
            )

        # If credentials found - verify and escalate
        if credentials:
            cred = credentials[0]
            username = cred.get("username", "")
            password = cred.get("password", "")

            # Priority 1: Verify credentials via SMB
            steps.append(
                create_next_step(
                    title="Verify credentials via SMB",
                    reason="Credentials from spray - check admin access",
                    commands=[
                        f"crackmapexec smb {target} -u '{username}' -p '{password}' -d {domain}",
                        f"# If admin (Pwn3d!):",
                        f"evil-winrm -i {target} -u '{username}' -p '{password}'",
                    ],
                    priority=1,
                    category="auth",
                    target=target,
                    tool="crackmapexec",
                    requires_creds=True,
                    cred_type="smb",
                )
            )

            # Priority 1: Kerberoasting
            steps.append(
                create_next_step(
                    title="Kerberoasting with valid credentials",
                    reason="Valid credentials - extract service account TGS tickets",
                    commands=[
                        f"GetUserSPNs.py {domain}/{username}:'{password}' -dc-ip {target} -request -outputfile spns.txt",
                        "# Crack captured hashes:",
                        "hashcat -m 13100 spns.txt /usr/share/wordlists/rockyou.txt",
                    ],
                    priority=1,
                    category="kerberos",
                    target=target,
                    tool="impacket_getuserspns",
                    requires_creds=True,
                    cred_type="kerberos",
                )
            )

            # Priority 2: BloodHound
            steps.append(
                create_next_step(
                    title="BloodHound collection",
                    reason="Valid credentials - map AD attack paths",
                    commands=[
                        f"bloodhound-python -c All -u '{username}' -p '{password}' -d {domain} -dc {target}",
                    ],
                    priority=2,
                    category="ad",
                    target=target,
                    tool="bloodhound",
                    requires_creds=True,
                    cred_type="kerberos",
                )
            )

        return steps[:10]

    def _extract_domain_from_args(self, args: str) -> str:
        """
        Extract domain name from kerbrute command args.

        Args:
            args: The command arguments string

        Returns:
            Domain name or "DOMAIN" placeholder
        """
        if not args:
            return "DOMAIN"

        # Look for -d or --domain flag
        domain_match = re.search(r"-d\s+([^\s]+)", args)
        if domain_match:
            return domain_match.group(1)

        domain_match = re.search(r"--domain\s+([^\s]+)", args)
        if domain_match:
            return domain_match.group(1)

        return "DOMAIN"
