#!/usr/bin/env python3
"""
Impacket Ticketer handler.

Handles parsing and display for Silver/Golden Ticket creation.
"""

import logging
import os
import re
from pathlib import Path
from typing import Any, Dict, Optional

import click

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

logger = logging.getLogger(__name__)


class ImpacketTicketerHandler(BaseToolHandler):
    """Handler for Impacket Ticketer Silver/Golden Ticket jobs."""

    tool_name = "impacket-ticketer"
    display_name = "Ticketer (Silver/Golden Ticket)"

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

    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 ticketer 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", "")

            # Read log file
            if not log_path or not os.path.exists(log_path):
                return {
                    "tool": "impacket-ticketer",
                    "status": STATUS_ERROR,
                    "error": "Log file not found",
                }

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

            # Look for success pattern: "Saving ticket in <username>.ccache"
            ccache_match = re.search(r"Saving ticket in\s+(\S+\.ccache)", output)
            ccache_path = ""
            impersonated_user = ""

            if ccache_match:
                ccache_filename = ccache_match.group(1)
                # The file is created in the current working directory (usually /home/user)
                # Try to find it
                possible_paths = [
                    Path.home() / ccache_filename,
                    Path.cwd() / ccache_filename,
                    Path("/tmp") / ccache_filename,
                ]
                for path in possible_paths:
                    if path.exists():
                        ccache_path = str(path)
                        break
                else:
                    # File not found in expected locations, use home as default
                    ccache_path = str(Path.home() / ccache_filename)

                # Extract username from ccache filename (e.g., Administrator.ccache)
                impersonated_user = ccache_filename.replace(".ccache", "")

            # Extract SPN from command args
            target_spn = ""
            spn_match = re.search(r"-spn\s+(\S+)", output)
            if spn_match:
                target_spn = spn_match.group(1)

            # Extract domain from command args
            domain = ""
            domain_match = re.search(r"-domain\s+(\S+)", output)
            if domain_match:
                domain = domain_match.group(1)

            # Check for errors
            error_patterns = [
                (r"KDC_ERR", "Kerberos error"),
                (r"ERROR:", "Tool error"),
                (r"Traceback", "Python exception"),
            ]

            tool_error = detect_tool_error(output, "impacket_ticketer")

            for pattern, error_msg in error_patterns:
                if re.search(pattern, output, re.IGNORECASE):
                    return {
                        "tool": "impacket-ticketer",
                        "status": STATUS_ERROR,
                        "target": target,
                        "error": error_msg,
                        "summary": f"Ticket creation failed: {error_msg}",
                    }

            # Success if ccache was created
            if ccache_path:
                # Create finding
                if findings_manager:
                    try:
                        # Get host ID
                        host_id = None
                        ip_match = re.search(r"(\d+\.\d+\.\d+\.\d+)", target)
                        if ip_match and host_manager:
                            host = host_manager.get_host_by_ip(
                                engagement_id, ip_match.group(1)
                            )
                            if host:
                                host_id = host["id"]

                        findings_manager.add_finding(
                            engagement_id=engagement_id,
                            host_id=host_id,
                            finding_type="silver_ticket",
                            severity="critical",
                            title=f"Silver Ticket Created - Impersonating {impersonated_user}",
                            description=(
                                f"Successfully forged a Silver Ticket for service {target_spn} "
                                f"impersonating {impersonated_user}. This ticket can be used to "
                                f"access the service without knowing {impersonated_user}'s password."
                            ),
                            evidence=f"Ticket saved to: {ccache_path}",
                            tool="impacket-ticketer",
                        )
                    except Exception as e:
                        logger.warning(f"Could not create Silver Ticket finding: {e}")

                return {
                    "tool": "impacket-ticketer",
                    "status": STATUS_DONE,
                    "target": target,
                    "ccache_path": ccache_path,
                    "impersonated_user": impersonated_user,
                    "target_spn": target_spn,
                    "domain": domain,
                    "summary": f"Silver Ticket created: {impersonated_user} for {target_spn}",
                }

            if tool_error:
                return {
                    "tool": self.tool_name,
                    "status": STATUS_ERROR,
                    "target": target,
                    "error": tool_error,
                    "summary": f"Tool error: {tool_error}",
                }

            return {
                "tool": "impacket-ticketer",
                "status": STATUS_NO_RESULTS,
                "target": target,
                "summary": "No ticket created",
            }

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

    def display_done(
        self,
        job: Dict[str, Any],
        log_path: str,
        show_all: bool = False,
        show_passwords: bool = False,
    ) -> None:
        """Display successful ticket creation."""
        click.echo()
        click.echo(click.style("=" * 70, fg="green"))
        click.echo(click.style("SILVER TICKET CREATED!", fg="green", bold=True))
        click.echo(click.style("=" * 70, fg="green"))
        click.echo()

        parse_result = job.get("parse_result", {})
        ccache_path = parse_result.get("ccache_path", "")
        impersonated_user = parse_result.get("impersonated_user", "")
        target_spn = parse_result.get("target_spn", "")

        if impersonated_user:
            click.echo(
                f"  Impersonating: {click.style(impersonated_user, fg='cyan', bold=True)}"
            )
        if target_spn:
            click.echo(f"  Target SPN: {click.style(target_spn, fg='yellow')}")
        if ccache_path:
            click.echo(f"  Ticket Path: {click.style(ccache_path, fg='green')}")

        click.echo()
        click.echo(click.style("  Use with:", bold=True))
        click.echo(f"    export KRB5CCNAME={ccache_path}")
        click.echo("    impacket-mssqlclient -k -no-pass <hostname>")
        click.echo()
        click.echo(click.style("=" * 70, fg="green"))
        click.echo()

    def display_warning(
        self,
        job: Dict[str, Any],
        log_path: str,
        log_content: Optional[str] = None,
    ) -> None:
        """Display warning."""
        click.echo()
        click.echo(click.style("=" * 70, fg="yellow"))
        click.echo(click.style("TICKETER - WARNING", fg="yellow", bold=True))
        click.echo(click.style("=" * 70, fg="yellow"))
        click.echo()
        click.echo("  Ticket creation completed with warnings.")
        click.echo("  Press [r] to view raw logs for details.")
        click.echo()
        click.echo(click.style("=" * 70, fg="yellow"))
        click.echo()

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

        parse_result = job.get("parse_result", {})
        error = parse_result.get("error", "Unknown error")
        click.echo(f"  Error: {error}")
        click.echo()
        click.echo("  Common issues:")
        click.echo("    - Invalid domain SID")
        click.echo("    - Incorrect NTLM hash")
        click.echo("    - SPN format mismatch")
        click.echo()
        click.echo(click.style("=" * 70, fg="red"))
        click.echo()

    def display_no_results(
        self,
        job: Dict[str, Any],
        log_path: str,
        show_all: bool = False,
    ) -> None:
        """Display no ticket created."""
        click.echo()
        click.echo(click.style("=" * 70, fg="yellow"))
        click.echo(click.style("NO TICKET CREATED", fg="yellow", bold=True))
        click.echo(click.style("=" * 70, fg="yellow"))
        click.echo()
        click.echo("  Ticketer did not produce a .ccache file.")
        click.echo("  Check the raw logs for details.")
        click.echo()
        click.echo(click.style("=" * 70, fg="yellow"))
        click.echo()

    def generate_next_steps(
        self,
        job: Dict[str, Any],
        context: Any,
    ) -> list:
        """Generate SMART next steps based on ticket creation."""
        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", "")
        ccache_path = parse_result.get("ccache_path", "")
        target_spn = parse_result.get("target_spn", "")
        impersonated_user = parse_result.get("impersonated_user", "")

        if ccache_path:
            # MSSQL access if SQL SPN
            if "MSSQLSvc" in target_spn:
                # Extract hostname from SPN (MSSQLSvc/hostname:port)
                hostname = ""
                if "/" in target_spn:
                    hostname = target_spn.split("/")[1].split(":")[0]

                steps.append(
                    create_next_step(
                        title=f"Access MSSQL as {impersonated_user}",
                        reason="Silver Ticket created - use Kerberos auth for MSSQL",
                        commands=[
                            f"export KRB5CCNAME={ccache_path}",
                            f"impacket-mssqlclient -k -no-pass {hostname}",
                        ],
                        priority=1,
                        category="access",
                        target=target,
                        tool="impacket-mssqlclient",
                    )
                )

            # Generic service access
            steps.append(
                create_next_step(
                    title="Use Silver Ticket for service access",
                    reason="Forged ticket can impersonate any user",
                    commands=[
                        f"export KRB5CCNAME={ccache_path}",
                        "# Access the target service with Kerberos auth (-k -no-pass)",
                    ],
                    priority=2,
                    category="access",
                    target=target,
                    tool="manual",
                )
            )

        return steps[:5]


# Register handler
handler = ImpacketTicketerHandler()
