#!/usr/bin/env python3
"""Parse and validate JSON with Pydantic.

This script demonstrates a practical, vendor-neutral pattern for turning raw JSON
into a typed Python model with explicit validation rules.

Features:
- Parse JSON from a string argument or a file
- Validate against a Pydantic model
- Distinguish malformed JSON from schema violations
- Optionally require strict type matching
- Emit validated data as JSON for downstream use

Examples:
  python validate_event_json.py --json '{"event_id":"evt-1001","source":"sensor-a","severity":3,"active":true}'
  python validate_event_json.py --file sample.json
  python validate_event_json.py --json '{"event_id":"","source":"sensor-a","severity":9,"active":true}'
"""

from __future__ import annotations

import argparse
import json
import sys
from pathlib import Path
from typing import Any, Optional

from pydantic import BaseModel, Field, ValidationError


class Event(BaseModel):
    """Expected event payload shape."""

    event_id: str = Field(min_length=1)
    source: str = Field(min_length=1)
    severity: int = Field(ge=0, le=5)
    active: bool


class StrictEvent(BaseModel):
    """Stricter variant that avoids common coercions."""

    event_id: str = Field(min_length=1)
    source: str = Field(min_length=1)
    severity: int = Field(ge=0, le=5)
    active: bool

    model_config = {
        "strict": True,
    }


def build_parser() -> argparse.ArgumentParser:
    parser = argparse.ArgumentParser(
        description="Parse and validate JSON with Pydantic."
    )
    input_group = parser.add_mutually_exclusive_group(required=True)
    input_group.add_argument(
        "--json",
        help="Raw JSON string to validate.",
    )
    input_group.add_argument(
        "--file",
        type=Path,
        help="Path to a file containing JSON to validate.",
    )
    parser.add_argument(
        "--strict",
        action="store_true",
        help="Use stricter validation settings.",
    )
    return parser


def read_input_text(args: argparse.Namespace) -> str:
    if args.json is not None:
        return args.json

    assert args.file is not None
    if not args.file.exists():
        raise FileNotFoundError(f"Input file does not exist: {args.file}")
    if not args.file.is_file():
        raise IsADirectoryError(f"Input path is not a file: {args.file}")
    return args.file.read_text(encoding="utf-8")


def validate_json(raw_json: str, strict: bool = False) -> BaseModel:
    model_cls: type[BaseModel] = StrictEvent if strict else Event
    return model_cls.model_validate_json(raw_json)


def safe_model_dump(model: BaseModel) -> dict[str, Any]:
    return model.model_dump()


def main(argv: Optional[list[str]] = None) -> int:
    parser = build_parser()
    args = parser.parse_args(argv)

    try:
        raw_json = read_input_text(args)
    except (OSError, ValueError) as exc:
        print(f"input error: {exc}", file=sys.stderr)
        return 2

    try:
        event = validate_json(raw_json, strict=args.strict)
    except ValidationError as exc:
        print("validation failed:", file=sys.stderr)
        print(exc, file=sys.stderr)
        return 1

    # Emit the validated object in a stable machine-readable form.
    print(json.dumps(safe_model_dump(event), indent=2, sort_keys=True))
    return 0


if __name__ == "__main__":
    raise SystemExit(main())