#!/usr/bin/env python3
"""
██╗   ██╗ █████╗ ██╗   ██╗██╗  ████████╗██╗  ██╗ ██████╗ ██╗   ██╗███╗   ██╗██████╗
██║   ██║██╔══██╗██║   ██║██║  ╚══██╔══╝██║  ██║██╔═══██╗██║   ██║████╗  ██║██╔══██╗
██║   ██║███████║██║   ██║██║     ██║   ███████║██║   ██║██║   ██║██╔██╗ ██║██║  ██║
╚██╗ ██╔╝██╔══██║██║   ██║██║     ██║   ██╔══██║██║   ██║██║   ██║██║╚██╗██║██║  ██║
 ╚████╔╝ ██║  ██║╚██████╔╝███████╗██║   ██║  ██║╚██████╔╝╚██████╔╝██║ ╚████║██████╔╝
  ╚═══╝  ╚═╝  ╚═╝ ╚═════╝ ╚══════╝╚═╝   ╚═╝  ╚═╝ ╚═════╝  ╚═════╝ ╚═╝  ╚═══╝╚═════╝

VaultHound v1.0.0 — Secret & Credential Scanner
  Hunts API keys, passwords, tokens, and secrets in URLs, directories, and git history.
  Part of the ExploitCraft toolkit — pairs with ReconNinja.

  ⚠  Use ONLY against targets you own or have explicit written permission to test.
"""

from __future__ import annotations

import argparse
import signal
import sys
from pathlib import Path

sys.path.insert(0, str(Path(__file__).parent))

try:
    from rich.panel import Panel
    from rich.prompt import Confirm, Prompt
    from rich.rule import Rule
except ImportError:
    print("ERROR: 'rich' library required.  pip install rich", file=sys.stderr)
    sys.exit(1)

from utils.logger import console, log
from utils.models import ScanConfig, ScanMode, ScanResult
from core.orchestrator import orchestrate

APP_NAME = "VaultHound"
VERSION  = "1.0.0"


# ── CLI ───────────────────────────────────────────────────────────────────────

def parse_args() -> argparse.Namespace | None:
    parser = argparse.ArgumentParser(
        prog="vaulthound",
        description=f"{APP_NAME} v{VERSION} — Secret & Credential Scanner",
        formatter_class=argparse.RawDescriptionHelpFormatter,
        epilog=(
            "Examples:\n"
            "  vaulthound -u https://example.com          # scan live URL\n"
            "  vaulthound -d /path/to/repo                # scan local directory\n"
            "  vaulthound -d /path/to/repo --git          # include git history\n"
            "  vaulthound -u https://example.com --js     # deep JS scanning\n"
            "  vaulthound -d . --output results/          # custom output dir\n"
        ),
    )

    # Target
    group = parser.add_mutually_exclusive_group(required=False)
    group.add_argument("--url",  "-u", help="Target URL to scan (fetches HTML + JS)")
    group.add_argument("--dir",  "-d", help="Local directory to scan recursively")

    # Scan options
    parser.add_argument("--git",        action="store_true", help="Scan git history (commits)")
    parser.add_argument("--js",         action="store_true", help="Deep JS file scanning (fetch all linked scripts)")
    parser.add_argument("--depth",      type=int, default=3, help="URL crawl depth (default: 3)")
    parser.add_argument("--threads",    type=int, default=10, help="Worker threads (default: 10)")
    parser.add_argument("--timeout",    type=int, default=10, help="HTTP timeout seconds (default: 10)")
    parser.add_argument("--severity",   choices=["critical","high","medium","low","info"],
                        default="low", help="Minimum severity to report (default: low)")
    parser.add_argument("--patterns",   default=None, help="Path to custom patterns YAML file")
    parser.add_argument("--output",     "-o", default="vaulthound_reports", help="Output directory")
    parser.add_argument("--no-report",  action="store_true", help="Skip HTML report generation")
    parser.add_argument("--yes",        "-y", action="store_true", help="Skip confirmation")
    parser.add_argument("--version",    action="store_true", help="Show version and exit")

    if len(sys.argv) == 1:
        return None
    return parser.parse_args()


def build_config(args: argparse.Namespace) -> ScanConfig | None:
    if args.version:
        console.print(f"[green]{APP_NAME} v{VERSION}[/]")
        return None

    if not args.url and not args.dir:
        console.print("[danger]Provide --url or --dir[/]")
        return None

    target = args.url or args.dir
    mode   = ScanMode.URL if args.url else ScanMode.DIR

    if not args.yes:
        if not Confirm.ask(
            f"[danger]⚠  Confirm you own or have written permission to scan: {target}?[/]",
            default=False,
        ):
            console.print("[danger]Aborted.[/]")
            return None

    return ScanConfig(
        target        = target,
        mode          = mode,
        scan_git      = args.git,
        scan_js       = args.js,
        crawl_depth   = args.depth,
        threads       = args.threads,
        timeout       = args.timeout,
        min_severity  = args.severity,
        patterns_file = args.patterns,
        output_dir    = args.output,
        no_report     = args.no_report,
    )


def build_config_interactive() -> ScanConfig | None:
    console.print(Panel.fit(
        f"[bold green]{APP_NAME} v{VERSION}[/]\n"
        "[dim]Secret & Credential Scanner — authorized use only[/]",
        border_style="green",
    ))

    console.print(Rule("[dim]Scan Mode[/]"))
    console.print("""
  [1] URL  — fetch and scan a live target (HTML, JS, responses)
  [2] DIR  — scan a local directory or cloned repository
  [0] Exit
""")
    choice = Prompt.ask("Choice", choices=["0","1","2"], default="1")
    if choice == "0":
        return None

    mode = ScanMode.URL if choice == "1" else ScanMode.DIR
    prompt = "Target URL" if mode == ScanMode.URL else "Directory path"
    target = Prompt.ask(f"\n[bold]{prompt}[/]").strip()
    if not target:
        console.print("[danger]No target provided.[/]")
        return None

    if not Confirm.ask(
        f"\n[danger bold]⚠  Confirm written permission to scan {target}?[/]",
        default=False,
    ):
        console.print("[danger]Aborted.[/]")
        return None

    scan_git = False
    scan_js  = False

    if mode == ScanMode.DIR:
        scan_git = Confirm.ask("Scan git history (commits)?", default=False)
    if mode == ScanMode.URL:
        scan_js = Confirm.ask("Deep JS scanning (fetch all linked scripts)?", default=True)

    min_sev = Prompt.ask(
        "Minimum severity to report",
        choices=["critical","high","medium","low","info"],
        default="low",
    )

    return ScanConfig(
        target       = target,
        mode         = mode,
        scan_git     = scan_git,
        scan_js      = scan_js,
        min_severity = min_sev,
    )


# ── Entry point ───────────────────────────────────────────────────────────────

def main() -> None:
    def _sigint(sig, frame):
        console.print("\n[danger]Interrupted — partial results may exist in output directory[/]")
        sys.exit(0)
    signal.signal(signal.SIGINT, _sigint)

    console.print(Panel.fit(
        f"[bold cyan]{APP_NAME}[/] [dim]v{VERSION}[/]\n"
        "[dim]Secret & Credential Scanner by ExploitCraft[/]",
        border_style="cyan",
    ))

    args = parse_args()
    if args is None:
        cfg = build_config_interactive()
    else:
        cfg = build_config(args)

    if cfg is None:
        return

    orchestrate(cfg)


if __name__ == "__main__":
    main()
