"""Tests for models file."""

import sys

import pytest

from open_alchemy import models_file

_DOCSTRING = '"""Autogenerated SQLAlchemy models based on OpenAlchemy models."""'
_LONG_NAME = "extremely_long_name_that_will_cause_wrapping_aaaaaaaaaaaaaaaaa"
_EXPECTED_TD_BASE = "typing.TypedDict"
if sys.version_info[1] < 8:
    _EXPECTED_TD_BASE = "typing_extensions.TypedDict"
_EXPECTED_MODEL_BASE = "typing.Protocol"
if sys.version_info[1] < 8:
    _EXPECTED_MODEL_BASE = "typing_extensions.Protocol"
_ADDITIONAL_IMPORT = ""
if sys.version_info[1] < 8:
    _ADDITIONAL_IMPORT = """
import typing_extensions"""


@pytest.mark.parametrize(
    "schemas, expected_source",
    [
        (
            [({"properties": {"id": {"type": "integer"}}}, "Model")],
            f'''{_DOCSTRING}
# pylint: disable=no-member,super-init-not-called,unused-argument

import typing

import sqlalchemy{_ADDITIONAL_IMPORT}
from sqlalchemy import orm

from open_alchemy import models


class ModelDict({_EXPECTED_TD_BASE}, total=False):
    """TypedDict for properties that are not required."""

    id: typing.Optional[int]


class TModel({_EXPECTED_MODEL_BASE}):
    """
    SQLAlchemy model protocol.

    Attrs:
        id: The id of the Model.

    """

    # SQLAlchemy properties
    __table__: sqlalchemy.Table
    __tablename__: str
    query: orm.Query

    # Model properties
    id: typing.Optional[int]

    def __init__(self, id: typing.Optional[int] = None) -> None:
        """
        Construct.

        Args:
            id: The id of the Model.

        """
        ...

    @classmethod
    def from_dict(cls, id: typing.Optional[int] = None) -> "TModel":
        """
        Construct from a dictionary (eg. a POST payload).

        Args:
            id: The id of the Model.

        Returns:
            Model instance based on the dictionary.

        """
        ...

    @classmethod
    def from_str(cls, value: str) -> "TModel":
        """
        Construct from a JSON string (eg. a POST payload).

        Returns:
            Model instance based on the JSON string.

        """
        ...

    def to_dict(self) -> ModelDict:
        """
        Convert to a dictionary (eg. to send back for a GET request).

        Returns:
            Dictionary based on the model instance.

        """
        ...

    def to_str(self) -> str:
        """
        Convert to a JSON string (eg. to send back for a GET request).

        Returns:
            JSON string based on the model instance.

        """
        ...


Model: TModel = models.Model  # type: ignore
''',
        ),
        (
            [
                ({"properties": {"id": {"type": "integer"}}}, "Model1"),
                ({"properties": {"id": {"type": "string"}}}, "Model2"),
            ],
            f'''{_DOCSTRING}
# pylint: disable=no-member,super-init-not-called,unused-argument

import typing

import sqlalchemy{_ADDITIONAL_IMPORT}
from sqlalchemy import orm

from open_alchemy import models


class Model1Dict({_EXPECTED_TD_BASE}, total=False):
    """TypedDict for properties that are not required."""

    id: typing.Optional[int]


class TModel1({_EXPECTED_MODEL_BASE}):
    """
    SQLAlchemy model protocol.

    Attrs:
        id: The id of the Model1.

    """

    # SQLAlchemy properties
    __table__: sqlalchemy.Table
    __tablename__: str
    query: orm.Query

    # Model properties
    id: typing.Optional[int]

    def __init__(self, id: typing.Optional[int] = None) -> None:
        """
        Construct.

        Args:
            id: The id of the Model1.

        """
        ...

    @classmethod
    def from_dict(cls, id: typing.Optional[int] = None) -> "TModel1":
        """
        Construct from a dictionary (eg. a POST payload).

        Args:
            id: The id of the Model1.

        Returns:
            Model instance based on the dictionary.

        """
        ...

    @classmethod
    def from_str(cls, value: str) -> "TModel1":
        """
        Construct from a JSON string (eg. a POST payload).

        Returns:
            Model instance based on the JSON string.

        """
        ...

    def to_dict(self) -> Model1Dict:
        """
        Convert to a dictionary (eg. to send back for a GET request).

        Returns:
            Dictionary based on the model instance.

        """
        ...

    def to_str(self) -> str:
        """
        Convert to a JSON string (eg. to send back for a GET request).

        Returns:
            JSON string based on the model instance.

        """
        ...


Model1: TModel1 = models.Model1  # type: ignore


class Model2Dict({_EXPECTED_TD_BASE}, total=False):
    """TypedDict for properties that are not required."""

    id: typing.Optional[str]


class TModel2({_EXPECTED_MODEL_BASE}):
    """
    SQLAlchemy model protocol.

    Attrs:
        id: The id of the Model2.

    """

    # SQLAlchemy properties
    __table__: sqlalchemy.Table
    __tablename__: str
    query: orm.Query

    # Model properties
    id: typing.Optional[str]

    def __init__(self, id: typing.Optional[str] = None) -> None:
        """
        Construct.

        Args:
            id: The id of the Model2.

        """
        ...

    @classmethod
    def from_dict(cls, id: typing.Optional[str] = None) -> "TModel2":
        """
        Construct from a dictionary (eg. a POST payload).

        Args:
            id: The id of the Model2.

        Returns:
            Model instance based on the dictionary.

        """
        ...

    @classmethod
    def from_str(cls, value: str) -> "TModel2":
        """
        Construct from a JSON string (eg. a POST payload).

        Returns:
            Model instance based on the JSON string.

        """
        ...

    def to_dict(self) -> Model2Dict:
        """
        Convert to a dictionary (eg. to send back for a GET request).

        Returns:
            Dictionary based on the model instance.

        """
        ...

    def to_str(self) -> str:
        """
        Convert to a JSON string (eg. to send back for a GET request).

        Returns:
            JSON string based on the model instance.

        """
        ...


Model2: TModel2 = models.Model2  # type: ignore
''',
        ),
        (
            [({"properties": {_LONG_NAME: {"type": "integer"}}}, "Model")],
            f'''{_DOCSTRING}
# pylint: disable=no-member,super-init-not-called,unused-argument

import typing

import sqlalchemy{_ADDITIONAL_IMPORT}
from sqlalchemy import orm

from open_alchemy import models


class ModelDict({_EXPECTED_TD_BASE}, total=False):
    """TypedDict for properties that are not required."""

    extremely_long_name_that_will_cause_wrapping_aaaaaaaaaaaaaaaaa: typing.Optional[int]


class TModel({_EXPECTED_MODEL_BASE}):
    """
    SQLAlchemy model protocol.

    Attrs:
        extremely_long_name_that_will_cause_wrapping_aaaaaaaaaaaaaaaaa: The
            extremely_long_name_that_will_cause_wrapping_aaaaaaaaaaaaaaaaa of
            the Model.

    """

    # SQLAlchemy properties
    __table__: sqlalchemy.Table
    __tablename__: str
    query: orm.Query

    # Model properties
    extremely_long_name_that_will_cause_wrapping_aaaaaaaaaaaaaaaaa: typing.Optional[int]

    def __init__(
        self,
        extremely_long_name_that_will_cause_wrapping_aaaaaaaaaaaaaaaaa: typing.Optional[
            int
        ] = None,
    ) -> None:
        """
        Construct.

        Args:
            extremely_long_name_that_will_cause_wrapping_aaaaaaaaaaaaaaaaa: The
                extremely_long_name_that_will_cause_wrapping_aaaaaaaaaaaaaaaaa
                of the Model.

        """
        ...

    @classmethod
    def from_dict(
        cls,
        extremely_long_name_that_will_cause_wrapping_aaaaaaaaaaaaaaaaa: typing.Optional[
            int
        ] = None,
    ) -> "TModel":
        """
        Construct from a dictionary (eg. a POST payload).

        Args:
            extremely_long_name_that_will_cause_wrapping_aaaaaaaaaaaaaaaaa: The
                extremely_long_name_that_will_cause_wrapping_aaaaaaaaaaaaaaaaa
                of the Model.

        Returns:
            Model instance based on the dictionary.

        """
        ...

    @classmethod
    def from_str(cls, value: str) -> "TModel":
        """
        Construct from a JSON string (eg. a POST payload).

        Returns:
            Model instance based on the JSON string.

        """
        ...

    def to_dict(self) -> ModelDict:
        """
        Convert to a dictionary (eg. to send back for a GET request).

        Returns:
            Dictionary based on the model instance.

        """
        ...

    def to_str(self) -> str:
        """
        Convert to a JSON string (eg. to send back for a GET request).

        Returns:
            JSON string based on the model instance.

        """
        ...


Model: TModel = models.Model  # type: ignore
''',
        ),
    ],
    ids=["single", "multiple", "black"],
)
@pytest.mark.models_file
def test_integration(schemas, expected_source):
    """
    GIVEN schema and name
    WHEN schema is added to the models file and the models file is generated
    THEN the models source code is returned.
    """
    models = models_file.ModelsFile()
    for schema, name in schemas:
        models.add_model(schema=schema, name=name)
    source = models.generate_models()

    assert source == expected_source


@pytest.mark.models_file
def test_schema_change():
    """
    GIVEN schema and name
    WHEN schema is added to the models file, changed and then the models file is
        generated
    THEN the change is reflected in the models file.
    """
    schema = {"properties": {}}
    name = "Model"
    models = models_file.ModelsFile()

    models.add_model(schema=schema, name=name)
    schema["properties"]["id"] = {"type": "integer"}
    source = models.generate_models()

    expected_source = f'''{_DOCSTRING}
# pylint: disable=no-member,super-init-not-called,unused-argument

import typing

import sqlalchemy{_ADDITIONAL_IMPORT}
from sqlalchemy import orm

from open_alchemy import models


class ModelDict({_EXPECTED_TD_BASE}, total=False):
    """TypedDict for properties that are not required."""

    id: typing.Optional[int]


class TModel({_EXPECTED_MODEL_BASE}):
    """
    SQLAlchemy model protocol.

    Attrs:
        id: The id of the Model.

    """

    # SQLAlchemy properties
    __table__: sqlalchemy.Table
    __tablename__: str
    query: orm.Query

    # Model properties
    id: typing.Optional[int]

    def __init__(self, id: typing.Optional[int] = None) -> None:
        """
        Construct.

        Args:
            id: The id of the Model.

        """
        ...

    @classmethod
    def from_dict(cls, id: typing.Optional[int] = None) -> "TModel":
        """
        Construct from a dictionary (eg. a POST payload).

        Args:
            id: The id of the Model.

        Returns:
            Model instance based on the dictionary.

        """
        ...

    @classmethod
    def from_str(cls, value: str) -> "TModel":
        """
        Construct from a JSON string (eg. a POST payload).

        Returns:
            Model instance based on the JSON string.

        """
        ...

    def to_dict(self) -> ModelDict:
        """
        Convert to a dictionary (eg. to send back for a GET request).

        Returns:
            Dictionary based on the model instance.

        """
        ...

    def to_str(self) -> str:
        """
        Convert to a JSON string (eg. to send back for a GET request).

        Returns:
            JSON string based on the model instance.

        """
        ...


Model: TModel = models.Model  # type: ignore
'''
    assert source == expected_source
