"""Idempotent workspace bootstrap + status reporting.

`init_workspace()` is safe to re-run any number of times: it creates the data
directory, applies the SQL schema via CREATE TABLE IF NOT EXISTS, and returns
a snapshot of what's currently in the workspace (config, DB tables, row
counts, auth-token presence, last sync/ingest timestamps).

CLI:
    openrun-init               # bootstrap + human-readable status
    openrun-init --json        # same, but machine-readable
"""

from __future__ import annotations

import argparse
import json
from dataclasses import asdict, dataclass
from pathlib import Path

from .config import Config, _find_config, default_config
from .db import connect, get_state


# Tables the loaders in `openrun.model` depend on. Kept in sync with `db.SCHEMA`.
EXPECTED_TABLES = (
    "activities",
    "activity_splits",
    "activity_fit_files",
    "activity_time_in_zone",
    "daily_steps",
    "daily_sleep",
    "daily_stress",
    "daily_hrv",
    "daily_body_battery",
    "daily_intensity_minutes",
    "daily_resting_hr",
    "manual_activities",
    "race_plan",
    "sync_state",
)


@dataclass(frozen=True)
class WorkspaceStatus:
    """Snapshot of the workspace after `init_workspace()` runs."""

    config_path: Path | None       # Where openrun.toml was found (None ⇒ defaults-only)
    db_path: Path                  # Resolved SQLite path
    db_existed_before: bool        # True if the DB file existed before this call
    tables_present: tuple[str, ...]
    table_counts: dict[str, int]
    auth_tokens_present: bool      # True iff secrets_dir has at least one file
    last_sync_utc: str | None
    last_ingest_utc: str | None

    def is_ready(self) -> bool:
        """Schema covers every table the loaders need."""
        return set(EXPECTED_TABLES).issubset(self.tables_present)

    def to_dict(self) -> dict:
        d = asdict(self)
        d["config_path"] = str(self.config_path) if self.config_path else None
        d["db_path"] = str(self.db_path)
        d["tables_present"] = list(self.tables_present)
        return d


def init_workspace(
    *,
    config: Config | None = None,
    db_path: Path | None = None,
    secrets_dir: Path | None = None,
) -> WorkspaceStatus:
    """Idempotently bootstrap an openrun workspace and report its state.

    Re-running is safe: schema is applied via CREATE TABLE IF NOT EXISTS,
    `mkdir(..., exist_ok=True)` is used everywhere, and no rows are ever
    deleted or modified by this function.

    Args:
        config: Optional Config override. If None, `default_config()` is used,
            which walks up from cwd looking for `openrun.toml`.
        db_path: Optional explicit DB path. Overrides `config.db_path`.
        secrets_dir: Optional auth-token directory. Defaults to `./.secrets`,
            matching what `openrun.ingest.auth` / `openrun.ingest.garmin_api` use.
    """
    cfg = config if config is not None else default_config()
    resolved_db = Path(db_path) if db_path is not None else cfg.db_path
    secrets = Path(secrets_dir) if secrets_dir is not None else (Path.cwd() / ".secrets")

    db_existed = resolved_db.exists()

    # connect() creates the parent dir and applies SCHEMA — both idempotent.
    conn = connect(resolved_db)
    try:
        tables = tuple(sorted(
            row[0]
            for row in conn.execute(
                "SELECT name FROM sqlite_master WHERE type='table'"
            )
        ))
        counts = {
            t: conn.execute(f"SELECT COUNT(*) FROM {t}").fetchone()[0]
            for t in tables
        }
        last_sync = get_state(conn, "last_sync_utc")
        last_ingest = get_state(conn, "last_ingest_utc")
    finally:
        conn.close()

    # Only attempt config discovery when the caller didn't pass an explicit Config —
    # an explicit Config has unknown provenance, so we leave config_path None.
    config_path = _find_config() if config is None else None

    tokens_present = secrets.is_dir() and any(secrets.iterdir())

    return WorkspaceStatus(
        config_path=config_path,
        db_path=resolved_db,
        db_existed_before=db_existed,
        tables_present=tables,
        table_counts=counts,
        auth_tokens_present=tokens_present,
        last_sync_utc=last_sync,
        last_ingest_utc=last_ingest,
    )


def _format_human(status: WorkspaceStatus) -> str:
    lines: list[str] = []
    lines.append("openrun workspace")
    lines.append("=" * 17)
    if status.config_path:
        lines.append(f"  config        : {status.config_path}")
    else:
        lines.append("  config        : (built-in defaults — write openrun.toml to customise)")
    lines.append(
        f"  db            : {status.db_path} "
        f"({'existed' if status.db_existed_before else 'created'})"
    )
    lines.append(f"  schema        : {'ready' if status.is_ready() else 'incomplete'}")
    lines.append(
        f"  auth tokens   : {'present' if status.auth_tokens_present else 'missing — run openrun-auth'}"
    )
    lines.append(f"  last sync     : {status.last_sync_utc or '(never)'}")
    lines.append(f"  last ingest   : {status.last_ingest_utc or '(never)'}")
    if status.table_counts:
        lines.append("")
        lines.append("table counts:")
        width = max(len(t) for t in status.table_counts)
        for t, n in status.table_counts.items():
            lines.append(f"  {t:<{width}}  {n:>10,}")
    return "\n".join(lines)


def main() -> None:
    parser = argparse.ArgumentParser(description=__doc__)
    parser.add_argument("--json", action="store_true", help="Emit machine-readable JSON")
    args = parser.parse_args()

    status = init_workspace()

    if args.json:
        print(json.dumps(status.to_dict(), indent=2, default=str))
    else:
        print(_format_human(status))


if __name__ == "__main__":
    main()
