"""Command-line interface for Scienith OpenAPI contract tooling."""

from __future__ import annotations

import argparse
import json
from collections.abc import Sequence
from pathlib import Path

from scienith_openapi_contract_tooling import __version__
from scienith_openapi_contract_tooling.openapi.bundle import bundle_openapi_spec_for_docs
from scienith_openapi_contract_tooling.openapi.msw_handlers_ts import openapi_to_msw_handlers_ts
from scienith_openapi_contract_tooling.openapi.python_models import openapi_to_python_models
from scienith_openapi_contract_tooling.openapi.redoc import openapi_to_redoc_html
from scienith_openapi_contract_tooling.openapi.ts_types import openapi_to_ts_types
from scienith_openapi_contract_tooling.openapi.uml import openapi_to_uml
from scienith_openapi_contract_tooling.openapi.zod_ts import openapi_to_zod_ts
from scienith_openapi_contract_tooling.profiles import build_from_config


def _add_common_build_options(parser: argparse.ArgumentParser) -> None:
    parser.add_argument(
        "--config",
        required=True,
        help="Path to contract-tooling.yaml. The file defines one or more build profiles.",
    )
    parser.add_argument(
        "--profile",
        help="Profile name to run. Required when the config contains multiple profiles.",
    )
    parser.add_argument(
        "--dry-run",
        action="store_true",
        help="Validate config and print planned actions without writing generated artifacts.",
    )


def _add_root_out(parser: argparse.ArgumentParser, *, out_name: str = "--out") -> None:
    parser.add_argument("--root", required=True, help="Input OpenAPI YAML or JSON path.")
    parser.add_argument(out_name, required=True, help="Output artifact path.")
    parser.add_argument("--bundle", action=argparse.BooleanOptionalAction, default=True)


def build_parser() -> argparse.ArgumentParser:
    parser = argparse.ArgumentParser(
        prog="scienith-contract",
        description="Generate and validate Scienith OpenAPI contract artifacts.",
    )
    parser.add_argument("--version", action="version", version=f"%(prog)s {__version__}")

    subparsers = parser.add_subparsers(dest="command")

    build = subparsers.add_parser(
        "build",
        help="Run contract artifact builds from a profile.",
        description="Run contract artifact builds from a profile.",
        epilog=(
            "Full-suite generation reads contract-tooling.yaml and writes all artifacts for a profile. "
            "Supported profile types: contract-package and monorepo-adapter. "
            "Example: scienith-contract build --config contract-tooling.yaml --profile sugarglider-contract. "
            "Prerequisites: Python deps installed in this tool environment; npm/openapi-typescript installed in the target TS package when ts-types are enabled."
        ),
    )
    _add_common_build_options(build)
    build_subparsers = build.add_subparsers(dest="build_command")
    build_all = build_subparsers.add_parser(
        "all",
        help="Generate the full artifact set for one profile.",
        description="Generate the full artifact set for one profile.",
        epilog=(
            "Alias for `scienith-contract build`: it runs the selected profile's full artifact suite."
        ),
    )
    _add_common_build_options(build_all)
    build_all.set_defaults(handler=_handle_build_profile)
    build.set_defaults(handler=_handle_build_profile)

    openapi = subparsers.add_parser(
        "openapi",
        help="OpenAPI bundle, validation, and documentation helpers.",
        description="OpenAPI bundle, validation, and documentation helpers.",
    )
    openapi_subparsers = openapi.add_subparsers(dest="openapi_command")

    bundle = openapi_subparsers.add_parser(
        "bundle",
        help="Bundle an OpenAPI document and resolve local references.",
        description="Bundle an OpenAPI document and resolve local references.",
    )
    _add_root_out(bundle)
    bundle.set_defaults(handler=_handle_openapi_bundle)

    views = openapi_subparsers.add_parser(
        "views",
        help="Generate PlantUML and Redoc outputs from OpenAPI input.",
        description="Generate PlantUML and Redoc outputs from OpenAPI input.",
    )
    views.add_argument("--root", required=True, help="Input OpenAPI YAML or JSON path.")
    views.add_argument("--uml-out", required=True, help="Output PlantUML path.")
    views.add_argument("--redoc-out", required=True, help="Output Redoc HTML path.")
    views.add_argument("--bundle", action=argparse.BooleanOptionalAction, default=True)
    views.add_argument("--direction", default="LR", choices=("LR", "TB"))
    views.set_defaults(handler=_handle_openapi_views)

    uml = openapi_subparsers.add_parser(
        "uml",
        help="Generate a PlantUML overview from OpenAPI input.",
        description="Generate a PlantUML overview from OpenAPI input.",
    )
    _add_root_out(uml)
    uml.add_argument("--plantuml-jar", help="PlantUML jar path when output is .png or .svg.")
    uml.add_argument("--direction", default="LR", choices=("LR", "TB"))
    uml.set_defaults(handler=_handle_openapi_uml)

    redoc = openapi_subparsers.add_parser(
        "redoc",
        help="Generate a Redoc HTML document from OpenAPI input.",
        description="Generate a Redoc HTML document from OpenAPI input.",
    )
    _add_root_out(redoc)
    redoc.add_argument("--title", help="Override document title.")
    redoc.set_defaults(handler=_handle_openapi_redoc)
    openapi.set_defaults(handler=_not_implemented)

    generate = subparsers.add_parser(
        "generate",
        help="Generate language-specific contract libraries.",
        description="Generate language-specific contract libraries.",
    )
    generate_subparsers = generate.add_subparsers(dest="generate_command")

    python_models = generate_subparsers.add_parser(
        "python-models",
        help="Generate Python Pydantic contract models.",
        description="Generate Python Pydantic contract models.",
    )
    _add_root_out(python_models)
    python_models.add_argument("--output-model-type", default="pydantic_v2.BaseModel")
    python_models.add_argument("--target-python-version", default="3.13")
    python_models.add_argument("--disable-timestamp", action="store_true")
    python_models.set_defaults(handler=_handle_generate_python_models)

    ts_types = generate_subparsers.add_parser(
        "ts-types",
        help="Generate TypeScript OpenAPI types using target project openapi-typescript.",
        description="Generate TypeScript OpenAPI types using target project openapi-typescript.",
    )
    ts_types.add_argument("--root", required=True, help="Input OpenAPI YAML or JSON path.")
    ts_types.add_argument("--out", required=True, help="Output TypeScript path.")
    ts_types.add_argument(
        "--package-dir",
        help="Target npm package directory containing openapi-typescript dependency.",
    )
    ts_types.set_defaults(handler=_handle_generate_ts_types)

    zod = generate_subparsers.add_parser(
        "zod-ts",
        help="Generate Zod schemas from OpenAPI input.",
        description="Generate Zod schemas from OpenAPI input.",
    )
    _add_root_out(zod)
    zod.set_defaults(handler=_handle_generate_zod_ts)

    msw = generate_subparsers.add_parser(
        "msw-handlers",
        help="Generate MSW handlers from OpenAPI input.",
        description="Generate MSW handlers from OpenAPI input.",
    )
    _add_root_out(msw)
    msw.set_defaults(handler=_handle_generate_msw_handlers)
    generate.set_defaults(handler=_not_implemented)

    return parser


def _not_implemented(args: argparse.Namespace) -> int:
    del args
    print(
        "This command surface is reserved for profile-driven generation. "
        "Use openapi/* or generate/* subcommands for single-artifact generation."
    )
    return 0


def _handle_build_profile(args: argparse.Namespace) -> int:
    build_from_config(config_path=args.config, profile_name=args.profile, dry_run=args.dry_run)
    return 0


def _handle_openapi_bundle(args: argparse.Namespace) -> int:
    spec = bundle_openapi_spec_for_docs(root_path=Path(args.root))
    out = Path(args.out)
    out.parent.mkdir(parents=True, exist_ok=True)
    out.write_text(json.dumps(spec, ensure_ascii=False, indent=2) + "\n", encoding="utf-8")
    return 0


def _handle_openapi_views(args: argparse.Namespace) -> int:
    openapi_to_uml(
        root_path=args.root,
        out_path=args.uml_out,
        plantuml_jar=None,
        bundle=args.bundle,
        direction=args.direction,
        redoc_out_path=None,
    )
    openapi_to_redoc_html(root_path=args.root, out_path=args.redoc_out, bundle=args.bundle)
    return 0


def _handle_openapi_uml(args: argparse.Namespace) -> int:
    openapi_to_uml(
        root_path=args.root,
        out_path=args.out,
        plantuml_jar=args.plantuml_jar,
        bundle=args.bundle,
        direction=args.direction,
        redoc_out_path=None,
    )
    return 0


def _handle_openapi_redoc(args: argparse.Namespace) -> int:
    openapi_to_redoc_html(root_path=args.root, out_path=args.out, bundle=args.bundle, title=args.title)
    return 0


def _handle_generate_python_models(args: argparse.Namespace) -> int:
    openapi_to_python_models(
        root_path=args.root,
        out_path=args.out,
        bundle=args.bundle,
        output_model_type=args.output_model_type,
        target_python_version=args.target_python_version,
        disable_timestamp=args.disable_timestamp,
    )
    return 0


def _handle_generate_ts_types(args: argparse.Namespace) -> int:
    openapi_to_ts_types(root_path=args.root, out_path=args.out, package_dir=args.package_dir)
    return 0


def _handle_generate_zod_ts(args: argparse.Namespace) -> int:
    openapi_to_zod_ts(root_path=args.root, out_path=args.out, bundle=args.bundle)
    return 0


def _handle_generate_msw_handlers(args: argparse.Namespace) -> int:
    openapi_to_msw_handlers_ts(root_path=args.root, out_path=args.out, bundle=args.bundle)
    return 0


def main(argv: Sequence[str] | None = None) -> int:
    parser = build_parser()
    args = parser.parse_args(argv)
    handler = getattr(args, "handler", None)
    if handler is None:
        parser.print_help()
        return 0
    return int(handler(args))


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