"""
Rivet CLI — commercial wrapper around the open-source scanner core.

Responsibilities of this file (only):
  1. CLI definition (commands, options, flags)
  2. Delegating license checks to app.license_guard (obfuscated in production)
  3. Delegating scan work to app.scanner (open-source core)
  4. Exit codes and output

License enforcement lives in app/license_guard.py (Cython-compiled in production).
Everything scan-related lives in app/scanner/ (open-sourceable, stays readable).
"""

import logging
import sys

# Ensure stdout/stderr use UTF-8 on Windows (default cp1252 breaks rule messages
# that contain unicode characters such as → or •)
if sys.platform == "win32":
    import io
    sys.stdout = io.TextIOWrapper(sys.stdout.buffer, encoding="utf-8", errors="replace")
    sys.stderr = io.TextIOWrapper(sys.stderr.buffer, encoding="utf-8", errors="replace")
from pathlib import Path
from typing import Optional

import click
from dotenv import load_dotenv

# Load .env (development) — Electron passes env vars directly in production
for _candidate in [
    Path(__file__).resolve().parent.parent.parent / ".env",
    Path(__file__).resolve().parent.parent / ".env",
]:
    if _candidate.exists():
        load_dotenv(_candidate)
        break


# ---------------------------------------------------------------------------
# CLI
# ---------------------------------------------------------------------------

try:
    from app._version import __version__ as _CLI_VERSION
except ImportError:
    _CLI_VERSION = "1.0.6"  # fallback during development


@click.group(context_settings={"help_option_names": ["-h", "--help"]})
@click.version_option(version=_CLI_VERSION, prog_name="rivet")
def cli():
    """Rivet — Post-Quantum Security Scanner

    Scan source code for quantum-vulnerable cryptography.
    Designed for CI/CD pipeline integration.

    \b
    Examples:
      rivet scan ./src
      rivet scan ./src --mode pqc
      rivet scan ./src --exclude tests --exclude vendor
      rivet scan ./src --format csv  -o results.csv
      rivet scan ./src --format json -o results.json   # Enterprise
      RIVET_LICENSE_KEY=<key> rivet scan ./src
    """


@cli.command()
@click.argument("target", type=click.Path(exists=True, resolve_path=True))
@click.option(
    "--format", "output_format",
    default="text",
    type=click.Choice(["text", "csv", "json", "sarif"]),
    show_default=True,
    help="Output format: text (default), csv, json, sarif.",
)
@click.option(
    "--output", "-o", "output_file",
    default=None, metavar="FILE",
    help="Write output to FILE instead of stdout.",
)
@click.option(
    "--mode",
    default="all",
    type=click.Choice(["all", "pqc"]),
    show_default=True,
    help="'all' = all security rules; 'pqc' = quantum/crypto rules only.",
)
@click.option(
    "--config", "scanner_config",
    default=None, metavar="CONFIG",
    help="Custom opengrep config (path or registry ID like p/python).",
)
@click.option("--exclude", multiple=True, metavar="DIR",
              help="Directory to exclude. Repeatable.")
@click.option("--ext", multiple=True, metavar="EXT",
              help="File extension to include (e.g. py js). Repeatable.")
@click.option("--no-fail", is_flag=True, default=False,
              help="Exit 0 even when findings exist.")
@click.option("--timeout", default=600, show_default=True, metavar="SECONDS")
@click.option("--verbose", "-v", is_flag=True, default=False)
@click.option(
    "--skip-license", is_flag=True, default=False,
    envvar="RIVET_SKIP_LICENSE", hidden=True,
    help="Skip license validation (development only).",
)
def scan(
    target: str,
    output_format: str,
    output_file: Optional[str],
    mode: str,
    scanner_config: Optional[str],
    exclude: tuple,
    ext: tuple,
    no_fail: bool,
    timeout: int,
    verbose: bool,
    skip_license: bool,
):
    """Scan TARGET for post-quantum security vulnerabilities.

    Exit codes: 0 = clean, 1 = findings found, 2 = error/license failure.
    """
    logging.basicConfig(
        level=logging.DEBUG if verbose else logging.WARNING,
        format="%(levelname)s: %(message)s",
        stream=sys.stderr,
    )

    # 1. License check (obfuscated in production — see app/license_guard.py)
    from app.license_guard import check_license
    license_result = check_license(skip_license)

    # 3. Delegate to open-source scanner core
    from app.code_analyzer.engine import build_rules, run_scan, format_text, format_csv, format_json, count_sarif_findings

    click.echo(f"Scanning: {target}", err=True)

    rules = build_rules(target, mode, scanner_config)

    if verbose:
        click.echo(f"Rules: {rules}", err=True)

    try:
        result = run_scan(
            target=target,
            rules=rules,
            output_format=output_format,
            exclude=exclude,
            extensions=ext,
            timeout=timeout,
        )
    except FileNotFoundError:
        click.echo(
            "ERROR: opengrep not found. Download from https://github.com/opengrep/opengrep/releases",
            err=True,
        )
        sys.exit(2)
    except TimeoutError:
        click.echo(f"ERROR: Scan timed out after {timeout} seconds.", err=True)
        sys.exit(1)
    except RuntimeError as exc:
        click.echo(f"ERROR: {exc}", err=True)
        sys.exit(1)

    # 4. Output
    if output_format == "sarif":
        finding_count = count_sarif_findings(result.raw_sarif)
        content = result.raw_sarif
    elif output_format == "json":
        finding_count = result.finding_count
        content = format_json(result)
    elif output_format == "csv":
        finding_count = result.finding_count
        content = format_csv(result)
    else:
        finding_count = result.finding_count
        content = format_text(result, target)

    if output_file:
        Path(output_file).write_text(content, encoding="utf-8")
        click.echo(f"Output written to: {output_file}", err=True)
    else:
        click.echo(content)

    click.echo(f"Done. {finding_count} finding(s).", err=True)

    if finding_count > 0 and not no_fail:
        sys.exit(1)


@cli.command()
@click.option("--out", "-o", "output_file", default=None, metavar="FILE",
              help="Write fingerprint JSON to FILE instead of stdout.")
def fingerprint(output_file: str):
    """Print this machine's device fingerprint.

    Send the output file to support when requesting an air-gapped offline license.

    \b
    Examples:
      rivet fingerprint
      rivet fingerprint -o fingerprint.json
    """
    import json
    import platform
    import socket
    from app.services.device_fingerprint import DeviceFingerprintGenerator

    fp = DeviceFingerprintGenerator.generate()
    metadata = DeviceFingerprintGenerator.generate_metadata()

    payload = {
        "device_fingerprint": fp,
        "hostname": socket.gethostname(),
        "platform": platform.system(),
        "platform_version": platform.version(),
        "architecture": platform.machine(),
        "metadata": metadata,
    }

    content = json.dumps(payload, indent=2)

    if output_file:
        Path(output_file).write_text(content, encoding="utf-8")
        click.echo(f"Fingerprint written to: {output_file}")
        click.echo(f"Send this file to support@cerebion.com to request an offline license.")
    else:
        click.echo(content)


@cli.command("mcp-server")
def mcp_server():
    """Run the Rivet MCP server (stdio) for local AI agent integrations.

    Requires an Enterprise license. Serves scan history and quantum risk
    data to any MCP-compatible client (Claude Desktop, Claude Code, etc.)
    running on this machine — no network listener, stdio only.

    \b
    Example Claude Desktop config entry:
      "cerebion-rivet": { "command": "rivet", "args": ["mcp-server"] }
    """
    from app.mcp_server.server import main as run_mcp_server
    run_mcp_server()


def main():
    cli()


if __name__ == "__main__":
    main()
