"""``universe-reads inspect FILE`` — quick introspection of a UniVerse file.

Connects to UniVerse, SELECT-counts the records, reads a sample record, and
prints a summary including field count, sample key, and the raw dynamic array.
"""

from __future__ import annotations

import json
import logging
import sys
import time
from typing import Any

from .import_utils import call_factory, load_callable

logger = logging.getLogger("universe_reads")

# UniVerse dynamic array delimiters
AM = chr(254)
VM = chr(253)
SVM = chr(252)


def inspect(
    *,
    file_name: str,
    session_factory_spec: str,
    session_factory_kwargs: dict[str, Any] | None = None,
    sample_count: int = 3,
) -> dict[str, Any]:
    """Inspect a UniVerse file and return summary metadata.

    Args:
        file_name: The UniVerse file to inspect.
        session_factory_spec: Import spec for the session factory callable.
        session_factory_kwargs: Optional kwargs forwarded to the factory.
        sample_count: How many sample records to read (default 3).

    Returns:
        A dict with keys: ``file``, ``record_count``, ``sample_keys``,
        ``field_counts``, ``sample_records``, ``elapsed_seconds``.
    """

    kwargs = session_factory_kwargs or {}
    factory = load_callable(session_factory_spec)
    session = call_factory(factory, kwargs)

    started = time.monotonic()

    try:
        import uopy  # type: ignore
    except ImportError as exc:
        raise ImportError(
            "Inspect requires 'uopy'. Install it: pip install uopy"
        ) from exc

    result: dict[str, Any] = {
        "file": file_name,
        "record_count": 0,
        "sample_keys": [],
        "field_counts": [],
        "sample_records": [],
    }

    try:
        # Count records via SELECT COUNT.
        cmd = uopy.Command(session=session)
        cmd.run(f"SELECT {file_name}")

        # Iterate the select list to count + grab samples.
        select_list = uopy.List(session=session)
        count = 0
        sample_keys: list[str] = []

        while True:
            rid = select_list.next()
            if rid is None:
                break
            count += 1
            key = _coerce_id(rid)
            if len(sample_keys) < sample_count:
                sample_keys.append(key)

        result["record_count"] = count
        result["sample_keys"] = sample_keys

        # Read sample records.
        if sample_keys:
            with uopy.File(file_name, session=session) as fobj:
                fobj.open()
                for key in sample_keys:
                    try:
                        raw = fobj.read(key)
                        text = _coerce_value(raw)
                        fields = text.split(AM) if isinstance(text, str) else []
                        result["field_counts"].append(len(fields))
                        result["sample_records"].append(
                            {"key": key, "fields": len(fields), "raw": text[:500]}
                        )
                    except Exception as exc:
                        result["sample_records"].append(
                            {"key": key, "error": str(exc)}
                        )

    finally:
        try:
            close = getattr(session, "close", None)
            if callable(close):
                close()
        except Exception:
            pass

    result["elapsed_seconds"] = round(time.monotonic() - started, 3)
    return result


def print_inspect(info: dict[str, Any], *, file: Any = None) -> None:
    """Pretty-print the result of :func:`inspect`."""

    out = file or sys.stderr
    out.write(f"\n{'=' * 60}\n")
    out.write(f"  File:          {info['file']}\n")
    out.write(f"  Record count:  {info['record_count']:,}\n")
    if info.get("field_counts"):
        avg_fields = sum(info["field_counts"]) / len(info["field_counts"])
        out.write(f"  Avg fields:    {avg_fields:.1f}\n")
    out.write(f"  Elapsed:       {info['elapsed_seconds']:.3f}s\n")
    out.write(f"{'=' * 60}\n")

    for sample in info.get("sample_records", []):
        out.write(f"\n  Key: {sample.get('key', '?')}\n")
        if "error" in sample:
            out.write(f"  ERROR: {sample['error']}\n")
        else:
            out.write(f"  Fields: {sample.get('fields', '?')}\n")
            raw = sample.get("raw", "")
            # Show first 200 chars with delimiters replaced for readability.
            display = raw[:200].replace(AM, " | ").replace(VM, " , ").replace(SVM, " ; ")
            out.write(f"  Preview: {display}\n")

    out.write("\n")
    out.flush()


def inspect_json(info: dict[str, Any], *, file: Any = None) -> None:
    """Write inspect result as JSON to a file-like object."""
    out = file or sys.stdout
    out.write(json.dumps(info, indent=2, default=str) + "\n")
    out.flush()


# ---------------------------------------------------------------------------
# Internal helpers
# ---------------------------------------------------------------------------

def _coerce_id(rid: Any) -> str:
    if isinstance(rid, str):
        return rid
    if isinstance(rid, bytes):
        return rid.decode("utf-8", errors="replace")
    v = getattr(rid, "list", None)
    if isinstance(v, str):
        return v
    if isinstance(v, list) and len(v) == 1 and isinstance(v[0], str):
        return v[0]
    return str(rid)


def _coerce_value(record: Any) -> Any:
    if isinstance(record, str):
        return record
    if isinstance(record, bytes):
        return record.decode("utf-8", errors="replace")
    v = getattr(record, "list", None)
    if v is not None:
        return v
    return str(record)
