"""
CrashSense CLI — AI Agent for DeFi Transaction Failure Diagnosis.

Provides Click-based commands for diagnosing failed/reverted blockchain
transactions, managing configuration, and launching the interactive TUI.
"""

from __future__ import annotations

import sys
from pathlib import Path

import click
from rich.console import Console
from rich.panel import Panel
from rich.table import Table
from rich.prompt import Prompt

from crashsense import __version__, __app_name__
from crashsense.config import (
    load_config,
    save_config,
    get_api_key,
    get_chain_info,
    SUPPORTED_CHAINS,
    CONFIG_PATH,
    DEFAULT_CONFIG,
)
from crashsense.utils import is_valid_tx_hash, normalise_tx_hash

console = Console()


# ── Main group ────────────────────────────────────────────────────────────

@click.group()
@click.version_option(__version__, prog_name=__app_name__)
def main():
    """CrashSense — AI Agent for DeFi Transaction Failure Diagnosis."""
    pass


# ── diagnose ──────────────────────────────────────────────────────────────

@main.command()
@click.argument("tx_hash")
@click.option("--chain", default=None, help="Blockchain to query (e.g. ethereum, polygon).")
@click.option("--provider", default=None, help="LLM provider (gemini/openai/claude/ollama).")
@click.option("--auto-apply", is_flag=True, default=False, help="Auto-apply fixes without asking.")
def diagnose(tx_hash: str, chain: str | None, provider: str | None, auto_apply: bool):
    """Diagnose a failed / reverted blockchain transaction."""
    if not is_valid_tx_hash(tx_hash):
        console.print("[red]Invalid transaction hash.[/red] Expected 0x + 64 hex characters.")
        raise SystemExit(1)

    tx_hash = normalise_tx_hash(tx_hash)

    from crashsense.core.tx_analyzer import TxAnalyzer
    from crashsense.core.remediation import RemediationEngine
    from crashsense.core.memory import MemoryStore

    cfg = load_config()
    if chain:
        cfg["chain"]["name"] = chain
        save_config(cfg)
    if provider:
        cfg["provider"] = provider

    analyzer = TxAnalyzer(
        provider=cfg.get("provider", "gemini"),
        chain=cfg.get("chain", {}).get("name", "ethereum"),
    )

    with console.status("[cyan]Fetching on-chain data & diagnosing…[/cyan]"):
        result = analyzer.diagnose(tx_hash)

    _display_diagnosis(result, auto_apply)

    # Save to memory (full history)
    try:
        from crashsense.core.remediation import RemediationEngine

        mem = MemoryStore()
        analysis = result.get("analysis", {})
        ctx = result.get("tx_context", {})
        patterns = result.get("patterns_matched", [])

        engine = RemediationEngine()
        remediation = engine.get_remediation(patterns)

        rec = mem.save_diagnosis(
            tx_hash=tx_hash,
            chain=ctx.get("chain", cfg.get("chain", {}).get("name", "ethereum")),
            summary=analysis.get("explanation", ""),
            root_cause=analysis.get("root_cause", ""),
            suggested_fix=analysis.get("suggested_fix", ""),
            explanation=analysis.get("explanation", ""),
            patterns=patterns,
            remediation=remediation,
            provider=cfg.get("provider", "gemini"),
            status=ctx.get("status", ""),
            function_called=ctx.get("function_called", ""),
            from_address=ctx.get("from", ""),
            to_address=ctx.get("to", ""),
            value_eth=ctx.get("value_eth", 0.0),
            gas_used=ctx.get("gas_used", 0),
            gas_limit=ctx.get("gas_limit", 0),
            revert_reason=ctx.get("revert_reason", ""),
            tx_context=ctx,
            analysis=analysis,
        )
        # Log AI actions
        if patterns:
            mem.log_action(rec.id, "pattern_match", f"Matched {len(patterns)} patterns", {"patterns": patterns})
        mem.log_action(rec.id, "llm_call", f"Provider: {cfg.get('provider', 'gemini')}", {"analysis": analysis})
        if remediation:
            mem.log_action(rec.id, "remediation", f"{len(remediation)} fixes suggested", {"remediation": remediation})
    except Exception:
        pass


# ── diagnose-raw ──────────────────────────────────────────────────────────

@main.command("diagnose-raw")
@click.option("--provider", default=None, help="LLM provider.")
def diagnose_raw(provider: str | None):
    """Paste an error message / revert string for analysis."""
    console.print("[cyan]Paste the error message below (press Enter twice to submit):[/cyan]")
    lines: list[str] = []
    while True:
        line = input()
        if line == "":
            break
        lines.append(line)
    error_text = "\n".join(lines).strip()
    if not error_text:
        console.print("[yellow]No input provided.[/yellow]")
        return

    from crashsense.core.tx_analyzer import TxAnalyzer
    from crashsense.core.remediation import RemediationEngine

    cfg = load_config()
    prov = provider or cfg.get("provider", "gemini")
    analyzer = TxAnalyzer(provider=prov)

    with console.status("[cyan]Analyzing error text…[/cyan]"):
        result = analyzer.diagnose_raw(error_text)

    _display_diagnosis(result, auto_apply=False)


# ── init ──────────────────────────────────────────────────────────────────

@main.command()
def init():
    """Interactive setup wizard for CrashSense."""
    console.print(Panel("[bold cyan]CrashSense Setup Wizard[/bold cyan]", expand=False))

    cfg = load_config()

    # Provider
    provider = Prompt.ask(
        "LLM provider",
        choices=["gemini", "openai", "claude", "ollama"],
        default=cfg.get("provider", "gemini"),
    )
    cfg["provider"] = provider

    # API key hint
    if provider != "ollama":
        key_env = {
            "gemini": "GEMINI_API_KEY",
            "openai": "OPENAI_API_KEY",
            "claude": "ANTHROPIC_API_KEY",
        }.get(provider, "")
        current = get_api_key(provider)
        if current:
            console.print(f"[green]✓[/green] {key_env} is already set.")
        else:
            console.print(f"[yellow]Set {key_env} in your .env file or environment.[/yellow]")

    # Chain
    chain = Prompt.ask(
        "Default chain",
        choices=list(SUPPORTED_CHAINS.keys()),
        default=cfg.get("chain", {}).get("name", "ethereum"),
    )
    cfg.setdefault("chain", {})["name"] = chain

    # Auto-apply
    auto = Prompt.ask("Auto-apply fixes?", choices=["yes", "no"], default="no")
    cfg.setdefault("behaviour", {})["auto_apply_fixes"] = auto == "yes"

    save_config(cfg)
    console.print(f"[green]✓ Config saved to {CONFIG_PATH}[/green]")


# ── provider ──────────────────────────────────────────────────────────────

@main.group()
def provider():
    """Manage LLM providers."""
    pass


@provider.command("list")
def provider_list():
    """List available LLM providers."""
    from crashsense.core.llm_adapter import LLMAdapter

    cfg = load_config()
    adapter = LLMAdapter(provider=cfg.get("provider", "gemini"))
    providers = adapter.list_providers()

    table = Table(title="LLM Providers")
    table.add_column("Provider", style="cyan")
    table.add_column("Model", style="green")
    table.add_column("Active", style="yellow")
    table.add_column("API Key", style="dim")

    for p in providers:
        active = "✓" if p["active"] else ""
        table.add_row(p["name"], p["model"], active, p["key_status"])

    console.print(table)


@provider.command("set")
@click.argument("name")
def provider_set(name: str):
    """Switch the active LLM provider."""
    valid = {"gemini", "openai", "claude", "ollama"}
    if name not in valid:
        console.print(f"[red]Unknown provider:[/red] {name}. Choose from {valid}")
        return
    cfg = load_config()
    cfg["provider"] = name
    save_config(cfg)
    console.print(f"[green]✓ Provider set to {name}[/green]")


# ── chain ─────────────────────────────────────────────────────────────────

@main.group()
def chain():
    """Manage blockchain networks."""
    pass


@chain.command("list")
def chain_list():
    """List supported chains."""
    cfg = load_config()
    active_chain = cfg.get("chain", {}).get("name", "ethereum")

    table = Table(title="Supported Chains")
    table.add_column("Chain", style="cyan")
    table.add_column("Chain ID")
    table.add_column("Currency")
    table.add_column("Active", style="yellow")

    for name, info in SUPPORTED_CHAINS.items():
        active = "✓" if name == active_chain else ""
        table.add_row(name, str(info["chain_id"]), info["currency"], active)

    console.print(table)


@chain.command("set")
@click.argument("name")
def chain_set(name: str):
    """Switch the active blockchain network."""
    if name not in SUPPORTED_CHAINS:
        console.print(f"[red]Unknown chain:[/red] {name}. Use 'crashsense chain list'.")
        return
    cfg = load_config()
    cfg.setdefault("chain", {})["name"] = name
    save_config(cfg)
    console.print(f"[green]✓ Chain set to {name}[/green]")


# ── memory ────────────────────────────────────────────────────────────────

@main.group()
def memory():
    """Manage diagnosis history."""
    pass


@memory.command("list")
@click.option("--limit", "-n", default=10, help="Number of recent entries.")
def memory_list(limit: int):
    """Show recent diagnoses."""
    from crashsense.core.memory import MemoryStore

    mem = MemoryStore()
    items = mem.list_recent(limit)
    if not items:
        console.print("[dim]No diagnoses recorded yet.[/dim]")
        return

    table = Table(title=f"Recent Diagnoses (last {limit})")
    table.add_column("Tx Hash", style="cyan", max_width=20)
    table.add_column("Chain")
    table.add_column("Root Cause", style="yellow")
    table.add_column("Status")
    table.add_column("Date", style="dim")

    for item in items:
        short_hash = item.tx_hash[:10] + "…" + item.tx_hash[-6:]
        table.add_row(
            short_hash,
            item.chain or "",
            (item.root_cause or "")[:40],
            item.status or "",
            str(item.created_at)[:19] if item.created_at else "",
        )

    console.print(table)


@memory.command("search")
@click.argument("query")
def memory_search(query: str):
    """Search diagnosis history."""
    from crashsense.core.memory import MemoryStore

    mem = MemoryStore()
    items = mem.search(query)
    if not items:
        console.print(f"[dim]No results for '{query}'.[/dim]")
        return

    for item in items:
        console.print(Panel(
            f"[cyan]{item.tx_hash}[/cyan]\n"
            f"Chain: {item.chain}  |  Status: {item.status}\n"
            f"Root cause: {item.root_cause}\n"
            f"Summary: {item.summary[:200]}",
            title=f"Match (freq={item.frequency})",
        ))


@memory.command("prune")
@click.option("--days", default=90, help="Remove entries older than N days.")
@click.option("--max-entries", default=1000, help="Max entries to keep.")
def memory_prune(days: int, max_entries: int):
    """Remove old diagnosis entries."""
    from crashsense.core.memory import MemoryStore

    mem = MemoryStore()
    deleted = mem.prune(max_entries=max_entries, retention_days=days)
    console.print(f"[green]✓ Pruned {deleted} entries.[/green]")


# ── config ────────────────────────────────────────────────────────────────

@main.group("config")
def config_group():
    """View and modify configuration."""
    pass


@config_group.command("show")
def config_show():
    """Show current configuration."""
    import toml as toml_mod

    cfg = load_config()
    text = toml_mod.dumps(cfg)
    console.print(Panel(text, title=str(CONFIG_PATH), border_style="cyan"))


@config_group.command("set")
@click.argument("key")
@click.argument("value")
def config_set(key: str, value: str):
    """Set a config key (dot notation). e.g. behaviour.auto_apply_fixes true"""
    cfg = load_config()
    parts = key.split(".")
    target = cfg
    for part in parts[:-1]:
        target = target.setdefault(part, {})

    # Coerce types
    if value.lower() in ("true", "yes"):
        value = True  # type: ignore[assignment]
    elif value.lower() in ("false", "no"):
        value = False  # type: ignore[assignment]
    elif value.isdigit():
        value = int(value)  # type: ignore[assignment]

    target[parts[-1]] = value
    save_config(cfg)
    console.print(f"[green]✓ {key} = {value}[/green]")


# ── rag ───────────────────────────────────────────────────────────────────

@main.group()
def rag():
    """Manage the RAG knowledge base."""
    pass


@rag.command("add")
@click.argument("filepath", type=click.Path(exists=True))
def rag_add(filepath: str):
    """Add a markdown file to the knowledge base."""
    import shutil

    src = Path(filepath)
    dest_dir = Path(__file__).resolve().parent.parent / "data"
    dest_dir.mkdir(parents=True, exist_ok=True)
    dest = dest_dir / src.name
    shutil.copy2(src, dest)
    console.print(f"[green]✓ Added {src.name} to knowledge base.[/green]")


@rag.command("clear")
@click.confirmation_option(prompt="Remove all custom knowledge base files?")
def rag_clear():
    """Clear all knowledge base files."""
    data_dir = Path(__file__).resolve().parent.parent / "data"
    if data_dir.exists():
        for f in data_dir.glob("*.md"):
            f.unlink()
        console.print("[green]✓ Knowledge base cleared.[/green]")
    else:
        console.print("[dim]No knowledge base directory found.[/dim]")


# ── tui (launcher) ────────────────────────────────────────────────────────

@main.command("tui")
def launch_tui():
    """Launch the Rich interactive TUI."""
    from crashsense.tui import run_tui

    run_tui()


# ── serve ─────────────────────────────────────────────────────────────────

@main.command("serve")
@click.option("--host", default="0.0.0.0", help="Bind address.")
@click.option("--port", "-p", default=8787, type=int, help="Port number.")
@click.option("--debug", is_flag=True, default=False, help="Enable Flask debug mode.")
def serve(host: str, port: int, debug: bool):
    """Start the CrashSense REST API server."""
    from crashsense.api import run_server

    console.print(f"[cyan]Starting CrashSense API on http://{host}:{port}[/cyan]")
    console.print("[dim]Endpoints:[/dim]")
    console.print("  GET  /api/v1/health")
    console.print("  GET  /api/v1/history")
    console.print("  GET  /api/v1/history/<tx_hash>")
    console.print("  GET  /api/v1/history/search?q=...")
    console.print("  GET  /api/v1/history/<id>/actions")
    console.print("  GET  /api/v1/stats")
    console.print("  POST /api/v1/diagnose")
    run_server(host=host, port=port, debug=debug)


# ── Display helper ────────────────────────────────────────────────────────

def _display_diagnosis(result: dict, auto_apply: bool = False):
    """Render a diagnosis result to the console."""
    from crashsense.core.remediation import RemediationEngine

    tx_ctx = result.get("tx_context", {})
    analysis = result.get("analysis", {})
    patterns = result.get("patterns_matched", [])

    # Transaction summary
    if tx_ctx and "error" not in tx_ctx:
        table = Table(title="Transaction Summary", show_lines=True)
        table.add_column("Field", style="cyan")
        table.add_column("Value")

        fields = [
            ("Tx Hash", tx_ctx.get("tx_hash", "")),
            ("Chain", tx_ctx.get("chain", "")),
            ("From", tx_ctx.get("from", "")),
            ("To", tx_ctx.get("to", "")),
            ("Value", f"{tx_ctx.get('value_eth', 0)} {tx_ctx.get('currency', 'ETH')}"),
            ("Function", tx_ctx.get("function_called", "")),
            ("Gas", f"{tx_ctx.get('gas_used', 0):,} / {tx_ctx.get('gas_limit', 0):,} ({tx_ctx.get('gas_utilisation_pct', 0):.1f}%)"),
            ("Status", tx_ctx.get("status", "")),
            ("Revert Reason", tx_ctx.get("revert_reason", "") or "—"),
        ]
        for name, val in fields:
            table.add_row(name, str(val))

        console.print(table)

    # Patterns
    if patterns:
        console.print(f"\n[bold]Patterns matched:[/bold] {', '.join(patterns)}")

    # Analysis
    console.print(Panel(
        f"[bold]Root Cause:[/bold] {analysis.get('root_cause', 'Unknown')}\n\n"
        f"{analysis.get('explanation', 'No explanation available.')}\n\n"
        f"[bold]Suggested Fix:[/bold] {analysis.get('suggested_fix', '—')}",
        title="[bold cyan]Diagnosis[/bold cyan]",
        border_style="green",
    ))

    # Remediation
    if patterns:
        engine = RemediationEngine()
        fixes_text = engine.format_fixes(patterns)
        console.print(Panel(fixes_text, title="[bold]Remediation Steps[/bold]", border_style="yellow"))

    # Auto-apply prompt
    if patterns and not auto_apply:
        console.print("\n[dim]Tip: Use --auto-apply to skip confirmation prompts.[/dim]")
