"""Tests for main CLI functionality with Korean locale settings."""

from __future__ import annotations

import json
from argparse import Namespace
from pathlib import Path

import black
import jsonschema
import pydantic
import pytest
from packaging import version

from datamodel_code_generator import MIN_VERSION, chdir, inferred_message
from datamodel_code_generator.__main__ import (
    Exit,
    _generated_files_from_result,
    _generation_output_json,
    _json_ready,
    generate_pyproject_config,
)
from datamodel_code_generator.arguments import arg_parser
from tests.conftest import (
    HttpxGetMockFactory,
    MockHttpxResponse,
    assert_directory_content,
    assert_error_message,
    assert_httpx_get_kwargs,
    assert_output,
    create_assert_file_content,
    freeze_time,
)
from tests.main.conftest import (
    DATA_PATH,
    JSON_SCHEMA_DATA_PATH,
    LEGACY_BLACK_SKIP,
    OPEN_API_DATA_PATH,
    TIMESTAMP,
    run_main_and_assert,
    run_main_url_and_assert,
    run_main_with_args,
)

EXPECTED_MAIN_KR_PATH = DATA_PATH / "expected" / "main_kr"
EXPECTED_OUTPUT_FORMAT_JSON_PATH = EXPECTED_MAIN_KR_PATH / "output_format_json"
EXPECTED_EMPTY_OUTPUT_PATH = DATA_PATH / "expected" / "__init__.py"
GENERATE_PROMPT_JSON_ARGS = [
    "--input",
    "tests/data/jsonschema/person.json",
    "--output-model-type",
    "pydantic_v2.BaseModel",
    "--no-use-annotated",
    "--strict-types",
    "str",
    "int",
    "--generate-prompt",
    "Which strict Pydantic v2 options should I use?",
    "--output-format",
    "json",
]
GENERATE_PROMPT_JSON_SCHEMA_ARGS = ["--output-format-json-schema", "generate-prompt"]

assert_file_content = create_assert_file_content(EXPECTED_MAIN_KR_PATH)


OUTPUT_FILE_PLACEHOLDER = "<OUTPUT_FILE>"
OUTPUT_DIR_PLACEHOLDER = "<OUTPUT_DIR>"


def _normalize_generation_json_output_path(output: str, output_path: Path, placeholder: str) -> str:
    payload = json.loads(output)
    if payload.get("output") == output_path.as_posix():
        payload["output"] = placeholder
    for file_payload in payload["files"]:
        if file_payload["path"] == output_path.as_posix():
            file_payload["path"] = placeholder
    return f"{json.dumps(payload, indent=2, ensure_ascii=False)}\n"


@pytest.fixture(autouse=True)
def reset_namespace(monkeypatch: pytest.MonkeyPatch) -> None:
    """Reset argument namespace before each test."""
    namespace_ = Namespace(no_color=False)
    monkeypatch.setattr("datamodel_code_generator.__main__.namespace", namespace_)
    monkeypatch.setattr("datamodel_code_generator.arguments.namespace", namespace_)


@pytest.fixture
def output_file(tmp_path: Path) -> Path:
    """Return standard output file path."""
    return tmp_path / "output.py"


@pytest.fixture
def output_dir(tmp_path: Path) -> Path:
    """Return standard output directory path."""
    return tmp_path / "model"


@freeze_time("2019-07-26")
def test_main(output_file: Path) -> None:
    """Test basic main function with OpenAPI input."""
    run_main_and_assert(
        input_path=OPEN_API_DATA_PATH / "api.yaml",
        output_path=output_file,
        input_file_type=None,
        assert_func=assert_file_content,
        expected_file="main/output.py",
    )


@freeze_time("2019-07-26")
def test_main_base_class(output_file: Path, tmp_path: Path) -> None:
    """Test main function with custom base class."""
    run_main_and_assert(
        input_path=OPEN_API_DATA_PATH / "api.yaml",
        output_path=output_file,
        input_file_type=None,
        assert_func=assert_file_content,
        expected_file=EXPECTED_MAIN_KR_PATH / "main_base_class" / "output.py",
        extra_args=["--base-class", "custom_module.Base"],
        copy_files=[(DATA_PATH / "pyproject.toml", tmp_path / "pyproject.toml")],
    )


@freeze_time("2019-07-26")
def test_target_python_version(output_file: Path) -> None:
    """Test main function with target Python version."""
    run_main_and_assert(
        input_path=OPEN_API_DATA_PATH / "api.yaml",
        output_path=output_file,
        input_file_type=None,
        assert_func=assert_file_content,
        expected_file=EXPECTED_MAIN_KR_PATH / "target_python_version" / "output.py",
        extra_args=["--target-python-version", f"3.{MIN_VERSION}"],
    )


def test_main_modular(output_dir: Path) -> None:
    """Test main function on modular file."""
    with freeze_time(TIMESTAMP):
        run_main_and_assert(
            input_path=OPEN_API_DATA_PATH / "modular.yaml",
            output_path=output_dir,
            expected_directory=EXPECTED_MAIN_KR_PATH / "main_modular",
        )


@pytest.mark.isolate_builtin_formatter_config
@freeze_time(TIMESTAMP)
def test_main_modular_no_file(capsys: pytest.CaptureFixture[str]) -> None:
    """Test main function on modular file with no output name outputs to stdout."""
    run_main_with_args(
        ["--input", str(OPEN_API_DATA_PATH / "modular.yaml")],
        expected_exit=Exit.OK,
        capsys=capsys,
        expected_stdout_path=EXPECTED_MAIN_KR_PATH / "main_modular_no_file" / "output.py",
    )


def test_main_modular_filename(output_file: Path) -> None:
    """Test main function on modular file with filename."""
    run_main_and_assert(
        input_path=OPEN_API_DATA_PATH / "modular.yaml",
        output_path=output_file,
        expected_exit=Exit.ERROR,
    )


@pytest.mark.isolate_builtin_formatter_config
def test_main_no_file(capsys: pytest.CaptureFixture[str], tmp_path: Path, monkeypatch: pytest.MonkeyPatch) -> None:
    """Test main function on non-modular file with no output name."""
    monkeypatch.chdir(tmp_path)

    with freeze_time(TIMESTAMP):
        run_main_and_assert(
            input_path=OPEN_API_DATA_PATH / "api.yaml",
            output_path=None,
            expected_stdout_path=EXPECTED_MAIN_KR_PATH / "main_no_file" / "output.py",
            capsys=capsys,
            expected_stderr=inferred_message.format("openapi") + "\n",
        )


@pytest.mark.isolate_builtin_formatter_config
def test_main_custom_template_dir(
    capsys: pytest.CaptureFixture[str], tmp_path: Path, monkeypatch: pytest.MonkeyPatch
) -> None:
    """Test main function with custom template directory."""
    monkeypatch.chdir(tmp_path)

    custom_template_dir = DATA_PATH / "templates"
    extra_template_data = OPEN_API_DATA_PATH / "extra_data.json"

    with freeze_time(TIMESTAMP):
        run_main_and_assert(
            input_path=OPEN_API_DATA_PATH / "api.yaml",
            output_path=None,
            expected_stdout_path=EXPECTED_MAIN_KR_PATH / "main_custom_template_dir" / "output.py",
            capsys=capsys,
            extra_args=[
                "--custom-template-dir",
                str(custom_template_dir),
                "--extra-template-data",
                str(extra_template_data),
            ],
            expected_stderr=inferred_message.format("openapi") + "\n",
        )


@pytest.mark.skipif(
    black.__version__.split(".")[0] >= "24",
    reason="Installed black doesn't support the old style",
)
@freeze_time("2019-07-26")
def test_pyproject(output_file: Path, tmp_path: Path) -> None:
    """Test main function with pyproject.toml configuration."""
    run_main_and_assert(
        input_path=OPEN_API_DATA_PATH / "api.yaml",
        output_path=output_file,
        input_file_type=None,
        assert_func=assert_file_content,
        expected_file="pyproject/output.py",
        copy_files=[(DATA_PATH / "project" / "pyproject.toml", tmp_path / "pyproject.toml")],
    )


@pytest.mark.parametrize("language", ["UK", "US"])
def test_pyproject_respects_both_spellings_of_capitalize_enum_members_flag(language: str, tmp_path: Path) -> None:
    """Test that both UK and US spellings of capitalise are accepted."""
    pyproject_toml_data = f"""
[tool.datamodel-codegen]
capitali{"s" if language == "UK" else "z"}e-enum-members = true
enable-version-header = false
input-file-type = "jsonschema"
"""
    with (tmp_path / "pyproject.toml").open("w") as f:
        f.write(pyproject_toml_data)

        input_data = """
{
  "$schema": "http://json-schema.org/draft-07/schema#",
  "definitions": {
    "MyEnum": {
      "enum": [
        "MEMBER_1",
        "member_2"
      ]
    }
  }
}
"""
    input_file = tmp_path / "schema.json"
    with input_file.open("w") as f:
        f.write(input_data)

    expected_output = """# generated by datamodel-codegen:
#   filename:  schema.json

from __future__ import annotations

from enum import Enum
from typing import Any

from pydantic import RootModel


class Model(RootModel[Any]):
    root: Any


class MyEnum(Enum):
    MEMBER_1 = 'MEMBER_1'
    member_2 = 'member_2'
"""

    output_file: Path = tmp_path / "output.py"
    run_main_and_assert(
        input_path=input_file,
        output_path=output_file,
        expected_output=expected_output,
        extra_args=["--disable-timestamp"],
    )


@pytest.mark.skipif(
    black.__version__.split(".")[0] == "19",
    reason="Installed black doesn't support the old style",
)
@freeze_time("2019-07-26")
def test_pyproject_with_tool_section(output_file: Path, tmp_path: Path) -> None:
    """Test that a pyproject.toml with [tool.datamodel-codegen] section is found and applied."""
    pyproject_toml = """
[tool.datamodel-codegen]
target-python-version = "3.10"
strict-types = ["str"]
"""
    (tmp_path / "pyproject.toml").write_text(pyproject_toml)

    with chdir(tmp_path):
        run_main_and_assert(
            input_path=(OPEN_API_DATA_PATH / "api.yaml").resolve(),
            output_path=output_file.resolve(),
            input_file_type=None,
            assert_func=assert_file_content,
            expected_file=EXPECTED_MAIN_KR_PATH / "pyproject" / "output.strictstr.py",
        )


@pytest.mark.cli_doc(
    options=["--use-schema-description"],
    option_description="""Use schema description as class docstring.

The `--use-schema-description` flag extracts the `description` property from
schema definitions and adds it as a docstring to the generated class. This is
useful for preserving documentation from your schema in the generated code.""",
    input_schema="openapi/api_multiline_docstrings.yaml",
    cli_args=["--use-schema-description"],
    golden_output="main_kr/main_use_schema_description/output.py",
    related_options=["--use-field-description", "--use-inline-field-description"],
)
@freeze_time("2019-07-26")
def test_main_use_schema_description(output_file: Path) -> None:
    """Use schema description as class docstring.

    The `--use-schema-description` flag extracts the `description` property from
    schema definitions and adds it as a docstring to the generated class. This is
    useful for preserving documentation from your schema in the generated code.
    """
    run_main_and_assert(
        input_path=OPEN_API_DATA_PATH / "api_multiline_docstrings.yaml",
        output_path=output_file,
        input_file_type=None,
        assert_func=assert_file_content,
        expected_file=EXPECTED_MAIN_KR_PATH / "main_use_schema_description" / "output.py",
        extra_args=["--use-schema-description"],
    )


@freeze_time("2019-07-26")
def test_main_docstring_special_chars(output_file: Path) -> None:
    """Escape special characters in docstrings.

    Backslashes and triple quotes in schema descriptions must be escaped
    to prevent Python syntax errors and type checker warnings. See GitHub
    issue #1808.
    """
    run_main_and_assert(
        input_path=OPEN_API_DATA_PATH / "docstring_special_chars.yaml",
        output_path=output_file,
        input_file_type=None,
        assert_func=assert_file_content,
        expected_file=EXPECTED_MAIN_KR_PATH / "main_docstring_special_chars" / "output.py",
        extra_args=["--use-schema-description", "--use-field-description"],
    )


@pytest.mark.cli_doc(
    options=["--use-field-description"],
    option_description="""Add field descriptions using Pydantic Field().

The `--use-field-description` flag adds the `description` property from
schema fields as the `description` parameter in Pydantic Field(). This
provides documentation that is accessible via model schema and OpenAPI docs.""",
    input_schema="openapi/api_multiline_docstrings.yaml",
    cli_args=["--use-field-description"],
    golden_output="main_kr/main_use_field_description/output.py",
    related_options=["--use-schema-description", "--use-inline-field-description"],
)
@freeze_time("2022-11-11")
def test_main_use_field_description(output_file: Path) -> None:
    """Add field descriptions using Pydantic Field().

    The `--use-field-description` flag adds the `description` property from
    schema fields as the `description` parameter in Pydantic Field(). This
    provides documentation that is accessible via model schema and OpenAPI docs.
    """
    run_main_and_assert(
        input_path=OPEN_API_DATA_PATH / "api_multiline_docstrings.yaml",
        output_path=output_file,
        input_file_type=None,
        assert_func=assert_file_content,
        expected_file=EXPECTED_MAIN_KR_PATH / "main_use_field_description" / "output.py",
        extra_args=["--use-field-description"],
    )


@pytest.mark.cli_doc(
    options=["--use-inline-field-description"],
    option_description="""Add field descriptions as inline comments.

The `--use-inline-field-description` flag adds the `description` property from
schema fields as inline comments after each field definition. This provides
documentation without using Field() wrappers.""",
    input_schema="openapi/api_multiline_docstrings.yaml",
    cli_args=["--use-inline-field-description"],
    golden_output="main_kr/main_use_inline_field_description/output.py",
    related_options=["--use-field-description", "--use-schema-description"],
)
@freeze_time("2022-11-11")
def test_main_use_inline_field_description(output_file: Path) -> None:
    """Add field descriptions as inline comments.

    The `--use-inline-field-description` flag adds the `description` property from
    schema fields as inline comments after each field definition. This provides
    documentation without using Field() wrappers.
    """
    run_main_and_assert(
        input_path=OPEN_API_DATA_PATH / "api_multiline_docstrings.yaml",
        output_path=output_file,
        input_file_type=None,
        assert_func=assert_file_content,
        expected_file=EXPECTED_MAIN_KR_PATH / "main_use_inline_field_description" / "output.py",
        extra_args=["--use-inline-field-description"],
    )


@pytest.mark.cli_doc(
    options=["--use-field-description-example"],
    option_description="""Add field examples to docstrings.

The `--use-field-description-example` flag adds the `example` or `examples`
property from schema fields as docstrings. This provides documentation that
is visible in IDE intellisense.""",
    input_schema="jsonschema/extras.json",
    cli_args=["--use-field-description-example"],
    golden_output="main_kr/main_use_field_description_example/output.py",
    related_options=["--use-field-description", "--use-inline-field-description"],
)
@freeze_time("2022-11-11")
def test_main_use_field_description_example(output_file: Path) -> None:
    """Add field examples to docstrings.

    The `--use-field-description-example` flag adds the `example` or `examples`
    property from schema fields as docstrings. This provides documentation that
    is visible in IDE intellisense.
    """
    run_main_and_assert(
        input_path=JSON_SCHEMA_DATA_PATH / "extras.json",
        output_path=output_file,
        input_file_type=None,
        assert_func=assert_file_content,
        expected_file=EXPECTED_MAIN_KR_PATH / "main_use_field_description_example" / "output.py",
        extra_args=["--use-field-description-example"],
    )


@freeze_time("2022-11-11")
def test_main_use_field_description_example_dataclass(output_file: Path) -> None:
    """Test single example docstrings with dataclass output."""
    run_main_and_assert(
        input_path=JSON_SCHEMA_DATA_PATH / "single_line_description_with_example.json",
        output_path=output_file,
        input_file_type=None,
        assert_func=assert_file_content,
        expected_file=EXPECTED_MAIN_KR_PATH / "main_use_field_description_example_dataclass" / "output.py",
        extra_args=["--use-field-description-example", "--output-model-type", "dataclasses.dataclass"],
    )


@pytest.mark.cli_doc(
    options=["--use-field-description", "--use-field-description-example"],
    option_description="""Add field descriptions and examples to docstrings.

When both `--use-field-description` and `--use-field-description-example` are used,
the docstring includes both the description and example(s).""",
    input_schema="jsonschema/extras.json",
    cli_args=["--use-field-description", "--use-field-description-example"],
    golden_output="main_kr/main_use_field_description_with_example/output.py",
    related_options=["--use-inline-field-description"],
)
@freeze_time("2022-11-11")
def test_main_use_field_description_with_example(output_file: Path) -> None:
    """Add field descriptions and examples to docstrings.

    When both `--use-field-description` and `--use-field-description-example` are used,
    the docstring includes both the description and example(s).
    """
    run_main_and_assert(
        input_path=JSON_SCHEMA_DATA_PATH / "extras.json",
        output_path=output_file,
        input_file_type=None,
        assert_func=assert_file_content,
        expected_file=EXPECTED_MAIN_KR_PATH / "main_use_field_description_with_example" / "output.py",
        extra_args=["--use-field-description", "--use-field-description-example"],
    )


@pytest.mark.cli_doc(
    options=["--use-inline-field-description", "--use-field-description-example"],
    option_description="""Add field descriptions and examples to docstrings with inline description.

When both `--use-inline-field-description` and `--use-field-description-example` are used,
multi-line descriptions and examples are included in the docstring.""",
    input_schema="jsonschema/multiline_description_with_example.json",
    cli_args=["--use-inline-field-description", "--use-field-description-example"],
    golden_output="main_kr/main_use_inline_field_description_with_example/output.py",
    related_options=["--use-field-description"],
)
@freeze_time("2022-11-11")
def test_main_use_inline_field_description_with_example(output_file: Path) -> None:
    """Add field descriptions and examples to docstrings with inline description.

    When both `--use-inline-field-description` and `--use-field-description-example` are used,
    multi-line descriptions and examples are included in the docstring.
    """
    run_main_and_assert(
        input_path=JSON_SCHEMA_DATA_PATH / "multiline_description_with_example.json",
        output_path=output_file,
        input_file_type=None,
        assert_func=assert_file_content,
        expected_file=EXPECTED_MAIN_KR_PATH / "main_use_inline_field_description_with_example" / "output.py",
        extra_args=["--use-inline-field-description", "--use-field-description-example"],
    )


@freeze_time("2022-11-11")
def test_main_use_inline_field_description_example_only(output_file: Path) -> None:
    """Test single-line description with use_inline_field_description and use_field_description_example.

    When both flags are used with a single-line description, only the example
    appears in the docstring (the single-line description stays in Field()).
    """
    run_main_and_assert(
        input_path=JSON_SCHEMA_DATA_PATH / "single_line_description_with_example.json",
        output_path=output_file,
        input_file_type=None,
        assert_func=assert_file_content,
        expected_file=EXPECTED_MAIN_KR_PATH / "main_use_inline_field_description_example_only" / "output.py",
        extra_args=["--use-inline-field-description", "--use-field-description-example"],
    )


@freeze_time("2022-11-11")
def test_main_use_field_description_example_multiple(output_file: Path) -> None:
    """Test multiple examples in docstring.

    When a field has multiple examples, they are formatted as a bulleted list.
    """
    run_main_and_assert(
        input_path=JSON_SCHEMA_DATA_PATH / "multiple_examples.json",
        output_path=output_file,
        input_file_type=None,
        assert_func=assert_file_content,
        expected_file=EXPECTED_MAIN_KR_PATH / "main_use_field_description_example_multiple" / "output.py",
        extra_args=["--use-field-description-example"],
    )


def test_capitalise_enum_members(tmp_path: Path) -> None:
    """Test capitalise-enum-members option (issue #2370)."""
    input_data = """
openapi: 3.0.3
info:
  version: X.Y.Z
  title: example schema
servers:
  - url: "https://acme.org"
paths: {}
components:
  schemas:
    EnumSystems:
      type: enum
      enum:
        - linux
        - osx
        - windows
"""
    input_file = tmp_path / "myschema.yaml"
    input_file.write_text(input_data, encoding="utf_8")

    expected_output = """# generated by datamodel-codegen:
#   filename:  myschema.yaml

from __future__ import annotations

from enum import Enum


class EnumSystems(Enum):
    LINUX = 'linux'
    OSX = 'osx'
    WINDOWS = 'windows'
"""

    output_file: Path = tmp_path / "output.py"
    run_main_and_assert(
        input_path=input_file,
        output_path=output_file,
        expected_output=expected_output,
        extra_args=[
            "--output-model-type",
            "pydantic_v2.BaseModel",
            "--disable-timestamp",
            "--capitalise-enum-members",
            "--snake-case-field",
        ],
    )


def test_capitalise_enum_members_and_use_subclass_enum(tmp_path: Path) -> None:
    """Test combination of capitalise-enum-members and use-subclass-enum (issue #2395)."""
    input_data = """
openapi: 3.0.3
info:
  version: X.Y.Z
  title: example schema
servers:
  - url: "https://acme.org"
paths: {}
components:
  schemas:
    EnumSystems:
      type: string
      enum:
        - linux
        - osx
        - windows
"""
    input_file = tmp_path / "myschema.yaml"
    input_file.write_text(input_data, encoding="utf_8")

    expected_output = """# generated by datamodel-codegen:
#   filename:  myschema.yaml

from __future__ import annotations

from enum import Enum


class EnumSystems(str, Enum):
    LINUX = 'linux'
    OSX = 'osx'
    WINDOWS = 'windows'
"""

    output_file: Path = tmp_path / "output.py"
    run_main_and_assert(
        input_path=input_file,
        output_path=output_file,
        expected_output=expected_output,
        extra_args=[
            "--output-model-type",
            "pydantic_v2.BaseModel",
            "--disable-timestamp",
            "--capitalise-enum-members",
            "--snake-case-field",
            "--use-subclass-enum",
        ],
    )


def test_capitalise_enum_members_builtin_conflict(output_file: Path) -> None:
    """Test capitalise-enum-members does not add underscore to builtin names (#2970)."""
    run_main_and_assert(
        input_path=JSON_SCHEMA_DATA_PATH / "enum_builtin_conflict.json",
        output_path=output_file,
        assert_func=assert_file_content,
        input_file_type="jsonschema",
        extra_args=[
            "--output-model-type",
            "pydantic_v2.BaseModel",
            "--disable-timestamp",
            "--capitalise-enum-members",
        ],
    )


def test_capitalise_enum_members_and_use_subclass_enum_builtin_conflict(output_file: Path) -> None:
    """Test capitalise-enum-members + use-subclass-enum with builtin names (#2970)."""
    run_main_and_assert(
        input_path=JSON_SCHEMA_DATA_PATH / "enum_builtin_conflict_two.json",
        output_path=output_file,
        assert_func=assert_file_content,
        input_file_type="jsonschema",
        extra_args=[
            "--output-model-type",
            "pydantic_v2.BaseModel",
            "--disable-timestamp",
            "--capitalise-enum-members",
            "--use-subclass-enum",
        ],
    )


def test_use_subclass_enum_builtin_conflict_no_capitalise(output_file: Path) -> None:
    """Test use-subclass-enum without capitalise adds underscore for builtin conflicts."""
    run_main_and_assert(
        input_path=JSON_SCHEMA_DATA_PATH / "enum_builtin_conflict_two.json",
        output_path=output_file,
        assert_func=assert_file_content,
        input_file_type="jsonschema",
        extra_args=[
            "--output-model-type",
            "pydantic_v2.BaseModel",
            "--disable-timestamp",
            "--use-subclass-enum",
        ],
    )


def test_no_subclass_enum_no_capitalise_builtin_names(output_file: Path) -> None:
    """Test default behavior with builtin names has no underscore suffix."""
    run_main_and_assert(
        input_path=JSON_SCHEMA_DATA_PATH / "enum_builtin_conflict_two.json",
        output_path=output_file,
        assert_func=assert_file_content,
        input_file_type="jsonschema",
        extra_args=[
            "--output-model-type",
            "pydantic_v2.BaseModel",
            "--disable-timestamp",
        ],
    )


EXPECTED_GENERATE_PYPROJECT_CONFIG_PATH = EXPECTED_MAIN_KR_PATH / "generate_pyproject_config"


@pytest.mark.cli_doc(
    options=["--generate-pyproject-config"],
    option_description="""Generate pyproject.toml configuration from CLI arguments.

The `--generate-pyproject-config` flag outputs a pyproject.toml configuration
snippet based on the provided CLI arguments. This is useful for converting
a working CLI command into a reusable configuration file.""",
    cli_args=["--generate-pyproject-config", "--input", "schema.yaml", "--output", "model.py"],
    expected_stdout="main_kr/generate_pyproject_config/basic.txt",
)
def test_generate_pyproject_config_basic(capsys: pytest.CaptureFixture[str]) -> None:
    """Generate pyproject.toml configuration from CLI arguments.

    The `--generate-pyproject-config` flag outputs a pyproject.toml configuration
    snippet based on the provided CLI arguments. This is useful for converting
    a working CLI command into a reusable configuration file.
    """
    run_main_with_args(
        [
            "--generate-pyproject-config",
            "--input",
            "schema.yaml",
            "--output",
            "model.py",
        ],
        capsys=capsys,
        expected_stdout_path=EXPECTED_GENERATE_PYPROJECT_CONFIG_PATH / "basic.txt",
    )


def test_generate_pyproject_config_with_boolean_options(capsys: pytest.CaptureFixture[str]) -> None:
    """Test --generate-pyproject-config with boolean options."""
    run_main_with_args(
        [
            "--generate-pyproject-config",
            "--snake-case-field",
            "--use-annotated",
            "--collapse-root-models",
        ],
        capsys=capsys,
        expected_stdout_path=EXPECTED_GENERATE_PYPROJECT_CONFIG_PATH / "boolean_options.txt",
    )


def test_generate_pyproject_config_with_list_options(capsys: pytest.CaptureFixture[str]) -> None:
    """Test --generate-pyproject-config with list options."""
    run_main_with_args(
        [
            "--generate-pyproject-config",
            "--strict-types",
            "str",
            "int",
        ],
        capsys=capsys,
        expected_stdout_path=EXPECTED_GENERATE_PYPROJECT_CONFIG_PATH / "list_options.txt",
    )


def test_generate_pyproject_config_with_multiple_options(capsys: pytest.CaptureFixture[str]) -> None:
    """Test --generate-pyproject-config with various option types."""
    run_main_with_args(
        [
            "--generate-pyproject-config",
            "--input",
            "schema.yaml",
            "--output",
            "model.py",
            "--output-model-type",
            "pydantic_v2.BaseModel",
            "--target-python-version",
            "3.11",
            "--snake-case-field",
            "--strict-types",
            "str",
            "bytes",
        ],
        capsys=capsys,
        expected_stdout_path=EXPECTED_GENERATE_PYPROJECT_CONFIG_PATH / "multiple_options.txt",
    )


def test_generate_pyproject_config_excludes_meta_options(capsys: pytest.CaptureFixture[str]) -> None:
    """Test that meta options are excluded from generated config."""
    run_main_with_args(
        [
            "--generate-pyproject-config",
            "--input",
            "schema.yaml",
        ],
        capsys=capsys,
        expected_stdout_path=EXPECTED_GENERATE_PYPROJECT_CONFIG_PATH / "excludes_meta_options.txt",
    )


def test_generate_pyproject_config_with_enum_option(capsys: pytest.CaptureFixture[str]) -> None:
    """Test --generate-pyproject-config with Enum option."""
    run_main_with_args(
        [
            "--generate-pyproject-config",
            "--input",
            "schema.yaml",
            "--read-only-write-only-model-type",
            "all",
        ],
        capsys=capsys,
        expected_stdout_path=EXPECTED_GENERATE_PYPROJECT_CONFIG_PATH / "enum_option.txt",
    )


def test_generate_pyproject_config_escapes_toml_strings(capsys: pytest.CaptureFixture[str]) -> None:
    """Test --generate-pyproject-config escapes special characters in TOML basic strings."""
    run_main_with_args(
        [
            "--generate-pyproject-config",
            "--input",
            "schema.yaml",
            "--custom-file-header",
            'say "hi" on C:\\tmp\nnext',
            "--http-headers",
            'Authorization: Bearer "abc"',
            "X-Path: C:\\tmp",
        ],
        capsys=capsys,
        expected_stdout_path=EXPECTED_GENERATE_PYPROJECT_CONFIG_PATH / "escapes_toml_strings.txt",
    )


EXPECTED_GENERATE_CLI_COMMAND_PATH = EXPECTED_MAIN_KR_PATH / "generate_cli_command"


@pytest.mark.cli_doc(
    options=["--generate-cli-command"],
    option_description="""Generate CLI command from pyproject.toml configuration.

The `--generate-cli-command` flag reads your pyproject.toml configuration
and outputs the equivalent CLI command. This is useful for debugging
configuration issues or sharing commands with others.""",
    cli_args=["--generate-cli-command"],
    config_content="""[tool.datamodel-codegen]
input = "schema.yaml"
output = "model.py"
""",
    expected_stdout="main_kr/generate_cli_command/basic.txt",
)
def test_generate_cli_command_basic(tmp_path: Path, capsys: pytest.CaptureFixture[str]) -> None:
    """Generate CLI command from pyproject.toml configuration.

    The `--generate-cli-command` flag reads your pyproject.toml configuration
    and outputs the equivalent CLI command. This is useful for debugging
    configuration issues or sharing commands with others.
    """
    pyproject_toml = """
[tool.datamodel-codegen]
input = "schema.yaml"
output = "model.py"
"""
    (tmp_path / "pyproject.toml").write_text(pyproject_toml)

    with chdir(tmp_path):
        run_main_with_args(
            ["--generate-cli-command"],
            capsys=capsys,
            expected_stdout_path=EXPECTED_GENERATE_CLI_COMMAND_PATH / "basic.txt",
        )


def test_generate_cli_command_with_boolean_options(tmp_path: Path, capsys: pytest.CaptureFixture[str]) -> None:
    """Test --generate-cli-command with boolean options."""
    pyproject_toml = """
[tool.datamodel-codegen]
snake-case-field = true
use-annotated = true
collapse-root-models = true
"""
    (tmp_path / "pyproject.toml").write_text(pyproject_toml)

    with chdir(tmp_path):
        run_main_with_args(
            ["--generate-cli-command"],
            capsys=capsys,
            expected_stdout_path=EXPECTED_GENERATE_CLI_COMMAND_PATH / "boolean_options.txt",
        )


def test_generate_cli_command_with_list_options(tmp_path: Path, capsys: pytest.CaptureFixture[str]) -> None:
    """Test --generate-cli-command with list options."""
    pyproject_toml = """
[tool.datamodel-codegen]
strict-types = ["str", "int"]
"""
    (tmp_path / "pyproject.toml").write_text(pyproject_toml)

    with chdir(tmp_path):
        run_main_with_args(
            ["--generate-cli-command"],
            capsys=capsys,
            expected_stdout_path=EXPECTED_GENERATE_CLI_COMMAND_PATH / "list_options.txt",
        )


def test_generate_cli_command_with_multiple_options(tmp_path: Path, capsys: pytest.CaptureFixture[str]) -> None:
    """Test --generate-cli-command with various option types."""
    pyproject_toml = """
[tool.datamodel-codegen]
input = "schema.yaml"
output = "model.py"
output-model-type = "pydantic_v2.BaseModel"
target-python-version = "3.11"
snake-case-field = true
strict-types = ["str", "bytes"]
"""
    (tmp_path / "pyproject.toml").write_text(pyproject_toml)

    with chdir(tmp_path):
        run_main_with_args(
            ["--generate-cli-command"],
            capsys=capsys,
            expected_stdout_path=EXPECTED_GENERATE_CLI_COMMAND_PATH / "multiple_options.txt",
        )


def test_generate_cli_command_no_config(tmp_path: Path) -> None:
    """Test --generate-cli-command when no config found."""
    with chdir(tmp_path):
        run_main_with_args(
            ["--generate-cli-command"],
            expected_exit=Exit.ERROR,
        )


def test_generate_cli_command_with_no_use_specialized_enum(tmp_path: Path, capsys: pytest.CaptureFixture[str]) -> None:
    """Test --generate-cli-command with use-specialized-enum = false."""
    pyproject_toml = """
[tool.datamodel-codegen]
input = "schema.yaml"
use-specialized-enum = false
"""
    (tmp_path / "pyproject.toml").write_text(pyproject_toml)

    with chdir(tmp_path):
        run_main_with_args(
            ["--generate-cli-command"],
            capsys=capsys,
            expected_stdout_path=EXPECTED_GENERATE_CLI_COMMAND_PATH / "no_use_specialized_enum.txt",
        )


def test_generate_cli_command_with_spaces_in_values(tmp_path: Path, capsys: pytest.CaptureFixture[str]) -> None:
    """Test --generate-cli-command with spaces in values."""
    pyproject_toml = """
[tool.datamodel-codegen]
input = "my schema.yaml"
output = "my model.py"
http-headers = ["Authorization: Bearer token", "X-Custom: value"]
"""
    (tmp_path / "pyproject.toml").write_text(pyproject_toml)

    with chdir(tmp_path):
        run_main_with_args(
            ["--generate-cli-command"],
            capsys=capsys,
            expected_stdout_path=EXPECTED_GENERATE_CLI_COMMAND_PATH / "spaces_in_values.txt",
        )


def test_generate_cli_command_with_false_boolean(tmp_path: Path, capsys: pytest.CaptureFixture[str]) -> None:
    """Test --generate-cli-command with regular boolean set to false (should be skipped)."""
    pyproject_toml = """
[tool.datamodel-codegen]
input = "schema.yaml"
snake-case-field = false
"""
    (tmp_path / "pyproject.toml").write_text(pyproject_toml)

    with chdir(tmp_path):
        run_main_with_args(
            ["--generate-cli-command"],
            capsys=capsys,
            expected_stdout_path=EXPECTED_GENERATE_CLI_COMMAND_PATH / "false_boolean.txt",
        )


def test_generate_cli_command_excludes_excluded_options(tmp_path: Path, capsys: pytest.CaptureFixture[str]) -> None:
    """Test --generate-cli-command excludes options like debug, version, etc."""
    pyproject_toml = """
[tool.datamodel-codegen]
input = "schema.yaml"
debug = true
version = true
no-color = true
disable-warnings = true
"""
    (tmp_path / "pyproject.toml").write_text(pyproject_toml)

    with chdir(tmp_path):
        run_main_with_args(
            ["--generate-cli-command"],
            capsys=capsys,
            expected_stdout_path=EXPECTED_GENERATE_CLI_COMMAND_PATH / "excluded_options.txt",
        )


EXPECTED_PYPROJECT_PROFILE_PATH = EXPECTED_MAIN_KR_PATH / "pyproject_profile"


@pytest.mark.skipif(
    version.parse(black.__version__) < version.parse("23.0.0"),
    reason="black 22.x doesn't support Python 3.11 target version",
)
@freeze_time("2019-07-26")
def test_pyproject_with_profile(output_file: Path, tmp_path: Path) -> None:
    """Test loading a named profile from pyproject.toml."""
    pyproject_toml = """
[tool.datamodel-codegen]
target-python-version = "3.10"
enable-version-header = false

[tool.datamodel-codegen.profiles.api]
target-python-version = "3.11"
snake-case-field = true
"""
    (tmp_path / "pyproject.toml").write_text(pyproject_toml)

    input_data = """
{
  "$schema": "http://json-schema.org/draft-07/schema#",
  "type": "object",
  "properties": {
    "firstName": {"type": "string"},
    "lastName": {"type": "string"}
  }
}
"""
    input_file = tmp_path / "schema.json"
    input_file.write_text(input_data)

    with chdir(tmp_path):
        run_main_and_assert(
            input_path=input_file,
            output_path=output_file.resolve(),
            assert_func=assert_file_content,
            expected_file=EXPECTED_PYPROJECT_PROFILE_PATH / "with_profile.py",
            extra_args=["--profile", "api", "--disable-timestamp"],
        )


def test_pyproject_profile_not_found(tmp_path: Path, capsys: pytest.CaptureFixture[str]) -> None:
    """Test error when profile is not found."""
    pyproject_toml = """
[tool.datamodel-codegen]
target-python-version = "3.10"
"""
    (tmp_path / "pyproject.toml").write_text(pyproject_toml)

    input_file = tmp_path / "schema.json"
    input_file.write_text('{"type": "object"}')

    output_file = tmp_path / "output.py"

    with chdir(tmp_path):
        run_main_with_args(
            ["--input", str(input_file), "--output", str(output_file), "--profile", "nonexistent"],
            expected_exit=Exit.ERROR,
            capsys=capsys,
        )
        assert_error_message(capsys, "Profile 'nonexistent' not found in pyproject.toml")


@freeze_time("2019-07-26")
def test_ignore_pyproject_option(output_file: Path, tmp_path: Path) -> None:
    """Test --ignore-pyproject ignores pyproject.toml configuration."""
    pyproject_toml = """
[tool.datamodel-codegen]
snake-case-field = true
enable-version-header = true
"""
    (tmp_path / "pyproject.toml").write_text(pyproject_toml)

    input_data = """
{
  "$schema": "http://json-schema.org/draft-07/schema#",
  "type": "object",
  "properties": {
    "firstName": {"type": "string"},
    "lastName": {"type": "string"}
  }
}
"""
    input_file = tmp_path / "schema.json"
    input_file.write_text(input_data)

    with chdir(tmp_path):
        run_main_and_assert(
            input_path=input_file,
            output_path=output_file.resolve(),
            assert_func=assert_file_content,
            expected_file=EXPECTED_PYPROJECT_PROFILE_PATH / "ignore_pyproject.py",
            extra_args=["--ignore-pyproject", "--disable-timestamp"],
        )


@freeze_time("2019-07-26")
def test_profile_overrides_base_config_shallow_merge(output_file: Path, tmp_path: Path) -> None:
    """Test that profile settings shallow-merge (replace) base settings for lists."""
    pyproject_toml = """
[tool.datamodel-codegen]
strict-types = ["str", "int"]
target-python-version = "3.10"
enable-version-header = false

[tool.datamodel-codegen.profiles.api]
strict-types = ["bytes"]
"""
    (tmp_path / "pyproject.toml").write_text(pyproject_toml)

    input_data = """
{
  "$schema": "http://json-schema.org/draft-07/schema#",
  "type": "object",
  "properties": {
    "data": {"type": "string", "format": "binary"}
  }
}
"""
    input_file = tmp_path / "schema.json"
    input_file.write_text(input_data)

    with chdir(tmp_path):
        run_main_and_assert(
            input_path=input_file,
            output_path=output_file.resolve(),
            assert_func=assert_file_content,
            expected_file=EXPECTED_PYPROJECT_PROFILE_PATH / "shallow_merge.py",
            extra_args=["--profile", "api", "--disable-timestamp"],
        )


def test_generate_cli_command_with_profile(tmp_path: Path, capsys: pytest.CaptureFixture[str]) -> None:
    """Test --generate-cli-command reflects merged profile settings."""
    pyproject_toml = """
[tool.datamodel-codegen]
target-python-version = "3.10"
snake-case-field = true

[tool.datamodel-codegen.profiles.api]
input = "api.yaml"
target-python-version = "3.11"
"""
    (tmp_path / "pyproject.toml").write_text(pyproject_toml)

    with chdir(tmp_path):
        run_main_with_args(
            ["--profile", "api", "--generate-cli-command"],
            capsys=capsys,
            expected_stdout_path=EXPECTED_GENERATE_CLI_COMMAND_PATH / "with_profile.txt",
        )


def test_help_shows_new_options() -> None:
    """Test that --profile and --ignore-pyproject appear in help."""
    assert_output(arg_parser.format_help(), EXPECTED_MAIN_KR_PATH / "help_shows_new_options.txt")


@pytest.mark.skipif(
    version.parse(black.__version__) < version.parse("23.0.0"),
    reason="black 22.x doesn't support Python 3.11 target version",
)
def test_pyproject_profile_inherits_base_settings(output_file: Path, tmp_path: Path) -> None:
    """Test that profile inherits settings from base config."""
    pyproject_toml = """
[tool.datamodel-codegen]
snake-case-field = true
enable-version-header = false

[tool.datamodel-codegen.profiles.api]
target-python-version = "3.11"
"""
    (tmp_path / "pyproject.toml").write_text(pyproject_toml)

    input_data = """
{
  "$schema": "http://json-schema.org/draft-07/schema#",
  "type": "object",
  "properties": {
    "firstName": {"type": "string"}
  }
}
"""
    input_file = tmp_path / "schema.json"
    input_file.write_text(input_data)

    with chdir(tmp_path):
        run_main_and_assert(
            input_path=input_file,
            output_path=output_file.resolve(),
            assert_func=assert_file_content,
            expected_file=EXPECTED_PYPROJECT_PROFILE_PATH / "inherits_base.py",
            extra_args=["--profile", "api", "--disable-timestamp"],
        )


@pytest.mark.skipif(
    version.parse(black.__version__) < version.parse("23.0.0"),
    reason="black 22.x doesn't support Python 3.11 target version",
)
@freeze_time("2019-07-26")
def test_cli_args_override_profile_and_base(output_file: Path, tmp_path: Path) -> None:
    """Test that CLI arguments take precedence over profile and base settings."""
    pyproject_toml = """
[tool.datamodel-codegen]
target-python-version = "3.10"
enable-version-header = false

[tool.datamodel-codegen.profiles.api]
target-python-version = "3.11"
"""
    (tmp_path / "pyproject.toml").write_text(pyproject_toml)

    input_data = """
{
  "$schema": "http://json-schema.org/draft-07/schema#",
  "type": "object",
  "properties": {
    "firstName": {"type": "string"}
  }
}
"""
    input_file = tmp_path / "schema.json"
    input_file.write_text(input_data)

    with chdir(tmp_path):
        run_main_and_assert(
            input_path=input_file,
            output_path=output_file.resolve(),
            assert_func=assert_file_content,
            expected_file=EXPECTED_PYPROJECT_PROFILE_PATH / "cli_override.py",
            extra_args=[
                "--profile",
                "api",
                "--disable-timestamp",
                "--target-python-version",
                "3.11",
                "--use-union-operator",
            ],
        )


def test_ignore_pyproject_with_profile(tmp_path: Path) -> None:
    """Test that --ignore-pyproject ignores --profile as well."""
    pyproject_toml = """
[tool.datamodel-codegen]
snake-case-field = true

[tool.datamodel-codegen.profiles.api]
target-python-version = "3.11"
"""
    (tmp_path / "pyproject.toml").write_text(pyproject_toml)

    input_data = """
{
  "$schema": "http://json-schema.org/draft-07/schema#",
  "type": "object",
  "properties": {
    "firstName": {"type": "string"}
  }
}
"""
    input_file = tmp_path / "schema.json"
    input_file.write_text(input_data)
    output_file = tmp_path / "output.py"

    with chdir(tmp_path):
        run_main_with_args(
            [
                "--input",
                str(input_file),
                "--output",
                str(output_file),
                "--ignore-pyproject",
                "--profile",
                "api",
                "--disable-timestamp",
            ],
        )
        assert_file_content(output_file, EXPECTED_PYPROJECT_PROFILE_PATH / "ignore_pyproject_with_profile.py")


def test_profile_without_pyproject_errors(tmp_path: Path, capsys: pytest.CaptureFixture[str]) -> None:
    """Test that --profile without pyproject.toml raises an error."""
    input_file = tmp_path / "schema.json"
    input_file.write_text('{"type": "object"}')
    output_file = tmp_path / "output.py"

    with chdir(tmp_path):
        run_main_with_args(
            ["--input", str(input_file), "--output", str(output_file), "--profile", "api"],
            expected_exit=Exit.ERROR,
            capsys=capsys,
        )
        assert_error_message(capsys, "no [tool.datamodel-codegen] section found")


@freeze_time("2019-07-26")
def test_allof_with_description_generates_class_not_alias(output_file: Path) -> None:
    """Test that allOf with description generates class definition, not alias."""
    run_main_and_assert(
        input_path=OPEN_API_DATA_PATH / "allof_with_description_only.yaml",
        output_path=output_file,
        input_file_type=None,
        assert_func=assert_file_content,
        expected_file=EXPECTED_MAIN_KR_PATH / "main_allof_with_description_only" / "output.py",
        extra_args=[
            "--output-model-type",
            "pydantic_v2.BaseModel",
            "--use-schema-description",
        ],
    )


@pytest.mark.cli_doc(
    options=["--use-decimal-for-multiple-of"],
    option_description="""Generate Decimal types for fields with multipleOf constraint.

The `--use-decimal-for-multiple-of` flag generates `condecimal` or `Decimal`
types for numeric fields that have a `multipleOf` constraint. This ensures
precise decimal arithmetic when validating values against the constraint.""",
    input_schema="jsonschema/use_decimal_for_multiple_of.json",
    cli_args=["--use-decimal-for-multiple-of"],
    golden_output="main_kr/use_decimal_for_multiple_of/output.py",
)
@LEGACY_BLACK_SKIP
@freeze_time("2019-07-26")
def test_use_decimal_for_multiple_of(output_file: Path) -> None:
    """Generate Decimal types for fields with multipleOf constraint.

    The `--use-decimal-for-multiple-of` flag generates `condecimal` or `Decimal`
    types for numeric fields that have a `multipleOf` constraint. This ensures
    precise decimal arithmetic when validating values against the constraint.
    """
    run_main_and_assert(
        input_path=JSON_SCHEMA_DATA_PATH / "use_decimal_for_multiple_of.json",
        output_path=output_file,
        input_file_type="jsonschema",
        assert_func=assert_file_content,
        expected_file=EXPECTED_MAIN_KR_PATH / "use_decimal_for_multiple_of" / "output.py",
        extra_args=["--use-decimal-for-multiple-of"],
    )


@pytest.mark.cli_doc(
    options=["--use-pendulum"],
    option_description="""Use pendulum types for date, time, and duration fields.

The `--use-pendulum` flag maps schema `date`, `time`, and `duration` values to
Pendulum types such as `pendulum.Date`, `pendulum.Time`, and `pendulum.Duration`.
`date-time` fields continue to use `pydantic.AwareDatetime`.

If you need a different datetime class for `date-time` fields, use
[`--output-datetime-class`](#output-datetime-class).""",
    input_schema="jsonschema/use_pendulum.json",
    cli_args=["--use-pendulum"],
    golden_output="main_kr/use_pendulum/output.py",
)
@freeze_time("2019-07-26")
def test_use_pendulum(output_file: Path) -> None:
    """Use pendulum types for date, time, and duration fields.

    The `--use-pendulum` flag maps schema `date`, `time`, and `duration` values
    to Pendulum types such as `pendulum.Date`, `pendulum.Time`, and
    `pendulum.Duration`. `date-time` fields continue to use
    `pydantic.AwareDatetime`.

    If you need a different datetime class for `date-time` fields, use
    `--output-datetime-class`.
    """
    run_main_and_assert(
        input_path=JSON_SCHEMA_DATA_PATH / "use_pendulum.json",
        output_path=output_file,
        input_file_type="jsonschema",
        assert_func=assert_file_content,
        expected_file=EXPECTED_MAIN_KR_PATH / "use_pendulum" / "output.py",
        extra_args=["--use-pendulum"],
    )


@pytest.mark.cli_doc(
    options=["--use-non-positive-negative-number-constrained-types"],
    option_description="""Use NonPositive/NonNegative types for number constraints.

The `--use-non-positive-negative-number-constrained-types` flag generates
Pydantic's NonPositiveInt, NonNegativeInt, NonPositiveFloat, and NonNegativeFloat
types for fields with minimum: 0 or maximum: 0 constraints, instead of using
conint/confloat with ge/le parameters.""",
    input_schema="jsonschema/use_non_positive_negative.json",
    cli_args=["--use-non-positive-negative-number-constrained-types"],
    golden_output="main_kr/use_non_positive_negative/output.py",
)
@pytest.mark.skipif(pydantic.VERSION < "2.0.0", reason="Require Pydantic version 2.0.0 or later")
@freeze_time("2019-07-26")
def test_use_non_positive_negative_number_constrained_types(output_file: Path) -> None:
    """Use NonPositive/NonNegative types for number constraints."""
    run_main_and_assert(
        input_path=JSON_SCHEMA_DATA_PATH / "use_non_positive_negative.json",
        output_path=output_file,
        input_file_type="jsonschema",
        assert_func=assert_file_content,
        expected_file=EXPECTED_MAIN_KR_PATH / "use_non_positive_negative" / "output.py",
        extra_args=["--use-non-positive-negative-number-constrained-types"],
    )


@pytest.mark.skipif(pydantic.VERSION < "2.0.0", reason="Require Pydantic version 2.0.0 or later")
@freeze_time("2019-07-26")
def test_use_non_positive_negative_number_constrained_types_with_use_annotated(output_file: Path) -> None:
    """Use NonPositive/NonNegative types combined with --use-annotated."""
    run_main_and_assert(
        input_path=JSON_SCHEMA_DATA_PATH / "use_non_positive_negative.json",
        output_path=output_file,
        input_file_type="jsonschema",
        assert_func=assert_file_content,
        expected_file=EXPECTED_MAIN_KR_PATH / "use_non_positive_negative_with_use_annotated" / "output.py",
        extra_args=["--use-non-positive-negative-number-constrained-types", "--use-annotated"],
    )


@pytest.mark.cli_doc(
    options=["--include-path-parameters"],
    option_description="""Include OpenAPI path parameters in generated parameter models.

The `--include-path-parameters` flag adds path parameters (like /users/{userId})
to the generated request parameter models. By default, only query parameters
are included. Use this with `--openapi-scopes parameters` to generate parameter
models that include both path and query parameters.""",
    input_schema="openapi/include_path_parameters.yaml",
    cli_args=["--include-path-parameters", "--openapi-scopes", "schemas", "paths", "parameters"],
    golden_output="main_kr/include_path_parameters/output.py",
)
@freeze_time("2019-07-26")
def test_include_path_parameters(output_file: Path) -> None:
    """Include OpenAPI path parameters in generated parameter models.

    The `--include-path-parameters` flag adds path parameters (like /users/{userId})
    to the generated request parameter models. By default, only query parameters
    are included. Use this with `--openapi-scopes parameters` to generate parameter
    models that include both path and query parameters.
    """
    run_main_and_assert(
        input_path=OPEN_API_DATA_PATH / "include_path_parameters.yaml",
        output_path=output_file,
        input_file_type="openapi",
        assert_func=assert_file_content,
        expected_file=EXPECTED_MAIN_KR_PATH / "include_path_parameters" / "output.py",
        extra_args=["--include-path-parameters", "--openapi-scopes", "schemas", "paths", "parameters"],
    )


@pytest.mark.cli_doc(
    options=["--no-alias"],
    option_description="""Disable Field alias generation for non-Python-safe property names.

The `--no-alias` flag disables automatic alias generation when JSON property
names contain characters invalid in Python (like hyphens). Without this flag,
fields are renamed to Python-safe names with `Field(alias='original-name')`.
With this flag, only Python-safe names are used without aliases.""",
    input_schema="jsonschema/no_alias.json",
    cli_args=["--no-alias"],
    golden_output="main_kr/no_alias/with_option.py",
    comparison_output="main_kr/no_alias/without_option.py",
)
@freeze_time("2019-07-26")
def test_no_alias(output_file: Path) -> None:
    """Disable Field alias generation for non-Python-safe property names.

    The `--no-alias` flag disables automatic alias generation when JSON property
    names contain characters invalid in Python (like hyphens). Without this flag,
    fields are renamed to Python-safe names with `Field(alias='original-name')`.
    With this flag, only Python-safe names are used without aliases.
    """
    run_main_and_assert(
        input_path=JSON_SCHEMA_DATA_PATH / "no_alias.json",
        output_path=output_file,
        input_file_type="jsonschema",
        assert_func=assert_file_content,
        expected_file=EXPECTED_MAIN_KR_PATH / "no_alias" / "with_option.py",
        extra_args=["--no-alias"],
    )


@pytest.mark.cli_doc(
    options=["--use-serialization-alias"],
    option_description="""Use serialization_alias instead of alias for field aliasing (Pydantic v2 only).

The `--use-serialization-alias` flag changes field aliasing to use `serialization_alias`
instead of `alias`. This allows setting values using the Pythonic field name while
serializing to the original JSON property name.""",
    input_schema="jsonschema/no_alias.json",
    cli_args=["--use-serialization-alias", "--output-model-type", "pydantic_v2.BaseModel"],
    golden_output="main_kr/use_serialization_alias/output.py",
    comparison_output="main_kr/no_alias/without_option.py",
)
@freeze_time("2019-07-26")
def test_use_serialization_alias(output_file: Path) -> None:
    """Use serialization_alias instead of alias for field aliasing (Pydantic v2 only).

    The `--use-serialization-alias` flag changes field aliasing to use `serialization_alias`
    instead of `alias`. This allows setting values using the Pythonic field name while
    serializing to the original JSON property name.
    """
    run_main_and_assert(
        input_path=JSON_SCHEMA_DATA_PATH / "no_alias.json",
        output_path=output_file,
        input_file_type="jsonschema",
        assert_func=assert_file_content,
        expected_file=EXPECTED_MAIN_KR_PATH / "use_serialization_alias" / "output.py",
        extra_args=["--use-serialization-alias", "--output-model-type", "pydantic_v2.BaseModel"],
    )


@pytest.mark.cli_doc(
    options=["--custom-file-header"],
    option_description="""Add custom header text to the generated file.

The `--custom-file-header` flag replaces the default "generated by datamodel-codegen"
header with custom text. This is useful for adding copyright notices, license
headers, or other metadata to generated files.""",
    input_schema="jsonschema/no_alias.json",
    cli_args=["--custom-file-header", "# Copyright 2024 MyCompany"],
    golden_output="main_kr/custom_file_header/with_option.py",
    comparison_output="main_kr/custom_file_header/without_option.py",
)
@freeze_time("2019-07-26")
def test_custom_file_header(output_file: Path) -> None:
    """Add custom header text to the generated file.

    The `--custom-file-header` flag replaces the default "generated by datamodel-codegen"
    header with custom text. This is useful for adding copyright notices, license
    headers, or other metadata to generated files.
    """
    run_main_and_assert(
        input_path=JSON_SCHEMA_DATA_PATH / "no_alias.json",
        output_path=output_file,
        input_file_type="jsonschema",
        assert_func=assert_file_content,
        expected_file=EXPECTED_MAIN_KR_PATH / "custom_file_header" / "with_option.py",
        extra_args=["--custom-file-header", "# Copyright 2024 MyCompany"],
    )


@pytest.mark.cli_doc(
    options=["--url", "--http-headers"],
    option_description="""Fetch schema from URL with custom HTTP headers.

The `--url` flag specifies a remote URL to fetch the schema from instead of
a local file. The `--http-headers` flag adds custom HTTP headers to the request,
useful for authentication (e.g., Bearer tokens) or custom API requirements.
Format: `HeaderName:HeaderValue`.""",
    input_schema="jsonschema/pet_simple.json",
    cli_args=["--url", "https://api.example.com/schema.json", "--http-headers", "Authorization:Bearer token"],
    golden_output="main_kr/url_with_headers/output.py",
)
@freeze_time("2019-07-26")
def test_url_with_http_headers(mock_httpx_get: HttpxGetMockFactory, output_file: Path) -> None:
    """Fetch schema from URL with custom HTTP headers.

    The `--url` flag specifies a remote URL to fetch the schema from instead of
    a local file. The `--http-headers` flag adds custom HTTP headers to the request,
    useful for authentication (e.g., Bearer tokens) or custom API requirements.
    Format: `HeaderName:HeaderValue`.
    """
    mock_get = mock_httpx_get(
        MockHttpxResponse("https://api.example.com/schema.json", JSON_SCHEMA_DATA_PATH / "pet_simple.json")
    )

    run_main_url_and_assert(
        url="https://api.example.com/schema.json",
        output_path=output_file,
        input_file_type="jsonschema",
        assert_func=assert_file_content,
        expected_file=EXPECTED_MAIN_KR_PATH / "url_with_headers" / "output.py",
        extra_args=["--http-headers", "Authorization:Bearer token"],
    )
    assert_httpx_get_kwargs(mock_get, headers=[("Authorization", "Bearer token")])


@pytest.mark.cli_doc(
    options=["--http-local-ref-path"],
    option_description="""Resolve HTTP references from local schema files.

The `--http-local-ref-path` flag maps HTTP(S) `$ref` URLs to files under
a local schema store instead of fetching them from the network. The host and
URL path are used as the relative path under the schema store. For example,
`https://api.example.com/schemas/pet.json` is read from
`schemas/api.example.com/schemas/pet.json`.""",
    input_schema="jsonschema/http_local_ref_path_root.json",
    cli_args=[
        "--url",
        "https://api.example.com/schema.json",
        "--http-local-ref-path",
        "schemas",
    ],
    golden_output="main_kr/http_local_ref_path/output.py",
)
@freeze_time("2019-07-26")
def test_http_local_ref_path_cli_doc(mock_httpx_get: HttpxGetMockFactory, output_file: Path, tmp_path: Path) -> None:
    """Resolve HTTP references from local schema files.

    The `--http-local-ref-path` flag maps HTTP(S) `$ref` URLs to files under
    a local schema store instead of fetching them from the network. The host and
    URL path are used as the relative path under the schema store.
    """
    schema_store = tmp_path / "schemas"
    local_schema = schema_store / "api.example.com" / "schemas" / "pet.json"
    local_schema.parent.mkdir(parents=True)
    local_schema.write_text((JSON_SCHEMA_DATA_PATH / "pet_simple.json").read_text(), encoding="utf-8")
    mock_get = mock_httpx_get(
        MockHttpxResponse(
            "https://api.example.com/schema.json",
            JSON_SCHEMA_DATA_PATH / "http_local_ref_path_root.json",
        )
    )

    run_main_url_and_assert(
        url="https://api.example.com/schema.json",
        output_path=output_file,
        input_file_type="jsonschema",
        assert_func=assert_file_content,
        expected_file=EXPECTED_MAIN_KR_PATH / "http_local_ref_path" / "output.py",
        extra_args=["--http-local-ref-path", str(schema_store)],
    )
    assert_httpx_get_kwargs(mock_get)


@pytest.mark.cli_doc(
    options=["--allow-private-network"],
    option_description="""Allow HTTP requests to private network schema endpoints.

The `--allow-private-network` flag permits trusted HTTP(S) schema requests to
private, loopback, link-local, or otherwise non-public network hosts. Without
this flag, those targets are blocked by default to reduce server-side request
forgery (SSRF) risk. If a trusted internal schema endpoint is blocked, verify
the URL and pass `--allow-private-network`; otherwise use a local schema file
or public endpoint.""",
    input_schema="jsonschema/pet_simple.json",
    cli_args=["--url", "http://127.0.0.1/schema.json", "--allow-private-network"],
    golden_output="main_kr/allow_private_network/output.py",
)
@freeze_time("2019-07-26")
def test_allow_private_network_cli_doc(mock_httpx_get: HttpxGetMockFactory, output_file: Path) -> None:
    """Allow trusted private network schema endpoints."""
    mock_get = mock_httpx_get(
        MockHttpxResponse("http://127.0.0.1/schema.json", JSON_SCHEMA_DATA_PATH / "pet_simple.json")
    )
    run_main_url_and_assert(
        url="http://127.0.0.1/schema.json",
        output_path=output_file,
        input_file_type="jsonschema",
        assert_func=assert_file_content,
        expected_file="allow_private_network/output.py",
        extra_args=["--allow-private-network"],
    )
    assert_httpx_get_kwargs(mock_get, expected_url="http://127.0.0.1/schema.json")


@pytest.mark.cli_doc(
    options=["--input"],
    option_description="""Specify the input schema file path.

The `--input` flag specifies the path to the schema file (JSON Schema,
OpenAPI, GraphQL, etc.). Multiple input files can be specified to merge
schemas. Required unless using `--url` to fetch schema from a URL.""",
    input_schema="jsonschema/pet_simple.json",
    cli_args=["--input", "pet_simple.json", "--output", "output.py"],
    golden_output="main_kr/input_output/output.py",
)
@freeze_time("2019-07-26")
def test_input_option(output_file: Path) -> None:
    """Specify the input schema file path.

    The `--input` flag specifies the path to the schema file (JSON Schema,
    OpenAPI, GraphQL, etc.). Multiple input files can be specified to merge
    schemas. Required unless using `--url` to fetch schema from a URL.
    """
    run_main_and_assert(
        input_path=JSON_SCHEMA_DATA_PATH / "pet_simple.json",
        output_path=output_file,
        input_file_type="jsonschema",
        assert_func=assert_file_content,
        expected_file=EXPECTED_MAIN_KR_PATH / "input_output" / "output.py",
    )


@pytest.mark.cli_doc(
    options=["--output"],
    option_description="""Specify the destination path for generated Python code.

The `--output` flag specifies where to write the generated Python code.
It can be either a file path (single-file output) or a directory path
(multi-file output for modular schemas). If omitted, the generated code
is written to stdout.""",
    input_schema="jsonschema/pet_simple.json",
    cli_args=["--input", "pet_simple.json", "--output", "output.py"],
    golden_output="main_kr/input_output/output.py",
)
@freeze_time("2019-07-26")
def test_output_option(output_file: Path) -> None:
    """Specify the destination path for generated Python code.

    The `--output` flag specifies where to write the generated Python code.
    It can be either a file path (single-file output) or a directory path
    (multi-file output for modular schemas). If omitted, the generated code
    is written to stdout.
    """
    run_main_and_assert(
        input_path=JSON_SCHEMA_DATA_PATH / "pet_simple.json",
        output_path=output_file,
        input_file_type="jsonschema",
        assert_func=assert_file_content,
        expected_file=EXPECTED_MAIN_KR_PATH / "input_output" / "output.py",
    )


@pytest.mark.cli_doc(
    options=["--encoding"],
    option_description="""Specify character encoding for input and output files.

The `--encoding` flag sets the character encoding used when reading
the schema file and writing the generated Python code. This is useful
for schemas containing non-ASCII characters (e.g., Japanese, Chinese).
Default is UTF-8, which is the standard encoding for JSON and most modern text files.""",
    input_schema="jsonschema/encoding_test.json",
    cli_args=["--encoding", "utf-8"],
    golden_output="main_kr/encoding/output.py",
)
@freeze_time("2019-07-26")
def test_encoding_option(output_file: Path) -> None:
    """Specify character encoding for input and output files.

    The `--encoding` flag sets the character encoding used when reading
    the schema file and writing the generated Python code. This is useful
    for schemas containing non-ASCII characters (e.g., Japanese, Chinese).
    Default is UTF-8, which is the standard encoding for JSON and most modern text files.
    """
    run_main_and_assert(
        input_path=JSON_SCHEMA_DATA_PATH / "encoding_test.json",
        output_path=output_file,
        input_file_type="jsonschema",
        assert_func=assert_file_content,
        expected_file=EXPECTED_MAIN_KR_PATH / "encoding" / "output.py",
        extra_args=["--encoding", "utf-8"],
    )


@pytest.mark.cli_doc(
    options=["--formatters"],
    option_description="""Specify code formatters to apply to generated output.

The `--formatters` flag specifies which code formatters to apply to
the generated Python code. Available formatters are: builtin, black,
isort, ruff-check, ruff-format. Default is [black, isort].
Use this to customize formatting or disable formatters entirely.""",
    input_schema="jsonschema/pet_simple.json",
    cli_args=["--formatters", "isort"],
    golden_output="main_kr/formatters/output.py",
)
@freeze_time("2019-07-26")
def test_formatters_option(output_file: Path) -> None:
    """Specify code formatters to apply to generated output.

    The `--formatters` flag specifies which code formatters to apply to
    the generated Python code. Available formatters are: builtin, black,
    isort, ruff-check, ruff-format. Default is [black, isort].
    Use this to customize formatting or disable formatters entirely.
    """
    run_main_and_assert(
        input_path=JSON_SCHEMA_DATA_PATH / "pet_simple.json",
        output_path=output_file,
        input_file_type="jsonschema",
        assert_func=assert_file_content,
        expected_file=EXPECTED_MAIN_KR_PATH / "formatters" / "output.py",
        extra_args=["--formatters", "isort"],
    )


@pytest.mark.cli_doc(
    options=["--custom-formatters-kwargs"],
    option_description="""Pass custom arguments to custom formatters via inline JSON or a JSON file path.

The `--custom-formatters-kwargs` flag accepts an inline JSON object or a path to a JSON file containing
custom configuration for custom formatters (used with --custom-formatters).
The file should contain a JSON object mapping formatter names to their kwargs.

Note: This option is primarily used with --custom-formatters to pass
configuration to user-defined formatter modules.""",
    input_schema="jsonschema/pet_simple.json",
    cli_args=["--custom-formatters-kwargs", "formatter_kwargs.json"],
    golden_output="main_kr/input_output/output.py",
)
@freeze_time("2019-07-26")
def test_custom_formatters_kwargs_option(output_file: Path) -> None:
    """Pass custom arguments to custom formatters via inline JSON or a JSON file path.

    The `--custom-formatters-kwargs` flag accepts an inline JSON object or a path to a JSON file containing
    custom configuration for custom formatters (used with --custom-formatters).
    The file should contain a JSON object mapping formatter names to their kwargs.

    Note: This option is primarily used with --custom-formatters to pass
    configuration to user-defined formatter modules.
    """
    # Simple test - the option is accepted. Full usage requires custom formatter module.
    run_main_and_assert(
        input_path=JSON_SCHEMA_DATA_PATH / "pet_simple.json",
        output_path=output_file,
        input_file_type="jsonschema",
        assert_func=assert_file_content,
        expected_file=EXPECTED_MAIN_KR_PATH / "input_output" / "output.py",
        extra_args=["--custom-formatters-kwargs", str(DATA_PATH / "config" / "formatter_kwargs.json")],
    )


@freeze_time("2019-07-26")
def test_custom_formatters_kwargs_inline_json_option(output_file: Path) -> None:
    """Pass custom formatter kwargs via inline JSON."""
    run_main_and_assert(
        input_path=JSON_SCHEMA_DATA_PATH / "pet_simple.json",
        output_path=output_file,
        input_file_type="jsonschema",
        assert_func=assert_file_content,
        expected_file=EXPECTED_MAIN_KR_PATH / "input_output" / "output.py",
        extra_args=[
            "--custom-formatters-kwargs",
            (DATA_PATH / "config" / "formatter_kwargs.json").read_text(encoding="utf-8"),
        ],
    )


@pytest.mark.cli_doc(
    options=["--http-ignore-tls"],
    option_description="""Disable TLS certificate verification for HTTPS requests.

The `--http-ignore-tls` flag disables SSL/TLS certificate verification
when fetching schemas from HTTPS URLs. This is useful for development
environments with self-signed certificates. Not recommended for production.""",
    input_schema="jsonschema/pet_simple.json",
    cli_args=["--url", "https://api.example.com/schema.json", "--http-ignore-tls"],
    golden_output="main_kr/url_with_headers/output.py",
)
@freeze_time("2019-07-26")
def test_http_ignore_tls(mock_httpx_get: HttpxGetMockFactory, output_file: Path) -> None:
    """Disable TLS certificate verification for HTTPS requests.

    The `--http-ignore-tls` flag disables SSL/TLS certificate verification
    when fetching schemas from HTTPS URLs. This is useful for development
    environments with self-signed certificates. Not recommended for production.
    """
    mock_get = mock_httpx_get(
        MockHttpxResponse("https://api.example.com/schema.json", JSON_SCHEMA_DATA_PATH / "pet_simple.json")
    )
    run_main_url_and_assert(
        url="https://api.example.com/schema.json",
        output_path=output_file,
        input_file_type="jsonschema",
        assert_func=assert_file_content,
        expected_file="url_with_headers/output.py",
        extra_args=["--http-ignore-tls"],
    )
    assert_httpx_get_kwargs(mock_get, verify=False)


@pytest.mark.cli_doc(
    options=["--http-query-parameters"],
    option_description="""Add query parameters to HTTP requests for remote schemas.

The `--http-query-parameters` flag adds query parameters to HTTP requests
when fetching schemas from URLs. Useful for APIs that require version
or format parameters. Format: `key=value`. Multiple parameters can be
specified: `--http-query-parameters version=v2 format=json`.""",
    input_schema="jsonschema/pet_simple.json",
    cli_args=["--url", "https://api.example.com/schema.json", "--http-query-parameters", "version=v2", "format=json"],
    golden_output="main_kr/url_with_headers/output.py",
)
@freeze_time("2019-07-26")
def test_http_query_parameters(mock_httpx_get: HttpxGetMockFactory, output_file: Path) -> None:
    """Add query parameters to HTTP requests for remote schemas.

    The `--http-query-parameters` flag adds query parameters to HTTP requests
    when fetching schemas from URLs. Useful for APIs that require version
    or format parameters. Format: `key=value`. Multiple parameters can be
    specified: `--http-query-parameters version=v2 format=json`.
    """
    mock_get = mock_httpx_get(
        MockHttpxResponse("https://api.example.com/schema.json", JSON_SCHEMA_DATA_PATH / "pet_simple.json")
    )
    run_main_url_and_assert(
        url="https://api.example.com/schema.json",
        output_path=output_file,
        input_file_type="jsonschema",
        assert_func=assert_file_content,
        expected_file="url_with_headers/output.py",
        extra_args=["--http-query-parameters", "version=v2", "format=json"],
    )
    assert_httpx_get_kwargs(mock_get, params_contains={"version": "v2", "format": "json"})


@pytest.mark.cli_doc(
    options=["--http-timeout"],
    option_description="""Set timeout for HTTP requests to remote hosts.

The `--http-timeout` flag sets the timeout in seconds for HTTP requests
when fetching schemas from URLs. Useful for slow servers or large schemas.
Default is 30 seconds.""",
    input_schema="jsonschema/pet_simple.json",
    cli_args=["--url", "https://api.example.com/schema.json", "--http-timeout", "60"],
    golden_output="main_kr/url_with_headers/output.py",
)
@freeze_time("2019-07-26")
def test_http_timeout(mock_httpx_get: HttpxGetMockFactory, output_file: Path) -> None:
    """Set timeout for HTTP requests to remote hosts.

    The `--http-timeout` flag sets the timeout in seconds for HTTP requests
    when fetching schemas from URLs. Useful for slow servers or large schemas.
    Default is 30 seconds.
    """
    mock_get = mock_httpx_get(
        MockHttpxResponse("https://api.example.com/schema.json", JSON_SCHEMA_DATA_PATH / "pet_simple.json")
    )
    run_main_url_and_assert(
        url="https://api.example.com/schema.json",
        output_path=output_file,
        input_file_type="jsonschema",
        assert_func=assert_file_content,
        expected_file="url_with_headers/output.py",
        extra_args=["--http-timeout", "60"],
    )
    assert_httpx_get_kwargs(mock_get, timeout=60.0)


@pytest.mark.cli_doc(
    options=["--ignore-pyproject"],
    option_description="""Ignore pyproject.toml configuration file.

The `--ignore-pyproject` flag tells datamodel-codegen to ignore any
[tool.datamodel-codegen] configuration in pyproject.toml. This is useful
when you want to override project defaults with CLI arguments, or when
testing without project configuration.""",
    input_schema="jsonschema/ignore_pyproject_example.json",
    cli_args=["--ignore-pyproject"],
    golden_output="main_kr/ignore_pyproject/output.py",
    comparison_output="main_kr/ignore_pyproject/without_option.py",
)
@freeze_time("2019-07-26")
def test_ignore_pyproject_cli_doc(output_file: Path, tmp_path: Path) -> None:
    """Ignore pyproject.toml configuration file.

    The `--ignore-pyproject` flag tells datamodel-codegen to ignore any
    [tool.datamodel-codegen] configuration in pyproject.toml. This is useful
    when you want to override project defaults with CLI arguments, or when
    testing without project configuration.
    """
    # Create a pyproject.toml with snake-case-field to demonstrate ignoring
    pyproject_toml = """
[tool.datamodel-codegen]
snake-case-field = true
"""
    (tmp_path / "pyproject.toml").write_text(pyproject_toml)

    input_data = """
{
  "$schema": "http://json-schema.org/draft-07/schema#",
  "type": "object",
  "properties": {
    "firstName": {"type": "string"},
    "lastName": {"type": "string"}
  }
}
"""
    input_file = tmp_path / "schema.json"
    input_file.write_text(input_data)

    with chdir(tmp_path):
        run_main_and_assert(
            input_path=input_file,
            output_path=output_file.resolve(),
            assert_func=assert_file_content,
            expected_file=EXPECTED_MAIN_KR_PATH / "ignore_pyproject" / "output.py",
            extra_args=["--ignore-pyproject", "--disable-timestamp"],
        )


@pytest.mark.cli_doc(
    options=["--shared-module-name"],
    option_description="""Customize the name of the shared module for deduplicated models.

The `--shared-module-name` flag sets the name of the shared module created
when using `--reuse-model` with `--reuse-scope=tree`. This module contains
deduplicated models that are referenced from multiple files. Default is
`shared`. Use this if your schema already has a file named `shared`.

Note: This option only affects modular output with tree-level model reuse.""",
    input_schema="jsonschema/pet_simple.json",
    cli_args=["--shared-module-name", "my_shared"],
    golden_output="main_kr/input_output/output.py",
)
@freeze_time("2019-07-26")
def test_shared_module_name(output_file: Path) -> None:
    """Customize the name of the shared module for deduplicated models.

    The `--shared-module-name` flag sets the name of the shared module created
    when using `--reuse-model` with `--reuse-scope=tree`. This module contains
    deduplicated models that are referenced from multiple files. Default is
    `shared`. Use this if your schema already has a file named `shared`.

    Note: This option only affects modular output with tree-level model reuse.
    """
    # Simple test - the option is accepted but only affects modular output with reuse
    run_main_and_assert(
        input_path=JSON_SCHEMA_DATA_PATH / "pet_simple.json",
        output_path=output_file,
        input_file_type="jsonschema",
        assert_func=assert_file_content,
        expected_file=EXPECTED_MAIN_KR_PATH / "input_output" / "output.py",
        extra_args=["--shared-module-name", "my_shared"],
    )


@pytest.mark.cli_doc(
    options=["--use-exact-imports"],
    option_description="""Import exact types instead of modules.

The `--use-exact-imports` flag changes import style from module imports
to exact type imports. For example, instead of `from . import foo` then
`foo.Bar`, it generates `from .foo import Bar`. This can make the generated
code more explicit and easier to read.

Note: This option primarily affects modular output where imports between
modules are generated. For single-file output, the difference is minimal.""",
    input_schema="jsonschema/pet_simple.json",
    cli_args=["--use-exact-imports"],
    golden_output="main_kr/input_output/output.py",
)
@freeze_time("2019-07-26")
def test_use_exact_imports(output_file: Path) -> None:
    """Import exact types instead of modules.

    The `--use-exact-imports` flag changes import style from module imports
    to exact type imports. For example, instead of `from . import foo` then
    `foo.Bar`, it generates `from .foo import Bar`. This can make the generated
    code more explicit and easier to read.

    Note: This option primarily affects modular output where imports between
    modules are generated. For single-file output, the difference is minimal.
    """
    # Simple test - the option is accepted and works for single file output
    run_main_and_assert(
        input_path=JSON_SCHEMA_DATA_PATH / "pet_simple.json",
        output_path=output_file,
        input_file_type="jsonschema",
        assert_func=assert_file_content,
        expected_file=EXPECTED_MAIN_KR_PATH / "input_output" / "output.py",
        extra_args=["--use-exact-imports"],
    )


@pytest.mark.cli_doc(
    options=["--target-python-version"],
    option_description="""Target Python version for generated code syntax and imports.

The `--target-python-version` flag controls Python version-specific syntax:

- **Python 3.10-3.11**: Uses `X | None` union operator, `TypeAlias` annotation
- **Python 3.12+**: Uses `type` statement for type aliases

This affects import statements and type annotation syntax in generated code.""",
    input_schema="jsonschema/person.json",
    cli_args=["--target-python-version", "3.10", "--use-standard-collections"],
    version_outputs={
        "3.10": "main_kr/target_python_version/py310.py",
    },
    primary=True,
)
@freeze_time("2019-07-26")
def test_target_python_version_outputs(output_file: Path) -> None:
    """Target Python version for generated code syntax and imports.

    The `--target-python-version` flag controls Python version-specific syntax:

    - **Python 3.10-3.11**: Uses `X | None` union operator, `TypeAlias` annotation
    - **Python 3.12+**: Uses `type` statement for type aliases

    This affects import statements and type annotation syntax in generated code.
    """
    # Test with Python 3.10 style
    run_main_and_assert(
        input_path=JSON_SCHEMA_DATA_PATH / "person.json",
        output_path=output_file,
        assert_func=assert_file_content,
        expected_file=EXPECTED_MAIN_KR_PATH / "target_python_version" / "py310.py",
        extra_args=["--target-python-version", "3.10", "--use-standard-collections"],
    )


@pytest.mark.cli_doc(
    options=["--target-pydantic-version"],
    option_description="""Target Pydantic version for generated code compatibility.

The `--target-pydantic-version` flag controls Pydantic version-specific config:

- **2**: Uses `populate_by_name=True` (compatible with Pydantic 2.0-2.10)
- **2.11**: Uses `validate_by_name=True` (for Pydantic 2.11+)
- **2.12**: Uses `validate_by_name=True` and allows features that require Pydantic 2.12+

This prevents breaking changes when generated code is used on older Pydantic versions.""",
    input_schema="jsonschema/person.json",
    cli_args=[
        "--target-pydantic-version",
        "2.11",
        "--allow-population-by-field-name",
        "--output-model-type",
        "pydantic_v2.BaseModel",
    ],
    golden_output="main_kr/target_pydantic_version/v2_11.py",
    primary=True,
)
@freeze_time("2019-07-26")
def test_target_pydantic_version(output_file: Path) -> None:
    """Target Pydantic version for generated code compatibility.

    The `--target-pydantic-version` flag controls Pydantic version-specific config:

    - **2**: Uses `populate_by_name=True` (compatible with Pydantic 2.0-2.10)
    - **2.11**: Uses `validate_by_name=True` (for Pydantic 2.11+)
    - **2.12**: Uses `validate_by_name=True` and allows features that require Pydantic 2.12+

    This prevents breaking changes when generated code is used on older Pydantic versions.
    """
    run_main_and_assert(
        input_path=JSON_SCHEMA_DATA_PATH / "person.json",
        output_path=output_file,
        assert_func=assert_file_content,
        expected_file=EXPECTED_MAIN_KR_PATH / "target_pydantic_version" / "v2_11.py",
        extra_args=[
            "--target-pydantic-version",
            "2.11",
            "--allow-population-by-field-name",
            "--output-model-type",
            "pydantic_v2.BaseModel",
        ],
    )


def test_generate_prompt_basic(capsys: pytest.CaptureFixture[str]) -> None:
    """Generate a prompt for consulting LLMs about CLI options.

    The `--generate-prompt` flag outputs a formatted prompt containing:
    - Current CLI options
    - Options organized by category with descriptions
    - Full help text

    This prompt can be copied to ChatGPT, Claude, or other LLMs to get
    recommendations for appropriate CLI options.
    """
    run_main_with_args(
        ["--generate-prompt"],
        expected_exit=Exit.OK,
        capsys=capsys,
        expected_stdout_path=EXPECTED_MAIN_KR_PATH / "generate_prompt" / "basic.txt",
    )


def test_generate_prompt_with_question(capsys: pytest.CaptureFixture[str]) -> None:
    """Test --generate-prompt with a question argument."""
    question = "How do I convert enums to Literal types?"
    run_main_with_args(
        ["--generate-prompt", question],
        expected_exit=Exit.OK,
        capsys=capsys,
        expected_stdout_path=EXPECTED_MAIN_KR_PATH / "generate_prompt" / "with_question.txt",
    )


def test_generate_prompt_with_options(capsys: pytest.CaptureFixture[str]) -> None:
    """Test --generate-prompt with other CLI options set."""
    run_main_with_args(
        [
            "--input",
            "schema.json",
            "--output-model-type",
            "pydantic_v2.BaseModel",
            "--snake-case-field",
            "--generate-prompt",
            "What other options should I use?",
        ],
        expected_exit=Exit.OK,
        capsys=capsys,
        expected_stdout_path=EXPECTED_MAIN_KR_PATH / "generate_prompt" / "with_options.txt",
    )


def test_generate_prompt_with_list_options(capsys: pytest.CaptureFixture[str]) -> None:
    """Test --generate-prompt with list options (e.g., --strict-types)."""
    run_main_with_args(
        [
            "--strict-types",
            "str",
            "int",
            "--generate-prompt",
        ],
        expected_exit=Exit.OK,
        capsys=capsys,
        expected_stdout_path=EXPECTED_MAIN_KR_PATH / "generate_prompt" / "with_list_options.txt",
    )


def test_generate_prompt_json(capsys: pytest.CaptureFixture[str]) -> None:
    """Test --generate-prompt --output-format json emits structured option metadata."""
    run_main_with_args(
        GENERATE_PROMPT_JSON_ARGS,
        expected_exit=Exit.OK,
        capsys=capsys,
        expected_stdout_path=EXPECTED_MAIN_KR_PATH / "generate_prompt" / "json_output.txt",
        assert_no_stderr=True,
    )


def test_output_format_json_schema_generate_prompt(capsys: pytest.CaptureFixture[str]) -> None:
    """Test --output-format-json-schema generate-prompt emits the prompt JSON Schema."""
    run_main_with_args(
        GENERATE_PROMPT_JSON_SCHEMA_ARGS,
        expected_exit=Exit.OK,
        capsys=capsys,
        expected_stdout_path=EXPECTED_MAIN_KR_PATH / "generate_prompt" / "json_schema.txt",
        assert_no_stderr=True,
    )


def test_output_format_json_schema_validates_generate_prompt_json() -> None:
    """Test prompt JSON output conforms to its emitted JSON Schema."""
    schema = json.loads((EXPECTED_MAIN_KR_PATH / "generate_prompt" / "json_schema.txt").read_text())
    payload = json.loads((EXPECTED_MAIN_KR_PATH / "generate_prompt" / "json_output.txt").read_text())

    jsonschema.validate(instance=payload, schema=schema)


def test_output_format_json_schema_generation(capsys: pytest.CaptureFixture[str]) -> None:
    """Test --output-format-json-schema generation emits the generation JSON Schema."""
    run_main_with_args(
        [
            "--output-format-json-schema",
            "generation",
        ],
        expected_exit=Exit.OK,
        capsys=capsys,
        expected_stdout_path=EXPECTED_OUTPUT_FORMAT_JSON_PATH / "generation_schema.txt",
        assert_no_stderr=True,
    )


def test_output_format_json_schema_model_metadata(capsys: pytest.CaptureFixture[str]) -> None:
    """Test --output-format-json-schema model-metadata emits the metadata JSON Schema."""
    run_main_with_args(
        [
            "--output-format-json-schema",
            "model-metadata",
        ],
        expected_exit=Exit.OK,
        capsys=capsys,
        expected_stdout_path=EXPECTED_OUTPUT_FORMAT_JSON_PATH / "model_metadata_schema.txt",
        assert_no_stderr=True,
    )


def test_output_format_json_schema_structured_output(capsys: pytest.CaptureFixture[str]) -> None:
    """Test --output-format-json-schema structured-output emits the JSON Schema for all structured outputs."""
    run_main_with_args(
        [
            "--output-format-json-schema",
            "structured-output",
        ],
        expected_exit=Exit.OK,
        capsys=capsys,
        expected_stdout_path=EXPECTED_OUTPUT_FORMAT_JSON_PATH / "structured_output_schema.txt",
        assert_no_stderr=True,
    )


def test_output_format_json_schema_config(capsys: pytest.CaptureFixture[str]) -> None:
    """Test --output-format-json-schema config emits the JSON config schema."""
    run_main_with_args(
        [
            "--output-format-json-schema",
            "config",
        ],
        expected_exit=Exit.OK,
        capsys=capsys,
        expected_stdout_path=EXPECTED_OUTPUT_FORMAT_JSON_PATH / "config_schema.txt",
        assert_no_stderr=True,
    )


def test_output_format_json_schema_validates_generation_json() -> None:
    """Test generation JSON output conforms to its emitted JSON Schema."""
    schema = json.loads((EXPECTED_OUTPUT_FORMAT_JSON_PATH / "generation_schema.txt").read_text())
    payload = json.loads((EXPECTED_OUTPUT_FORMAT_JSON_PATH / "generation_stdout.txt").read_text())

    jsonschema.validate(instance=payload, schema=schema)


def test_output_format_json_schema_validates_model_metadata_json(
    output_file: Path, capsys: pytest.CaptureFixture[str]
) -> None:
    """Test generated model metadata conforms to the emitted JSON Schema."""
    metadata_path = output_file.parent / "metadata" / "model-map.json"
    run_main_and_assert(
        input_path=JSON_SCHEMA_DATA_PATH / "model_metadata_base_model_name.json",
        output_path=output_file,
        input_file_type="jsonschema",
        assert_func=assert_file_content,
        expected_file=DATA_PATH / "expected" / "main" / "jsonschema" / "model_metadata_base_model_name.py",
        extra_args=[
            "--emit-model-metadata",
            str(metadata_path),
            "--disable-timestamp",
        ],
    )
    run_main_with_args(
        [
            "--output-format-json-schema",
            "model-metadata",
        ],
        expected_exit=Exit.OK,
        capsys=capsys,
    )
    schema = json.loads(capsys.readouterr().out)
    payload = json.loads(metadata_path.read_text(encoding="utf-8"))

    jsonschema.validate(instance=payload, schema=schema)


def test_output_format_json_json_ready_values() -> None:
    """Test JSON output normalization handles CLI values used in configs."""

    class Payload(pydantic.BaseModel):
        name: str
        skipped: str | None = None

    payload = _json_ready({
        "exit": Exit.OK,
        "path": Path("schema/person.json"),
        "items": [Exit.ERROR, Path("model/output.py")],
        "model": Payload(name="value"),
    })
    assert_output(
        f"{json.dumps(payload, indent=2, ensure_ascii=False)}\n",
        EXPECTED_OUTPUT_FORMAT_JSON_PATH / "json_ready_values.txt",
    )


def test_output_format_json_generation_mapping_payload() -> None:
    """Test mapping generation results become per-module JSON files."""
    output = _generation_output_json(
        _generated_files_from_result({
            ("models", "user.py"): "class User:\n    pass\n",
        })
    )
    assert_output(f"{output}\n", EXPECTED_OUTPUT_FORMAT_JSON_PATH / "generation_mapping_payload.txt")


def test_output_format_json_normalizes_multiple_generation_output_paths(output_file: Path) -> None:
    """Test generated output path normalization only replaces matching file paths."""
    disk_output = json.dumps({
        "output": output_file.as_posix(),
        "files": [
            {"path": output_file.as_posix(), "content": "generated\n"},
            {"path": "pkg/model.py", "content": "keep\n"},
        ],
    })
    assert_output(
        _normalize_generation_json_output_path(disk_output, output_file, OUTPUT_FILE_PLACEHOLDER),
        EXPECTED_OUTPUT_FORMAT_JSON_PATH / "generation_output_path_normalized.txt",
    )
    stdout_output = json.dumps({
        "output": None,
        "files": [
            {"path": output_file.as_posix(), "content": "generated\n"},
            {"path": "pkg/model.py", "content": "keep\n"},
        ],
    })
    assert_output(
        _normalize_generation_json_output_path(stdout_output, output_file, OUTPUT_FILE_PLACEHOLDER),
        EXPECTED_OUTPUT_FORMAT_JSON_PATH / "generation_stdout_path_normalized.txt",
    )


@pytest.mark.parametrize(
    "payload_name",
    [
        "generation_stdout.txt",
        "generation_output_file.txt",
        "generation_output_directory.txt",
        "pyproject_config.txt",
        "cli_command.txt",
        "list_deprecations.txt",
        "list_experimental.txt",
        "check_success.txt",
        "check_missing_output.txt",
        "check_output_file_difference_json.txt",
        "check_output_directory_differences_json.txt",
    ],
)
def test_output_format_json_schema_validates_structured_json(payload_name: str) -> None:
    """Test structured JSON outputs conform to their emitted JSON Schema."""
    schema = json.loads((EXPECTED_OUTPUT_FORMAT_JSON_PATH / "structured_output_schema.txt").read_text())
    payload = json.loads((EXPECTED_OUTPUT_FORMAT_JSON_PATH / payload_name).read_text())

    jsonschema.validate(instance=payload, schema=schema)


def test_output_format_json_schema_validates_prompt_json_as_structured_output() -> None:
    """Test prompt JSON output also conforms to the structured-output JSON Schema."""
    schema = json.loads((EXPECTED_OUTPUT_FORMAT_JSON_PATH / "structured_output_schema.txt").read_text())
    payload = json.loads((EXPECTED_MAIN_KR_PATH / "generate_prompt" / "json_output.txt").read_text())

    jsonschema.validate(instance=payload, schema=schema)


def test_output_format_json_generation_stdout(capsys: pytest.CaptureFixture[str]) -> None:
    """Test normal generation can emit generated content as JSON."""
    run_main_with_args(
        [
            "--input",
            str(OPEN_API_DATA_PATH / "api.yaml"),
            "--input-file-type",
            "openapi",
            "--output-format",
            "json",
            "--disable-timestamp",
        ],
        expected_exit=Exit.OK,
        capsys=capsys,
        expected_stdout_path=EXPECTED_OUTPUT_FORMAT_JSON_PATH / "generation_stdout.txt",
        assert_no_stderr=True,
    )


def test_output_format_json_generate_pyproject_config(capsys: pytest.CaptureFixture[str]) -> None:
    """Test --generate-pyproject-config can emit structured JSON."""
    run_main_with_args(
        [
            "--generate-pyproject-config",
            "--input",
            "schema.yaml",
            "--output",
            "model.py",
            "--output-format",
            "json",
        ],
        expected_exit=Exit.OK,
        capsys=capsys,
        expected_stdout_path=EXPECTED_OUTPUT_FORMAT_JSON_PATH / "pyproject_config.txt",
        assert_no_stderr=True,
    )


def test_generate_pyproject_config_helper_uses_default_formatter() -> None:
    """Test the pyproject config helper returns text output by default."""
    assert_output(
        generate_pyproject_config(
            Namespace(
                input="schema.yaml",
                output="model.py",
                strict_types=["str", "bytes"],
                output_format="json",
                output_format_json_schema="structured-output",
            )
        ),
        EXPECTED_GENERATE_PYPROJECT_CONFIG_PATH / "helper_default_formatter.txt",
    )


def test_output_format_json_generate_cli_command(tmp_path: Path, capsys: pytest.CaptureFixture[str]) -> None:
    """Test --generate-cli-command can emit structured JSON."""
    pyproject_toml = """
[tool.datamodel-codegen]
input = "schema.yaml"
output = "model.py"
"""
    (tmp_path / "pyproject.toml").write_text(pyproject_toml)

    with chdir(tmp_path):
        run_main_with_args(
            [
                "--generate-cli-command",
                "--output-format",
                "json",
            ],
            expected_exit=Exit.OK,
            capsys=capsys,
            expected_stdout_path=EXPECTED_OUTPUT_FORMAT_JSON_PATH / "cli_command.txt",
            assert_no_stderr=True,
        )


def test_output_format_json_list_deprecations(capsys: pytest.CaptureFixture[str]) -> None:
    """Test --list-deprecations can emit structured JSON."""
    run_main_with_args(
        [
            "--list-deprecations",
            "--output-format",
            "json",
        ],
        expected_exit=Exit.OK,
        capsys=capsys,
        expected_stdout_path=EXPECTED_OUTPUT_FORMAT_JSON_PATH / "list_deprecations.txt",
        assert_no_stderr=True,
    )


def test_output_format_json_list_experimental(capsys: pytest.CaptureFixture[str]) -> None:
    """Test --list-experimental can emit structured JSON."""
    run_main_with_args(
        [
            "--list-experimental",
            "json",
            "--output-format",
            "json",
        ],
        expected_exit=Exit.OK,
        capsys=capsys,
        expected_stdout_path=EXPECTED_OUTPUT_FORMAT_JSON_PATH / "list_experimental.txt",
        assert_no_stderr=True,
    )


@freeze_time(TIMESTAMP)
def test_output_format_json_generation_output_file(capsys: pytest.CaptureFixture[str], output_file: Path) -> None:
    """Test --output-format json can mirror a generated single output file."""
    run_main_with_args(
        [
            "--input",
            str(OPEN_API_DATA_PATH / "api.yaml"),
            "--input-file-type",
            "openapi",
            "--output",
            str(output_file),
            "--output-format",
            "json",
        ],
        expected_exit=Exit.OK,
    )
    captured = capsys.readouterr()
    assert_output(captured.err, EXPECTED_EMPTY_OUTPUT_PATH)
    assert_output(
        _normalize_generation_json_output_path(captured.out, output_file, OUTPUT_FILE_PLACEHOLDER),
        EXPECTED_OUTPUT_FORMAT_JSON_PATH / "generation_output_file.txt",
    )
    assert_file_content(output_file, EXPECTED_MAIN_KR_PATH / "main_no_file" / "output.py")


@freeze_time(TIMESTAMP)
def test_output_format_json_generation_output_directory(capsys: pytest.CaptureFixture[str], output_dir: Path) -> None:
    """Test --output-format json reports generated multi-file modules."""
    run_main_with_args(
        [
            "--input",
            str(OPEN_API_DATA_PATH / "modular.yaml"),
            "--input-file-type",
            "openapi",
            "--output",
            str(output_dir),
            "--output-format",
            "json",
        ],
        expected_exit=Exit.OK,
    )
    captured = capsys.readouterr()
    assert_output(captured.err, EXPECTED_EMPTY_OUTPUT_PATH)
    assert_output(
        _normalize_generation_json_output_path(captured.out, output_dir, OUTPUT_DIR_PLACEHOLDER),
        EXPECTED_OUTPUT_FORMAT_JSON_PATH / "generation_output_directory.txt",
    )
    assert_directory_content(output_dir, EXPECTED_MAIN_KR_PATH / "main_modular")


def test_output_format_json_check_success(tmp_path: Path, capsys: pytest.CaptureFixture[str]) -> None:
    """Test --check can emit structured JSON when files are up-to-date."""
    with chdir(tmp_path):
        run_main_with_args(
            [
                "--input",
                str(OPEN_API_DATA_PATH / "api.yaml"),
                "--input-file-type",
                "openapi",
                "--output",
                "output.py",
                "--disable-timestamp",
            ],
            expected_exit=Exit.OK,
        )
        capsys.readouterr()

        run_main_with_args(
            [
                "--input",
                str(OPEN_API_DATA_PATH / "api.yaml"),
                "--input-file-type",
                "openapi",
                "--output",
                "output.py",
                "--check",
                "--output-format",
                "json",
                "--disable-timestamp",
            ],
            expected_exit=Exit.OK,
            capsys=capsys,
            expected_stdout_path=EXPECTED_OUTPUT_FORMAT_JSON_PATH / "check_success.txt",
            assert_no_stderr=True,
        )


def test_output_format_json_check_missing_output_directory(tmp_path: Path, capsys: pytest.CaptureFixture[str]) -> None:
    """Test --check can emit structured JSON when files differ."""
    with chdir(tmp_path):
        run_main_with_args(
            [
                "--input",
                str(OPEN_API_DATA_PATH / "modular.yaml"),
                "--input-file-type",
                "openapi",
                "--output",
                "model",
                "--check",
                "--output-format",
                "json",
                "--disable-timestamp",
            ],
            expected_exit=Exit.DIFF,
            capsys=capsys,
            expected_stdout_path=EXPECTED_OUTPUT_FORMAT_JSON_PATH / "check_missing_output.txt",
            assert_no_stderr=True,
        )


def test_output_format_json_check_output_file_difference(
    tmp_path: Path,
    capsys: pytest.CaptureFixture[str],
) -> None:
    """Test --check can emit structured JSON for single-file differences."""
    with chdir(tmp_path):
        output_path = Path("output.py")
        output_path.write_text("outdated\n", encoding="utf-8")
        run_main_with_args(
            [
                "--input",
                str(OPEN_API_DATA_PATH / "api.yaml"),
                "--input-file-type",
                "openapi",
                "--output",
                output_path.as_posix(),
                "--check",
                "--output-format",
                "json",
                "--disable-timestamp",
            ],
            expected_exit=Exit.DIFF,
        )
    captured = capsys.readouterr()
    assert_output(
        captured.out.replace((tmp_path / output_path).as_posix(), OUTPUT_FILE_PLACEHOLDER),
        EXPECTED_OUTPUT_FORMAT_JSON_PATH / "check_output_file_difference_json.txt",
    )
    assert_output(captured.err, EXPECTED_EMPTY_OUTPUT_PATH)


def test_output_format_json_check_output_directory_differences(
    tmp_path: Path,
    capsys: pytest.CaptureFixture[str],
) -> None:
    """Test --check can emit structured JSON for directory differences."""
    with chdir(tmp_path):
        output_dir = Path("model")
        output_dir.mkdir()
        (output_dir / "models.py").write_text("outdated\n", encoding="utf-8")
        (output_dir / "extra.py").write_text("extra\n", encoding="utf-8")
        run_main_with_args(
            [
                "--input",
                str(OPEN_API_DATA_PATH / "modular.yaml"),
                "--input-file-type",
                "openapi",
                "--output",
                output_dir.as_posix(),
                "--check",
                "--output-format",
                "json",
                "--disable-timestamp",
            ],
            expected_exit=Exit.DIFF,
        )
    captured = capsys.readouterr()
    assert_output(
        captured.out.replace((tmp_path / output_dir).as_posix(), OUTPUT_DIR_PLACEHOLDER),
        EXPECTED_OUTPUT_FORMAT_JSON_PATH / "check_output_directory_differences_json.txt",
    )
    assert_output(captured.err, EXPECTED_EMPTY_OUTPUT_PATH)


def test_output_format_json_check_text_reports_output_file_difference(
    tmp_path: Path,
    capsys: pytest.CaptureFixture[str],
) -> None:
    """Test --check reports single-file differences in text output."""
    with chdir(tmp_path):
        output_path = Path("output.py")
        output_path.write_text("outdated\n", encoding="utf-8")
        run_main_with_args(
            [
                "--input",
                str(OPEN_API_DATA_PATH / "api.yaml"),
                "--input-file-type",
                "openapi",
                "--output",
                output_path.as_posix(),
                "--check",
                "--disable-timestamp",
            ],
            expected_exit=Exit.DIFF,
        )
    captured = capsys.readouterr()
    assert_output(
        captured.out.replace((tmp_path / output_path).as_posix(), OUTPUT_FILE_PLACEHOLDER),
        EXPECTED_OUTPUT_FORMAT_JSON_PATH / "check_output_file_difference_text.txt",
    )
    assert_output(captured.err, EXPECTED_EMPTY_OUTPUT_PATH)


def test_output_format_json_check_text_reports_output_directory_differences(
    tmp_path: Path,
    capsys: pytest.CaptureFixture[str],
) -> None:
    """Test --check reports directory differences in text output."""
    with chdir(tmp_path):
        output_dir = Path("model")
        output_dir.mkdir()
        (output_dir / "models.py").write_text("outdated\n", encoding="utf-8")
        (output_dir / "extra.py").write_text("extra\n", encoding="utf-8")
        run_main_with_args(
            [
                "--input",
                str(OPEN_API_DATA_PATH / "modular.yaml"),
                "--input-file-type",
                "openapi",
                "--output",
                "model",
                "--check",
                "--disable-timestamp",
            ],
            expected_exit=Exit.DIFF,
            capsys=capsys,
            expected_stdout_path=EXPECTED_OUTPUT_FORMAT_JSON_PATH / "check_output_directory_differences_text.txt",
            assert_no_stderr=True,
        )


def test_output_format_json_rejects_watch(capsys: pytest.CaptureFixture[str]) -> None:
    """Test --watch cannot be combined with --output-format json."""
    run_main_with_args(
        [
            "--input",
            str(OPEN_API_DATA_PATH / "api.yaml"),
            "--input-file-type",
            "openapi",
            "--output-format",
            "json",
            "--watch",
        ],
        expected_exit=Exit.ERROR,
    )
    captured = capsys.readouterr()
    assert_output(captured.out, EXPECTED_EMPTY_OUTPUT_PATH)
    assert_output(captured.err, EXPECTED_OUTPUT_FORMAT_JSON_PATH / "watch_incompatible.txt")


@freeze_time("2019-07-26")
def test_profile_extends_single_parent(output_file: Path, tmp_path: Path) -> None:
    """Test profile inheritance with single extends."""
    pyproject_toml = """
[tool.datamodel-codegen]
target-python-version = "3.10"
enable-version-header = false

[tool.datamodel-codegen.profiles._base]
snake-case-field = true

[tool.datamodel-codegen.profiles.api]
extends = "_base"
"""
    (tmp_path / "pyproject.toml").write_text(pyproject_toml)

    input_data = '{"type": "object", "properties": {"firstName": {"type": "string"}}}'
    input_file = tmp_path / "schema.json"
    input_file.write_text(input_data)

    with chdir(tmp_path):
        run_main_and_assert(
            input_path=input_file,
            output_path=output_file.resolve(),
            assert_func=assert_file_content,
            expected_file=EXPECTED_PYPROJECT_PROFILE_PATH / "extends_single.py",
            extra_args=["--profile", "api", "--disable-timestamp"],
        )


@freeze_time("2019-07-26")
def test_profile_extends_multiple_parents(output_file: Path, tmp_path: Path) -> None:
    """Test profile inheritance with multiple extends (list)."""
    pyproject_toml = """
[tool.datamodel-codegen]
target-python-version = "3.10"
enable-version-header = false

[tool.datamodel-codegen.profiles._snake]
snake-case-field = true

[tool.datamodel-codegen.profiles._constraints]
field-constraints = true

[tool.datamodel-codegen.profiles.api]
extends = ["_snake", "_constraints"]
"""
    (tmp_path / "pyproject.toml").write_text(pyproject_toml)

    input_data = '{"type": "object", "properties": {"firstName": {"type": "string", "minLength": 1}}}'
    input_file = tmp_path / "schema.json"
    input_file.write_text(input_data)

    with chdir(tmp_path):
        run_main_and_assert(
            input_path=input_file,
            output_path=output_file.resolve(),
            assert_func=assert_file_content,
            expected_file=EXPECTED_PYPROJECT_PROFILE_PATH / "extends_multiple.py",
            extra_args=["--profile", "api", "--disable-timestamp"],
        )


@freeze_time("2019-07-26")
def test_profile_extends_chain(output_file: Path, tmp_path: Path) -> None:
    """Test profile inheritance chain (a extends b extends c)."""
    pyproject_toml = """
[tool.datamodel-codegen]
target-python-version = "3.10"
enable-version-header = false

[tool.datamodel-codegen.profiles._base]
snake-case-field = true

[tool.datamodel-codegen.profiles._middle]
extends = "_base"
field-constraints = true

[tool.datamodel-codegen.profiles.api]
extends = "_middle"
"""
    (tmp_path / "pyproject.toml").write_text(pyproject_toml)

    input_data = '{"type": "object", "properties": {"firstName": {"type": "string", "minLength": 1}}}'
    input_file = tmp_path / "schema.json"
    input_file.write_text(input_data)

    with chdir(tmp_path):
        run_main_and_assert(
            input_path=input_file,
            output_path=output_file.resolve(),
            assert_func=assert_file_content,
            expected_file=EXPECTED_PYPROJECT_PROFILE_PATH / "extends_chain.py",
            extra_args=["--profile", "api", "--disable-timestamp"],
        )


def test_profile_extends_circular_error(tmp_path: Path, capsys: pytest.CaptureFixture[str]) -> None:
    """Test error when circular extends is detected."""
    pyproject_toml = """
[tool.datamodel-codegen]

[tool.datamodel-codegen.profiles.a]
extends = "b"

[tool.datamodel-codegen.profiles.b]
extends = "a"
"""
    (tmp_path / "pyproject.toml").write_text(pyproject_toml)

    input_file = tmp_path / "schema.json"
    input_file.write_text('{"type": "object"}')
    output_file = tmp_path / "output.py"

    with chdir(tmp_path):
        run_main_with_args(
            ["--input", str(input_file), "--output", str(output_file), "--profile", "a"],
            expected_exit=Exit.ERROR,
        )
        assert_error_message(capsys, "Circular extends detected")


def test_profile_extends_not_found_error(tmp_path: Path, capsys: pytest.CaptureFixture[str]) -> None:
    """Test error when extended profile is not found."""
    pyproject_toml = """
[tool.datamodel-codegen]

[tool.datamodel-codegen.profiles.api]
extends = "nonexistent"
"""
    (tmp_path / "pyproject.toml").write_text(pyproject_toml)

    input_file = tmp_path / "schema.json"
    input_file.write_text('{"type": "object"}')
    output_file = tmp_path / "output.py"

    with chdir(tmp_path):
        run_main_with_args(
            ["--input", str(input_file), "--output", str(output_file), "--profile", "api"],
            expected_exit=Exit.ERROR,
        )
        assert_error_message(capsys, "Extended profile 'nonexistent' not found")


def test_profile_extends_self_error(tmp_path: Path, capsys: pytest.CaptureFixture[str]) -> None:
    """Test error when profile extends itself."""
    pyproject_toml = """
[tool.datamodel-codegen]

[tool.datamodel-codegen.profiles.api]
extends = "api"
"""
    (tmp_path / "pyproject.toml").write_text(pyproject_toml)

    input_file = tmp_path / "schema.json"
    input_file.write_text('{"type": "object"}')
    output_file = tmp_path / "output.py"

    with chdir(tmp_path):
        run_main_with_args(
            ["--input", str(input_file), "--output", str(output_file), "--profile", "api"],
            expected_exit=Exit.ERROR,
        )
        assert_error_message(capsys, "cannot extend itself")


@freeze_time("2019-07-26")
def test_profile_extends_child_overrides_parent(output_file: Path, tmp_path: Path) -> None:
    """Test that child profile settings override parent settings."""
    pyproject_toml = """
[tool.datamodel-codegen]
target-python-version = "3.10"
enable-version-header = false

[tool.datamodel-codegen.profiles._base]
snake-case-field = true

[tool.datamodel-codegen.profiles.api]
extends = "_base"
snake-case-field = false
"""
    (tmp_path / "pyproject.toml").write_text(pyproject_toml)

    input_data = '{"type": "object", "properties": {"firstName": {"type": "string"}}}'
    input_file = tmp_path / "schema.json"
    input_file.write_text(input_data)

    with chdir(tmp_path):
        run_main_and_assert(
            input_path=input_file,
            output_path=output_file.resolve(),
            assert_func=assert_file_content,
            expected_file=EXPECTED_PYPROJECT_PROFILE_PATH / "extends_override.py",
            extra_args=["--profile", "api", "--disable-timestamp"],
        )
