"""Shared CLI utilities - zero external dependencies."""
import json
import os
import subprocess
import sys

from obol.api import ObolAPI, ObolAPIError


# --- Cross-platform helpers ---

def _detect_unicode_support():
    """Check if the terminal can display Unicode characters."""
    if sys.platform == "win32":
        # Windows Terminal and ConEmu support Unicode
        if os.environ.get("WT_SESSION") or os.environ.get("ConEmuPID"):
            return True
        # Check if console codepage is UTF-8
        try:
            import ctypes
            return ctypes.windll.kernel32.GetConsoleOutputCP() == 65001
        except Exception:
            return False
    return True


UNICODE = _detect_unicode_support()

# Characters with ASCII fallbacks for legacy Windows terminals
if UNICODE:
    CHAR_FILLED = "\u2588"     # █
    CHAR_EMPTY = "\u2591"      # ░
    CHAR_CHECK = "\u2713"      # ✓
    CHAR_CROSS = "\u2717"      # ✗
    CHAR_PARTIAL = "\u25d0"    # ◐
    CHAR_WARNING = "\u26a0"    # ⚠
    CHAR_ACTIVE = "\u25cf"     # ●
    CHAR_INACTIVE = "\u25cb"   # ○
    CHAR_HLINE = "\u2500"      # ─
    CHAR_DLINE = "\u2550"      # ═
    CHAR_TL = "\u2554"         # ╔
    CHAR_TR = "\u2557"         # ╗
    CHAR_BL = "\u255a"         # ╚
    CHAR_BR = "\u255d"         # ╝
    CHAR_VLINE = "\u2551"      # ║
else:
    CHAR_FILLED = "#"
    CHAR_EMPTY = "-"
    CHAR_CHECK = "+"
    CHAR_CROSS = "x"
    CHAR_PARTIAL = "~"
    CHAR_WARNING = "!"
    CHAR_ACTIVE = "*"
    CHAR_INACTIVE = "o"
    CHAR_HLINE = "-"
    CHAR_DLINE = "="
    CHAR_TL = "+"
    CHAR_TR = "+"
    CHAR_BL = "+"
    CHAR_BR = "+"
    CHAR_VLINE = "|"


def secure_file(path):
    """Restrict file permissions to owner-only. Cross-platform.

    On Unix: chmod 600. On Windows: icacls to restrict to current user.
    """
    if sys.platform == "win32":
        try:
            username = os.environ.get("USERNAME", "")
            if username:
                subprocess.run(
                    ["icacls", path, "/inheritance:r", "/grant:r",
                     f"{username}:(R,W)"],
                    stdout=subprocess.DEVNULL, stderr=subprocess.DEVNULL,
                    timeout=10,
                )
        except Exception:
            pass  # Best effort on Windows
    else:
        os.chmod(path, 0o600)


def quote_path(path):
    """Quote a path for use in shell command strings if it contains spaces."""
    if " " in str(path):
        return f'"{path}"'
    return str(path)


def get_api() -> ObolAPI:
    """Get configured API client from environment."""
    api_key = os.environ.get("OBOL_API_KEY")
    if not api_key:
        # Try config file
        config_path = os.path.expanduser("~/.obol/config.json")
        if os.path.exists(config_path):
            with open(config_path) as f:
                config = json.load(f)
                api_key = config.get("api_key")

    if not api_key:
        error("No API key. Set OBOL_API_KEY or run: obol config set api_key <key>")

    base_url = os.environ.get(
        "OBOL_API_URL", "https://obol.grayowlresearch.com/v1"
    )
    # Also check config file for base_url
    config_path = os.path.expanduser("~/.obol/config.json")
    if os.path.exists(config_path):
        with open(config_path) as f:
            config = json.load(f)
            base_url = config.get("api_url", base_url)

    return ObolAPI(api_key, base_url)


def output(data, fmt="text"):
    """Output data in requested format."""
    if fmt == "json":
        print(json.dumps(data, indent=2, default=str))
    else:
        if isinstance(data, str):
            print(data)
        elif isinstance(data, dict):
            _print_dict(data)
        elif isinstance(data, list):
            for item in data:
                if isinstance(item, dict):
                    _print_dict(item)
                    print()
                else:
                    print(item)


def _print_dict(d, indent=0):
    """Print a dict with aligned keys."""
    if not d:
        return
    prefix = "  " * indent
    max_key = max(len(str(k)) for k in d.keys()) if d else 0
    for k, v in d.items():
        if isinstance(v, dict):
            print(f"{prefix}{str(k):<{max_key}}:")
            _print_dict(v, indent + 1)
        elif isinstance(v, list) and v and isinstance(v[0], dict):
            print(f"{prefix}{str(k):<{max_key}}:")
            for item in v:
                _print_dict(item, indent + 1)
                print()
        else:
            print(f"{prefix}{str(k):<{max_key}}  {v}")


def table(headers: list[str], rows: list[list], max_widths: dict = None):
    """Print a formatted table. Zero dependencies."""
    if not rows:
        print("(no data)")
        return

    # Calculate column widths
    widths = [len(h) for h in headers]
    for row in rows:
        for i, cell in enumerate(row):
            if i < len(widths):
                widths[i] = max(widths[i], len(str(cell)))

    # Apply max widths
    if max_widths:
        for i, w in max_widths.items():
            if i < len(widths):
                widths[i] = min(widths[i], w)

    # Header
    header_line = "  ".join(str(h).ljust(widths[i]) for i, h in enumerate(headers))
    print(header_line)
    print("  ".join("-" * w for w in widths))

    # Rows
    for row in rows:
        cells = []
        for i, cell in enumerate(row):
            s = str(cell)
            if i < len(widths):
                if len(s) > widths[i]:
                    s = s[:widths[i] - 1] + ("\u2026" if UNICODE else "~")
                cells.append(s.ljust(widths[i]))
            else:
                cells.append(s)
        print("  ".join(cells))


def error(msg: str, code: int = 1):
    """Print error and exit."""
    print(f"Error: {msg}", file=sys.stderr)
    sys.exit(code)


def handle_api_error(func):
    """Decorator to handle API errors gracefully."""
    def wrapper(*args, **kwargs):
        try:
            return func(*args, **kwargs)
        except ObolAPIError as e:
            error(f"API error ({e.status_code}): {e.detail}")
        except Exception as e:
            error(str(e))
    wrapper.__name__ = func.__name__
    wrapper.__doc__ = func.__doc__
    return wrapper


def add_format_arg(parser, choices=None):
    """Add --format flag to a subparser so it works in both positions.

    This mirrors the global --format on the parent parser, allowing
    ``obol export events --format json`` in addition to
    ``obol --format json export events``.

    Uses SUPPRESS default so the subparser only sets ``args.format``
    when explicitly provided, avoiding clobbering the parent value.
    """
    import argparse
    if choices is None:
        choices = ["text", "json"]
    parser.add_argument(
        "--format", "-f",
        choices=choices,
        default=argparse.SUPPRESS,
        help="Output format",
    )


def score_bar(score: float, width: int = 20) -> str:
    """Visual score bar using block chars (Unicode or ASCII fallback)."""
    filled = int(score / 100 * width) if score is not None else 0
    bar = CHAR_FILLED * filled + CHAR_EMPTY * (width - filled)
    return f"[{bar}] {score:.0f}" if score is not None else f"[{CHAR_EMPTY * width}] --"


def compliance_bar(score: float, width: int = 20) -> str:
    """Visual compliance bar (0.0-1.0 scale)."""
    if score is None:
        return f"[{CHAR_EMPTY * width}] --"
    pct = score * 100
    filled = int(score * width)
    bar = CHAR_FILLED * filled + CHAR_EMPTY * (width - filled)
    return f"[{bar}] {pct:.0f}%"
