"""Places on the command line: the guide document in, out, and checked (Stream G2).

    python -m app.cli places guide import import/places/heathrow-lhr-london.json [--check] [--by rian]
    python -m app.cli places guide import --all [--check]
    python -m app.cli places guide export heathrow-lhr-london [--stdout]
    python -m app.cli places list

The import is how a place is populated, and it is the whole of it: no release, no migration. It is
idempotent, so it is safe to repeat and safe to run after a deploy; `--check` reads and validates and
writes nothing. A file that does not validate stops that file and names it, and the others still run,
because one bad document should not hold up eighteen good ones.

Registered from `cli.py` by one `register(sub)` line.
"""

from __future__ import annotations

import argparse
import json
import sys
from pathlib import Path

from app.db import SessionLocal
from app.services import place_guides
from app.services.hours import store


def _load(path: Path):
    """Validate one file, printing the reason it is refused rather than a traceback."""
    try:
        return place_guides.load_file(path), None
    except FileNotFoundError:
        return None, "no such file"
    except json.JSONDecodeError as exc:
        return None, f"not JSON: {exc}"
    except Exception as exc:  # pydantic ValidationError, kept readable
        first = str(exc).splitlines()
        return None, "does not match the guide document: " + " ".join(first[:4])


def cmd_guide_import(args: argparse.Namespace) -> int:
    paths = sorted(place_guides.FILES.glob("*.json")) if args.all else [Path(p) for p in args.files]
    if not paths:
        print("no files: pass one or more paths, or --all for import/places/*.json")
        return 2
    written = unchanged = refused = 0
    with SessionLocal() as db:
        for path in paths:
            slug = path.stem
            guide, why = _load(path)
            if guide is None:
                print(f"{slug}: REFUSED, {why}")
                refused += 1
                continue
            place = place_guides.place_by_slug(db, slug)
            if place is None:
                print(f"{slug}: REFUSED, no place has that slug (`places list`)")
                refused += 1
                continue
            if args.check:
                current = (place.attributes or {}).get(place_guides.KEY)
                same = current == place_guides.as_stored(guide)
                print(f"{slug}: [check, nothing written] {'no change' if same else 'would change'}"
                      f" ({len(guide.areas)} areas, {len(guide.sections)} sections)")
                continue
            if place_guides.set_guide(db, place, guide):
                written += 1
                print(f"{slug}: written ({len(guide.areas)} areas, {len(guide.sections)} sections)")
            else:
                unchanged += 1
                print(f"{slug}: unchanged")
        if not args.check:
            db.commit()
    if not args.check:
        print(f"{written} written, {unchanged} unchanged, {refused} refused")
    return 1 if refused else 0


def cmd_guide_export(args: argparse.Namespace) -> int:
    with SessionLocal() as db:
        place = place_guides.place_by_slug(db, args.slug)
        if place is None:
            print(f"no place has the slug {args.slug!r} (`places list`)")
            return 2
        guide = place_guides.for_place(place)
        if guide is None:
            print(f"{args.slug} has no guide")
            return 2
        if args.stdout:
            json.dump(place_guides.as_stored(guide), sys.stdout, indent=2, ensure_ascii=False)
            print()
            return 0
        path = place_guides.write_file(args.slug, guide)
        print(f"{args.slug}: written to {path}")
        return 0


def cmd_places_list(args: argparse.Namespace) -> int:
    from sqlalchemy import select

    from app.models.places import Place

    with SessionLocal() as db:
        for place in db.execute(select(Place).order_by(Place.slug)).scalars():
            codes = ",".join(str(i.get("value")) for i in (place.identifiers or []))
            guide = place_guides.for_place(place)
            hours = store.current_for_airport(db, codes.split(",")[0]) if codes else None
            mark = f"guide: {len(guide.areas)} areas" if guide else "no guide"
            print(f"  {place.slug:28} {place.kind:8} {codes or '-':6} {mark:18} "
                  f"{'hours ' + hours.source_kind if hours else 'no hours'}")
    return 0


def register(sub: argparse._SubParsersAction) -> None:
    places = sub.add_parser("places", help="places: the guide document in and out, and what each place holds")
    psub = places.add_subparsers(dest="places_command", required=True)

    guide = psub.add_parser("guide", help="a place's written guide, stored on the place")
    gsub = guide.add_subparsers(dest="guide_command", required=True)

    imp = gsub.add_parser("import", help="write one or more guide documents onto their places; idempotent")
    imp.add_argument("files", nargs="*", help="paths to import/places/<slug>.json")
    imp.add_argument("--all", action="store_true", help="every file in import/places/")
    imp.add_argument("--check", action="store_true", help="validate and report; write nothing")
    imp.set_defaults(func=cmd_guide_import)

    exp = gsub.add_parser("export", help="write a place's stored guide back to its file")
    exp.add_argument("slug")
    exp.add_argument("--stdout", action="store_true", help="print it instead of writing the file")
    exp.set_defaults(func=cmd_guide_export)

    lst = psub.add_parser("list", help="every place, its identifiers, and whether it has a guide and hours")
    lst.set_defaults(func=cmd_places_list)
