"""
spidercrypt.cli
~~~~~~~~~~~~~~~
Click-based command-line interface.
"""

import json
import sys
from pathlib import Path

import click

from . import __version__
from .crypto    import gen_key as _gen_key, encrypt_file, decrypt_file
from .audit     import audit_file
from .firewall  import check_injection, check_file
from .pii       import mask_file
from .poisoning import analyze_file
from .db        import save_report, get_history

BANNER = r"""
  ____  ____  ___ ____  _____ ____   ____ ______     ______  _____
 / ___||  _ \|_ _|  _ \| ____|  _ \ / ___|  _ \ \   / /  _ \|_   _|
 \___ \| |_) || || | | |  _| | |_) | |   | |_) \ \ / /| |_) | | |
  ___) |  __/ | || |_| | |___|  _ <| |___|  _ < \ V / |  __/  | |
 |____/|_|   |___|____/|_____|_| \_\\____|_| \_\ \_/  |_|     |_|

      >>> SECURITY & CRYPTOGRAPHY TOOLSET v{version} <<<
""".format(version=__version__)


@click.group(invoke_without_command=True)
@click.option("--verbose", is_flag=True)
@click.version_option(__version__, prog_name="spidercrypt")
@click.pass_context
def cli(ctx, verbose):
    """SpiderCrypt CLI — Cybersecurity toolset."""
    click.secho(BANNER, fg="cyan", bold=True)
    ctx.ensure_object(dict)
    ctx.obj["verbose"] = verbose
    if ctx.invoked_subcommand is None:
        click.echo(ctx.get_help())


# ── gen-key ───────────────────────────────────────────────────────────────────

@cli.command(name="gen-key")
def cmd_gen_key():
    """Generate a base64-encoded AES-256 key."""
    click.echo(_gen_key())


# ── encrypt ───────────────────────────────────────────────────────────────────

@cli.command(name="encrypt")
@click.option("--key", required=True, envvar="SPIDER_KEY")
@click.argument("infile",  type=click.Path(exists=True))
@click.argument("outfile", type=click.Path())
def cmd_encrypt(key, infile, outfile):
    """Encrypt a file using AES-256-GCM."""
    encrypt_file(key, infile, outfile)
    click.secho(f"✅ File encrypted: {outfile}", fg="green")


# ── decrypt ───────────────────────────────────────────────────────────────────

@cli.command(name="decrypt")
@click.option("--key", required=True, envvar="SPIDER_KEY")
@click.argument("infile",  type=click.Path(exists=True))
@click.argument("outfile", type=click.Path())
def cmd_decrypt(key, infile, outfile):
    """Decrypt a file protected with AES-256-GCM."""
    try:
        decrypt_file(key, infile, outfile)
        click.secho(f"🔓 Decryption successful: {outfile}", fg="cyan")
    except Exception:
        click.secho("❌ Failed: incorrect key or corrupted file.", fg="red", bold=True)
        sys.exit(1)


# ── audit ─────────────────────────────────────────────────────────────────────

@cli.command(name="audit")
@click.argument("file", type=click.Path(exists=True))
@click.option("--ai", is_flag=True, help="Enable AI-assisted analysis.")
def cmd_audit(file, ai):
    """Security scan (regex + optional AI)."""
    report = audit_file(file, ai=ai)
    click.secho(f"--- Static analysis: {file} ---", bold=True)
    click.echo(f"Secrets found  : {report['secrets_found']}")
    click.echo(f"Risky patterns : {len(report['risky_code'])}")
    if "ai_analysis" in report:
        click.secho("\nAI Analysis:", bold=True)
        click.echo(report["ai_analysis"])
    save_report(file, report)


# ── firewall ──────────────────────────────────────────────────────────────────

@cli.command(name="firewall")
@click.argument("input_data")
@click.option("--file", "from_file", is_flag=True, help="Read input from a file.")
def cmd_firewall(input_data, from_file):
    """Check for prompt-injection attempts (text or file)."""
    result = check_file(input_data) if from_file else check_injection(input_data)
    click.echo(json.dumps(result, indent=2))


# ── ghost-pii ─────────────────────────────────────────────────────────────────

@cli.command(name="ghost-pii")
@click.argument("infile", type=click.Path(exists=True))
@click.option("--output", type=click.Path(), help="Write redacted text to this file.")
def cmd_ghost_pii(infile, output):
    """Mask personally identifiable information in a text file."""
    redacted = mask_file(infile, outfile=output)
    if not output:
        click.echo(redacted)
    else:
        click.secho(f"✅ Redacted file written: {output}", fg="green")


# ── history ───────────────────────────────────────────────────────────────────

@cli.command(name="history")
def cmd_history():
    """Display the audit log history."""
    logs = get_history()
    if not logs:
        click.echo("No audit records found.")
        return
    for log in logs:
        click.echo(f"{log['timestamp']}  |  {log['filename']}")


# ── detect-poison ─────────────────────────────────────────────────────────────

@cli.command(name="detect-poison")
@click.argument("files", nargs=-1, type=click.Path(exists=True), required=True)
@click.option("--json-output", is_flag=True, help="Print full report as JSON.")
@click.option("--save",        is_flag=True, help="Save results to the audit database.")
@click.option("--threshold",   default=0.40, show_default=True,
              help="Risk score threshold above which a file is marked 'poisoned'.")
def cmd_detect_poison(files, json_output, save, threshold):
    """
    Analyse one or more JSON files for ML data poisoning.

    \b
    Heuristics applied:
      • Backdoor triggers in text values
      • Suspicious JSON keys
      • Abnormal label distribution (dominance, entropy)
      • Code / vulnerability label consistency
      • Label-flip attacks (same input, contradictory labels)
      • High ratio of exact duplicates

    \b
    Examples:
      spidercrypt detect-poison dataset.json
      spidercrypt detect-poison *.json --json-output --save
      spidercrypt detect-poison data.json --threshold 0.35
    """
    all_reports = []

    for filepath in files:
        if not filepath.lower().endswith(".json"):
            click.secho(f"⏭  {filepath} skipped (not a .json)", fg="yellow")
            continue

        click.secho(f"\n🔍 Analysing: {filepath}", bold=True)
        report = analyze_file(filepath, threshold=threshold)

        if json_output:
            click.echo(json.dumps(report, indent=2, ensure_ascii=False))
        else:
            _print_poison_report(report)

        if save:
            save_report(filepath, report)
            click.secho("  💾 Report saved to database.", fg="blue")

        all_reports.append(report)

    if len(all_reports) > 1:
        poisoned = [r for r in all_reports if r["poisoning_detected"]]
        clean    = [r for r in all_reports if not r["poisoning_detected"]]
        click.secho("\n" + "═" * 55, bold=True)
        click.secho(f"  SUMMARY  |  {len(all_reports)} files analysed", bold=True)
        click.secho("═" * 55, bold=True)
        click.secho(f"  🚨 Poisoned : {len(poisoned)}", fg="red"   if poisoned else "white")
        click.secho(f"  ✅ Clean    : {len(clean)}",    fg="green"  if clean    else "white")
        for r in poisoned:
            click.secho(f"     ↳ {r['file']}  (score={r['risk_score']})", fg="red")


# ── Report renderer ───────────────────────────────────────────────────────────

def _print_poison_report(report: dict) -> None:
    detected = report["poisoning_detected"]
    score    = report["risk_score"]
    warnings = report["warnings"]
    details  = report["details"]

    if detected:
        click.secho("  ┌─────────────────────────────────────────┐", fg="red",   bold=True)
        click.secho("  │  🚨  DATA POISONING DETECTED              │", fg="red",   bold=True)
        click.secho("  └─────────────────────────────────────────┘", fg="red",   bold=True)
    else:
        click.secho("  ┌─────────────────────────────────────────┐", fg="green", bold=True)
        click.secho("  │  ✅  Clean file — no suspicious signs     │", fg="green", bold=True)
        click.secho("  └─────────────────────────────────────────┘", fg="green", bold=True)

    bar_len = 30
    filled  = int(score * bar_len)
    color   = "red" if score >= 0.5 else ("yellow" if score >= 0.25 else "green")
    bar     = "█" * filled + "░" * (bar_len - filled)
    click.secho(f"\n  Risk Score : [{bar}] {score:.3f}", fg=color, bold=True)

    if details.get("total_records"):
        click.echo(f"  Records analysed: {details['total_records']}")
    if "label_fields" in details:
        for field, info in details["label_fields"].items():
            click.echo(f"  Field '{field}': {info['distribution']}  "
                       f"entropy={info['entropy']}  dominance={info['dominance'] * 100:.1f}%")
    if "insecure_code_density" in details:
        density = details["insecure_code_density"]
        col = "red" if density > 0.05 else "green"
        click.secho(f"  Insecure code density: {density * 100:.1f}%", fg=col)

    if warnings:
        click.secho("\n  ⚠️  Warnings:", bold=True)
        for w in warnings:
            click.secho(f"    • {w}", fg="yellow")

    if "triggered_samples" in details:
        click.secho("\n  🎯 Suspicious excerpts:", bold=True)
        for s in details["triggered_samples"]:
            click.secho(f'    "{s[:100]}"', fg="magenta")

    if "insecure_code_samples" in details:
        click.secho("\n  🔓 Insecure code labeled 'safe':", bold=True)
        for s in details["insecure_code_samples"]:
            click.secho(f"    [{s['desc']}]",             fg="red")
            click.secho(f"    {s['code_snippet'][:100]}", fg="magenta")

    if "secure_code_mislabeled" in details:
        click.secho("\n  🔒 Secure code labeled 'vulnerable':", bold=True)
        for s in details["secure_code_mislabeled"]:
            click.secho(f"    [{s['desc']}]",             fg="red")
            click.secho(f"    {s['code_snippet'][:100]}", fg="magenta")

    if "conflicting_samples" in details:
        click.secho("\n  🔀 Entries with contradictory labels:", bold=True)
        for s in details["conflicting_samples"]:
            try:
                parsed = json.loads(s)
                click.secho(f"    {json.dumps(parsed, ensure_ascii=False)[:120]}", fg="magenta")
            except Exception:
                click.secho(f"    {str(s)[:120]}", fg="magenta")

    click.echo()