import time
import random
import traceback
from typing import Dict, Any, List, Optional


def diagnose_hybrid_store(
    store,
    *,
    mode: str = "sample",
    sample_size: int = 5000,
    random_seed: int = 0,
    check_sqlite_integrity: bool = True,
    check_sqlite_consistency: bool = True,
    check_hdf5_open: bool = True,
    check_dataset_existence: bool = True,
    check_dataset_read: bool = True,
    check_unpickle: bool = False,
    max_reported_errors: int = 50,
    verbose: bool = True,
) -> Dict[str, Any]:
    """
    Perform a comprehensive integrity diagnostic of a HybridStorage database.

    This routine validates both persistence layers:

        1. SQLite metadata/index database
        2. HDF5 payload storage

    The diagnostic is intentionally modular, allowing inexpensive health
    checks for routine monitoring as well as deep validation suitable for
    archival verification.

    ----------------------------------------------------------------------
    Validation Levels
    ----------------------------------------------------------------------

    SQLite Checks
    -------------
    * Structural integrity via PRAGMA integrity_check
    * Logical consistency (orphan rows across relational tables)

    HDF5 Checks
    -----------
    * File accessibility and validity
    * Dataset existence for every stored object
    * Minimal dataset read to validate compression/chunk integrity
    * Optional full object deserialization (pickle validation)

    ----------------------------------------------------------------------
    Parameters
    ----------------------------------------------------------------------
    store :
        An instance of HybridStorage.

    mode : {"sample", "full"}
        Diagnostic scope.

        - "sample": randomly sample objects (recommended for routine checks)
        - "full":   validate every stored object

    sample_size : int
        Number of objects inspected when mode="sample".

    random_seed : int
        Seed controlling deterministic sampling.

    check_sqlite_integrity : bool
        Execute SQLite structural integrity verification.

    check_sqlite_consistency : bool
        Detect relational inconsistencies (orphan entries).

    check_hdf5_open : bool
        Verify that the HDF5 file handle is valid.

    check_dataset_existence : bool
        Confirm that every SQLite object has a corresponding HDF5 dataset.

    check_dataset_read : bool
        Perform a minimal dataset read operation. This forces HDF5 chunk
        decoding and compression validation without loading full objects.

    check_unpickle : bool
        Fully deserialize objects via ``store.get(id)``.
        This is the strongest validation but also the slowest.

    max_reported_errors : int
        Maximum number of failing object IDs stored per category.

    verbose : bool
        Print a human-readable diagnostic summary.

    ----------------------------------------------------------------------
    Returns
    ----------------------------------------------------------------------
    dict
        Structured diagnostic report containing:

        * global status flag
        * timing information
        * failure statistics
        * representative failing object identifiers
    """

    start_time = time.perf_counter()
    rng = random.Random(random_seed)

    report: Dict[str, Any] = {
        "ok": None,
        "mode": mode,
        "counts": {},
        "sqlite": {},
        "hdf5": {},
        "failures": {
            "missing_dataset": [],
            "read_failure": [],
            "unpickle_failure": [],
        },
        "examples": {},
        "elapsed_seconds": None,
    }

    # ------------------------------------------------------------------
    # Collect object identifiers
    # ------------------------------------------------------------------
    ids_all: List[int] = store.list_ids()
    total_objects = len(ids_all)

    if mode == "sample":
        ids = rng.sample(ids_all, min(sample_size, total_objects))
    elif mode == "full":
        ids = ids_all
    else:
        raise ValueError("mode must be 'sample' or 'full'")

    report["counts"]["objects_total"] = total_objects
    report["counts"]["objects_checked"] = len(ids)

    # ------------------------------------------------------------------
    # 1. SQLite structural integrity
    # ------------------------------------------------------------------
    if check_sqlite_integrity:
        try:
            result = store._conn.execute(
                "PRAGMA integrity_check;"
            ).fetchone()
            report["sqlite"]["integrity_check"] = {
                "ok": result[0] == "ok",
                "result": result[0],
            }
        except Exception as exc:
            report["sqlite"]["integrity_check"] = {
                "ok": False,
                "error": repr(exc),
            }

    # ------------------------------------------------------------------
    # 2. SQLite relational consistency
    # ------------------------------------------------------------------
    if check_sqlite_consistency:
        cur = store._conn.cursor()
        consistency = {}

        queries = {
            "orphan_compositions":
                """SELECT COUNT(*) FROM compositions c
                   LEFT JOIN objects o ON o.id=c.object_id
                   WHERE o.id IS NULL""",

            "orphan_scalars":
                """SELECT COUNT(*) FROM scalars s
                   LEFT JOIN objects o ON o.id=s.object_id
                   WHERE o.id IS NULL""",

            "orphan_hashes":
                """SELECT COUNT(*) FROM object_hashes h
                   LEFT JOIN objects o ON o.id=h.object_id
                   WHERE o.id IS NULL""",
        }

        for name, q in queries.items():
            try:
                consistency[name] = int(cur.execute(q).fetchone()[0])
            except Exception as exc:
                consistency[name] = f"ERROR: {exc!r}"

        report["sqlite"]["consistency"] = consistency

    # ------------------------------------------------------------------
    # 3. HDF5 accessibility check
    # ------------------------------------------------------------------
    if check_hdf5_open:
        try:
            valid = bool(store._h5.id.valid)
            has_group = "objs" in store._h5
            report["hdf5"]["open"] = {
                "ok": valid and has_group
            }
        except Exception as exc:
            report["hdf5"]["open"] = {"ok": False, "error": repr(exc)}

    # ------------------------------------------------------------------
    # Helper
    # ------------------------------------------------------------------
    def capped_append(lst: List[int], value: int):
        if len(lst) < max_reported_errors:
            lst.append(value)

    # ------------------------------------------------------------------
    # 4. Per-object validation
    # ------------------------------------------------------------------
    for oid in ids:

        dataset = None

        # Resolve dataset location
        try:
            grp, name = store._resolve_h5_path(
                store._h5,
                oid,
                store.hdf5_shard_levels,
                store.hdf5_shard_digits,
            )
            dataset = grp.get(name, None)
        except Exception:
            dataset = None

        # Dataset existence
        if check_dataset_existence and dataset is None:
            capped_append(report["failures"]["missing_dataset"], oid)
            continue

        # Dataset read validation
        if check_dataset_read and dataset is not None:
            try:
                _ = dataset[0:1]
            except Exception:
                capped_append(report["failures"]["read_failure"], oid)
                if "read_trace" not in report["examples"]:
                    report["examples"]["read_trace"] = traceback.format_exc()
                continue

        # Full deserialization
        if check_unpickle:
            try:
                store.get(oid)
            except Exception:
                capped_append(report["failures"]["unpickle_failure"], oid)
                if "unpickle_trace" not in report["examples"]:
                    report["examples"]["unpickle_trace"] = traceback.format_exc()

    # ------------------------------------------------------------------
    # Final evaluation
    # ------------------------------------------------------------------
    ok = True

    if check_sqlite_integrity:
        ok &= report["sqlite"]["integrity_check"]["ok"]

    if check_dataset_existence:
        ok &= len(report["failures"]["missing_dataset"]) == 0

    if check_dataset_read:
        ok &= len(report["failures"]["read_failure"]) == 0

    if check_unpickle:
        ok &= len(report["failures"]["unpickle_failure"]) == 0

    report["ok"] = bool(ok)
    report["elapsed_seconds"] = time.perf_counter() - start_time

    # ------------------------------------------------------------------
    # Human-readable summary
    # ------------------------------------------------------------------
    if verbose:
        print("\n================ HybridStorage Diagnostic ================")
        print(f"Mode                : {mode}")
        print(f"Objects checked     : {len(ids)}/{total_objects}")
        print(f"SQLite OK           : {report['sqlite'].get('integrity_check')}")
        print(f"HDF5 Open OK        : {report['hdf5'].get('open')}")
        print(f"Missing datasets    : {len(report['failures']['missing_dataset'])}")
        print(f"Read failures       : {len(report['failures']['read_failure'])}")
        print(f"Unpickle failures   : {len(report['failures']['unpickle_failure'])}")
        print(f"GLOBAL STATUS       : {'OK' if report['ok'] else 'FAILED'}")
        print(f"Elapsed time (s)    : {report['elapsed_seconds']:.3f}")
        print("===========================================================\n")

    return report