import textwrap
from typing import List, Optional

import pytest

from sqlmodel import Field, Relationship, SQLModel

import strawberry


class City(SQLModel, table=True):
    id: Optional[int] = Field(primary_key=True, default=None)
    name: str = Field()
    population: int = Field()


class Manager(SQLModel, table=True):
    id: Optional[int] = Field(primary_key=True, default=None)
    name: str = Field()
    managed_team: "Team" = Relationship(back_populates="manager")


class Team(SQLModel, table=True):
    id: Optional[int] = Field(primary_key=True, default=None)
    name: str = Field(index=True)
    headquarters: Optional[str] = Field(default=None)
    manager_id: int = Field(nullable=False, foreign_key="manager.id")
    manager: Manager = Relationship(back_populates="managed_team")
    heroes: List["Hero"] = Relationship(back_populates="team")


class Hero(SQLModel, table=True):
    id: Optional[int] = Field(primary_key=True, default=None)
    name: str = Field(index=True)
    secret_name: str
    age: Optional[int] = Field(default=None, index=True)
    team_id: Optional[int] = Field(default=None, foreign_key="team.id")
    team: Optional[Team] = Relationship(back_populates="heroes")


@pytest.fixture
def clear_types():
    for model in (Team, Hero, Manager, City):
        if hasattr(model, "_strawberry_type"):
            delattr(model, "_strawberry_type")


def test_all_fields(clear_types):
    @strawberry.experimental.pydantic.type(City, all_fields=True)
    class CityType:
        pass

    @strawberry.type
    class Query:
        @strawberry.field
        def city(self) -> CityType:
            return CityType(id=1, name="Gotham", population=100000)

    schema = strawberry.Schema(query=Query)

    expected_schema = """
    type CityType {
      name: String!
      population: Int!
      id: Int
    }

    type Query {
      city: CityType!
    }
    """

    assert str(schema) == textwrap.dedent(expected_schema).strip()

    query = "{ city { name } }"

    result = schema.execute_sync(query)

    assert not result.errors
    assert result.data["city"]["name"] == "Gotham"


def test_basic_type_field_list(clear_types):
    @strawberry.experimental.pydantic.type(Team, fields=["name", "headquarters"])
    class TeamType:
        pass

    @strawberry.type
    class Query:
        @strawberry.field
        def team(self) -> TeamType:
            return TeamType(name="hobbits", headquarters="The Shire")

    schema = strawberry.Schema(query=Query)

    expected_schema = """
    type Query {
      team: TeamType!
    }

    type TeamType {
      name: String!
      headquarters: String
    }
    """

    assert str(schema) == textwrap.dedent(expected_schema).strip()

    query = "{ team { name } }"

    result = schema.execute_sync(query)

    assert not result.errors
    assert result.data["team"]["name"] == "hobbits"


def test_one_to_one_optional(clear_types):
    @strawberry.experimental.pydantic.type(Team, fields=["name"])
    class TeamType:
        pass

    @strawberry.experimental.pydantic.type(Hero, fields=["team"])
    class HeroType:
        pass

    @strawberry.type
    class Query:
        @strawberry.field
        def hero(self) -> HeroType:
            return HeroType(team=TeamType(name="Skii"))

    schema = strawberry.Schema(query=Query)

    expected_schema = """
    type HeroType {
      team: TeamType
    }

    type Query {
      hero: HeroType!
    }

    type TeamType {
      name: String!
    }
    """

    assert str(schema) == textwrap.dedent(expected_schema).strip()

    query = "{ hero { team { name } } }"

    result = schema.execute_sync(query)

    assert not result.errors
    assert result.data["hero"]["team"]["name"] == "Skii"


def test_one_to_one_required(clear_types):
    @strawberry.experimental.pydantic.type(Manager, fields=["name"])
    class ManagerType:
        pass

    @strawberry.experimental.pydantic.type(Team, fields=["manager"])
    class TeamType:
        pass

    @strawberry.type
    class Query:
        @strawberry.field
        def team(self) -> TeamType:
            return TeamType(manager=ManagerType(name="Skii"))

    schema = strawberry.Schema(query=Query)

    expected_schema = """
    type ManagerType {
      name: String!
    }

    type Query {
      team: TeamType!
    }

    type TeamType {
      manager: ManagerType!
    }
    """

    assert str(schema) == textwrap.dedent(expected_schema).strip()

    query = "{ team { manager { name } } }"

    result = schema.execute_sync(query)

    assert not result.errors
    assert result.data["team"]["manager"]["name"] == "Skii"


def test_nested_type_unordered(clear_types):
    @strawberry.experimental.pydantic.type(Hero, fields=["team"])
    class HeroType:
        pass

    @strawberry.experimental.pydantic.type(Team, fields=["name"])
    class TeamType:
        pass

    @strawberry.type
    class Query:
        @strawberry.field
        def hero(self) -> HeroType:
            return HeroType(team=TeamType(name="Skii"))

    schema = strawberry.Schema(query=Query)

    expected_schema = """
    type HeroType {
      team: TeamType
    }

    type Query {
      hero: HeroType!
    }

    type TeamType {
      name: String!
    }
    """

    assert str(schema) == textwrap.dedent(expected_schema).strip()

    query = "{ hero { team { name } } }"

    result = schema.execute_sync(query)

    assert not result.errors
    assert result.data["hero"]["team"]["name"] == "Skii"


def test_one_to_many(clear_types):
    @strawberry.experimental.pydantic.type(Team, fields=["heroes"])
    class TeamType:
        pass

    @strawberry.experimental.pydantic.type(Hero, fields=["name"])
    class HeroType:
        pass

    @strawberry.type
    class Query:
        @strawberry.field
        def team(self) -> TeamType:
            return TeamType(heroes=[HeroType(name="Skii"), HeroType(name="Chris")])

    schema = strawberry.Schema(query=Query)

    expected_schema = """
    type HeroType {
      name: String!
    }

    type Query {
      team: TeamType!
    }

    type TeamType {
      heroes: [HeroType!]!
    }
    """

    assert str(schema) == textwrap.dedent(expected_schema).strip()

    query = "{ team { heroes { name } } }"

    result = schema.execute_sync(query)

    assert not result.errors
    assert result.data["team"]["heroes"][0]["name"] == "Skii"
    assert result.data["team"]["heroes"][1]["name"] == "Chris"

textwrap.dedent sqlmodel.Relationship sqlmodel.Field strawberry.experimental.pydantic.type strawberry.Schema
[((196, 233), 'sqlmodel.Field', 'Field', ([], {'primary_key': '(True)', 'default': 'None'}), '(primary_key=True, default=None)\n', (201, 233), False, 'from sqlmodel import Field, Relationship, SQLModel\n'), ((250, 257), 'sqlmodel.Field', 'Field', ([], {}), '()\n', (255, 257), False, 'from sqlmodel import Field, Relationship, SQLModel\n'), ((280, 287), 'sqlmodel.Field', 'Field', ([], {}), '()\n', (285, 287), False, 'from sqlmodel import Field, Relationship, SQLModel\n'), ((351, 388), 'sqlmodel.Field', 'Field', ([], {'primary_key': '(True)', 'default': 'None'}), '(primary_key=True, default=None)\n', (356, 388), False, 'from sqlmodel import Field, Relationship, SQLModel\n'), ((405, 412), 'sqlmodel.Field', 'Field', ([], {}), '()\n', (410, 412), False, 'from sqlmodel import Field, Relationship, SQLModel\n'), ((440, 478), 'sqlmodel.Relationship', 'Relationship', ([], {'back_populates': '"""manager"""'}), "(back_populates='manager')\n", (452, 478), False, 'from sqlmodel import Field, Relationship, SQLModel\n'), ((539, 576), 'sqlmodel.Field', 'Field', ([], {'primary_key': '(True)', 'default': 'None'}), '(primary_key=True, default=None)\n', (544, 576), False, 'from sqlmodel import Field, Relationship, SQLModel\n'), ((593, 610), 'sqlmodel.Field', 'Field', ([], {'index': '(True)'}), '(index=True)\n', (598, 610), False, 'from sqlmodel import Field, Relationship, SQLModel\n'), ((645, 664), 'sqlmodel.Field', 'Field', ([], {'default': 'None'}), '(default=None)\n', (650, 664), False, 'from sqlmodel import Field, Relationship, SQLModel\n'), ((687, 734), 'sqlmodel.Field', 'Field', ([], {'nullable': '(False)', 'foreign_key': '"""manager.id"""'}), "(nullable=False, foreign_key='manager.id')\n", (692, 734), False, 'from sqlmodel import Field, Relationship, SQLModel\n'), ((758, 801), 'sqlmodel.Relationship', 'Relationship', ([], {'back_populates': '"""managed_team"""'}), "(back_populates='managed_team')\n", (770, 801), False, 'from sqlmodel import Field, Relationship, SQLModel\n'), ((829, 864), 'sqlmodel.Relationship', 'Relationship', ([], {'back_populates': '"""team"""'}), "(back_populates='team')\n", (841, 864), False, 'from sqlmodel import Field, Relationship, SQLModel\n'), ((925, 962), 'sqlmodel.Field', 'Field', ([], {'primary_key': '(True)', 'default': 'None'}), '(primary_key=True, default=None)\n', (930, 962), False, 'from sqlmodel import Field, Relationship, SQLModel\n'), ((979, 996), 'sqlmodel.Field', 'Field', ([], {'index': '(True)'}), '(index=True)\n', (984, 996), False, 'from sqlmodel import Field, Relationship, SQLModel\n'), ((1043, 1074), 'sqlmodel.Field', 'Field', ([], {'default': 'None', 'index': '(True)'}), '(default=None, index=True)\n', (1048, 1074), False, 'from sqlmodel import Field, Relationship, SQLModel\n'), ((1104, 1146), 'sqlmodel.Field', 'Field', ([], {'default': 'None', 'foreign_key': '"""team.id"""'}), "(default=None, foreign_key='team.id')\n", (1109, 1146), False, 'from sqlmodel import Field, Relationship, SQLModel\n'), ((1174, 1211), 'sqlmodel.Relationship', 'Relationship', ([], {'back_populates': '"""heroes"""'}), "(back_populates='heroes')\n", (1186, 1211), False, 'from sqlmodel import Field, Relationship, SQLModel\n'), ((1430, 1490), 'strawberry.experimental.pydantic.type', 'strawberry.experimental.pydantic.type', (['City'], {'all_fields': '(True)'}), '(City, all_fields=True)\n', (1467, 1490), False, 'import strawberry\n'), ((1707, 1737), 'strawberry.Schema', 'strawberry.Schema', ([], {'query': 'Query'}), '(query=Query)\n', (1724, 1737), False, 'import strawberry\n'), ((2177, 2253), 'strawberry.experimental.pydantic.type', 'strawberry.experimental.pydantic.type', (['Team'], {'fields': "['name', 'headquarters']"}), "(Team, fields=['name', 'headquarters'])\n", (2214, 2253), False, 'import strawberry\n'), ((2472, 2502), 'strawberry.Schema', 'strawberry.Schema', ([], {'query': 'Query'}), '(query=Query)\n', (2489, 2502), False, 'import strawberry\n'), ((2931, 2991), 'strawberry.experimental.pydantic.type', 'strawberry.experimental.pydantic.type', (['Team'], {'fields': "['name']"}), "(Team, fields=['name'])\n", (2968, 2991), False, 'import strawberry\n'), ((3031, 3091), 'strawberry.experimental.pydantic.type', 'strawberry.experimental.pydantic.type', (['Hero'], {'fields': "['team']"}), "(Hero, fields=['team'])\n", (3068, 3091), False, 'import strawberry\n'), ((3296, 3326), 'strawberry.Schema', 'strawberry.Schema', ([], {'query': 'Query'}), '(query=Query)\n', (3313, 3326), False, 'import strawberry\n'), ((3790, 3853), 'strawberry.experimental.pydantic.type', 'strawberry.experimental.pydantic.type', (['Manager'], {'fields': "['name']"}), "(Manager, fields=['name'])\n", (3827, 3853), False, 'import strawberry\n'), ((3896, 3959), 'strawberry.experimental.pydantic.type', 'strawberry.experimental.pydantic.type', (['Team'], {'fields': "['manager']"}), "(Team, fields=['manager'])\n", (3933, 3959), False, 'import strawberry\n'), ((4170, 4200), 'strawberry.Schema', 'strawberry.Schema', ([], {'query': 'Query'}), '(query=Query)\n', (4187, 4200), False, 'import strawberry\n'), ((4682, 4742), 'strawberry.experimental.pydantic.type', 'strawberry.experimental.pydantic.type', (['Hero'], {'fields': "['team']"}), "(Hero, fields=['team'])\n", (4719, 4742), False, 'import strawberry\n'), ((4782, 4842), 'strawberry.experimental.pydantic.type', 'strawberry.experimental.pydantic.type', (['Team'], {'fields': "['name']"}), "(Team, fields=['name'])\n", (4819, 4842), False, 'import strawberry\n'), ((5047, 5077), 'strawberry.Schema', 'strawberry.Schema', ([], {'query': 'Query'}), '(query=Query)\n', (5064, 5077), False, 'import strawberry\n'), ((5533, 5595), 'strawberry.experimental.pydantic.type', 'strawberry.experimental.pydantic.type', (['Team'], {'fields': "['heroes']"}), "(Team, fields=['heroes'])\n", (5570, 5595), False, 'import strawberry\n'), ((5635, 5695), 'strawberry.experimental.pydantic.type', 'strawberry.experimental.pydantic.type', (['Hero'], {'fields': "['name']"}), "(Hero, fields=['name'])\n", (5672, 5695), False, 'import strawberry\n'), ((5928, 5958), 'strawberry.Schema', 'strawberry.Schema', ([], {'query': 'Query'}), '(query=Query)\n', (5945, 5958), False, 'import strawberry\n'), ((1929, 1961), 'textwrap.dedent', 'textwrap.dedent', (['expected_schema'], {}), '(expected_schema)\n', (1944, 1961), False, 'import textwrap\n'), ((2684, 2716), 'textwrap.dedent', 'textwrap.dedent', (['expected_schema'], {}), '(expected_schema)\n', (2699, 2716), False, 'import textwrap\n'), ((3529, 3561), 'textwrap.dedent', 'textwrap.dedent', (['expected_schema'], {}), '(expected_schema)\n', (3544, 3561), False, 'import textwrap\n'), ((4413, 4445), 'textwrap.dedent', 'textwrap.dedent', (['expected_schema'], {}), '(expected_schema)\n', (4428, 4445), False, 'import textwrap\n'), ((5280, 5312), 'textwrap.dedent', 'textwrap.dedent', (['expected_schema'], {}), '(expected_schema)\n', (5295, 5312), False, 'import textwrap\n'), ((6167, 6199), 'textwrap.dedent', 'textwrap.dedent', (['expected_schema'], {}), '(expected_schema)\n', (6182, 6199), False, 'import textwrap\n')]