"""
Utility functions for CrashSense.

Provides transaction hash validation, file I/O helpers, safe command
execution, and OS-compatible formatting.
"""

from __future__ import annotations

import re
import shlex
import shutil
import subprocess
from pathlib import Path
from typing import Tuple

from rich.console import Console

console = Console()

# ── Transaction hash validation ───────────────────────────────────────────

_TX_HASH_RE = re.compile(r"^0x[0-9a-fA-F]{64}$")


def is_valid_tx_hash(value: str) -> bool:
    """Return True if *value* is a valid 66-char hex transaction hash."""
    return bool(_TX_HASH_RE.match(value.strip()))


def normalise_tx_hash(value: str) -> str:
    """Strip whitespace and lowercase a tx hash."""
    h = value.strip().lower()
    if not h.startswith("0x"):
        h = "0x" + h
    return h


# ── File I/O ──────────────────────────────────────────────────────────────

def read_file(path: str) -> str:
    """Read a text file with multi-encoding fallback."""
    p = Path(path)
    if not p.exists():
        raise FileNotFoundError(path)
    for enc in ("utf-8", "iso-8859-1", "latin-1"):
        try:
            return p.read_text(encoding=enc)
        except UnicodeDecodeError:
            continue
    raise UnicodeDecodeError(
        "all", b"", 0, 0,
        f"Unable to decode {path} with supported encodings",
    )


def short_text(text: str, max_len: int = 200) -> str:
    """Truncate text with ellipsis."""
    return text if len(text) <= max_len else text[: max_len] + "…"


# ── Safe command execution ────────────────────────────────────────────────

_SHELL_META = set("|&;><`$(){\n")


def has_shell_metacharacters(cmd: str) -> bool:
    """Detect shell metacharacters that require shell=True."""
    return any(ch in cmd for ch in _SHELL_META)


def run_command_safe(cmd: str, timeout: int = 600) -> Tuple[int, str, str]:
    """
    Run a simple command without a shell.
    Returns (returncode, stdout, stderr).
    """
    if has_shell_metacharacters(cmd):
        return (127, "", "Unsafe shell metacharacters detected; refusing to run.")
    try:
        parts = shlex.split(cmd)
    except Exception as exc:
        return (127, "", f"Failed to parse command: {exc}")
    if not parts:
        return (0, "", "")
    try:
        cp = subprocess.run(parts, capture_output=True, text=True, timeout=timeout)
        return (cp.returncode, cp.stdout, cp.stderr)
    except FileNotFoundError as exc:
        return (127, "", str(exc))
    except subprocess.TimeoutExpired:
        return (124, "", "Command timed out")
    except Exception as exc:
        return (1, "", str(exc))


# ── Ollama helpers ────────────────────────────────────────────────────────

def is_executable_on_path(name: str) -> bool:
    return shutil.which(name) is not None


def check_ollama_running() -> bool:
    """Return True if the ollama binary is on PATH."""
    return is_executable_on_path("ollama")


def pull_ollama_model(model_name: str) -> bool:
    """Attempt to pull an Ollama model."""
    try:
        console.print(f"[cyan]Pulling model: ollama pull {model_name}[/cyan]")
        cp = subprocess.run(
            ["ollama", "pull", model_name],
            capture_output=True, text=True, timeout=600,
        )
        if cp.returncode == 0:
            console.print("[green]Model pulled successfully.[/green]")
            return True
        console.print(f"[yellow]Pull returned code {cp.returncode}: {cp.stderr[:200]}[/yellow]")
    except FileNotFoundError:
        console.print("[red]ollama binary not found on PATH.[/red]")
    except subprocess.TimeoutExpired:
        console.print("[red]Model pull timed out after 600 seconds.[/red]")
    except Exception as exc:
        console.print(f"[red]Error pulling model: {exc}[/red]")
    return False


# ── Ethereum address helpers ──────────────────────────────────────────────

_ADDR_RE = re.compile(r"^0x[0-9a-fA-F]{40}$")


def is_valid_address(value: str) -> bool:
    """Return True if *value* is a valid 42-char hex Ethereum address."""
    return bool(_ADDR_RE.match(value.strip()))


# ── Wei / Gwei / Ether conversion ────────────────────────────────────────

def wei_to_ether(wei: int) -> float:
    return wei / 1e18


def wei_to_gwei(wei: int) -> float:
    return wei / 1e9


def gwei_to_wei(gwei: float) -> int:
    return int(gwei * 1e9)


# ── Formatting ────────────────────────────────────────────────────────────

def format_token_amount(amount_raw: int, decimals: int = 18, symbol: str = "") -> str:
    """Human-readable token amount (e.g. '1,234.56 USDC')."""
    value = amount_raw / (10 ** decimals)
    formatted = f"{value:,.{min(decimals, 6)}f}"
    return f"{formatted} {symbol}".strip()


def format_gas(gas_used: int, gas_limit: int) -> str:
    """Return e.g. '184,320 / 300,000 (61.4%)'."""
    pct = round(gas_used / gas_limit * 100, 1) if gas_limit else 0
    return f"{gas_used:,} / {gas_limit:,} ({pct}%)"
