from typing import TYPE_CHECKING, List, Optional
from uuid import UUID

from sqlalchemy import event
from sqlalchemy.ext.orderinglist import ordering_list
from sqlalchemy.schema import Column, ForeignKey, UniqueConstraint
from sqlmodel import Field, Relationship
from sqlmodel.sql.sqltypes import GUID

from joj.horse.models.base import DomainURLORMModel, url_pre_save
from joj.horse.models.link_tables import ProblemProblemSetLink
from joj.horse.schemas.base import Operation
from joj.horse.schemas.problem_set import ProblemSetDetail
from joj.horse.utils.errors import BizError, ErrorCode

if TYPE_CHECKING:
    from joj.horse.models import Domain, Problem, Record, User


class ProblemSet(DomainURLORMModel, ProblemSetDetail, table=True):  # type: ignore[call-arg]
    __tablename__ = "problem_sets"
    __table_args__ = (UniqueConstraint("domain_id", "url"),)

    domain_id: UUID = Field(
        sa_column=Column(
            GUID, ForeignKey("domains.id", ondelete="CASCADE"), nullable=False
        )
    )
    domain: "Domain" = Relationship(back_populates="problem_sets")

    owner_id: Optional[UUID] = Field(
        sa_column=Column(
            GUID,
            ForeignKey("users.id", ondelete="SET NULL"),
            nullable=True,
        )
    )
    owner: Optional["User"] = Relationship(back_populates="owned_problem_sets")

    # problems_link: List["Problem"] = Relationship(
    #     back_populates="problem_problem_set_links",
    #     # link_model=ProblemProblemSetLink,
    #     sa_relationship_kwargs={
    #         "secondary": ProblemProblemSetLink,
    #         "order_by": "ProblemProblemSetLink.position",
    #         "collection_class": ordering_list("position"),
    #     },
    # )

    # maintain the order of many to many relationship
    problem_problem_set_links: List[ProblemProblemSetLink] = Relationship(
        back_populates="problem_set",
        sa_relationship_kwargs={
            "order_by": "ProblemProblemSetLink.position",
            "collection_class": ordering_list("position"),
        },
    )

    problems: List["Problem"] = Relationship(
        back_populates="problem_sets",
        link_model=ProblemProblemSetLink,
        sa_relationship_kwargs={
            "order_by": "ProblemProblemSetLink.position",
        },
    )
    records: List["Record"] = Relationship(back_populates="problem_set")

    async def operate_problem(
        self, problem: "Problem", operation: Operation, position: Optional[int] = None
    ) -> None:
        assert problem.domain_id == self.domain_id
        link = await ProblemProblemSetLink.get_or_none(
            problem_set_id=self.id, problem_id=problem.id
        )
        if operation == Operation.Create:
            if link is not None:
                raise BizError(ErrorCode.IntegrityError, "problem already added")
            link = ProblemProblemSetLink(problem_set_id=self.id, problem_id=problem.id)
        else:
            if link is None:
                raise BizError(ErrorCode.IntegrityError, "problem not added")

        if operation == Operation.Read:
            return
        if operation in (Operation.Update, Operation.Delete):
            self.problem_problem_set_links.remove(link)
        if operation in (Operation.Create, Operation.Update):
            if position is None:
                self.problem_problem_set_links.append(link)
            else:
                self.problem_problem_set_links.insert(position, link)
        if operation == Operation.Delete:
            await link.delete_model(commit=False)
        await self.save_model()


event.listen(ProblemSet, "before_insert", url_pre_save)
event.listen(ProblemSet, "before_update", url_pre_save)

sqlmodel.Relationship sqlalchemy.event.listen joj.horse.models.link_tables.ProblemProblemSetLink sqlalchemy.schema.ForeignKey joj.horse.utils.errors.BizError joj.horse.models.link_tables.ProblemProblemSetLink.get_or_none sqlalchemy.schema.UniqueConstraint sqlalchemy.ext.orderinglist.ordering_list
[((3592, 3647), 'sqlalchemy.event.listen', 'event.listen', (['ProblemSet', '"""before_insert"""', 'url_pre_save'], {}), "(ProblemSet, 'before_insert', url_pre_save)\n", (3604, 3647), False, 'from sqlalchemy import event\n'), ((3648, 3703), 'sqlalchemy.event.listen', 'event.listen', (['ProblemSet', '"""before_update"""', 'url_pre_save'], {}), "(ProblemSet, 'before_update', url_pre_save)\n", (3660, 3703), False, 'from sqlalchemy import event\n'), ((1038, 1081), 'sqlmodel.Relationship', 'Relationship', ([], {'back_populates': '"""problem_sets"""'}), "(back_populates='problem_sets')\n", (1050, 1081), False, 'from sqlmodel import Field, Relationship\n'), ((1295, 1344), 'sqlmodel.Relationship', 'Relationship', ([], {'back_populates': '"""owned_problem_sets"""'}), "(back_populates='owned_problem_sets')\n", (1307, 1344), False, 'from sqlmodel import Field, Relationship\n'), ((2094, 2251), 'sqlmodel.Relationship', 'Relationship', ([], {'back_populates': '"""problem_sets"""', 'link_model': 'ProblemProblemSetLink', 'sa_relationship_kwargs': "{'order_by': 'ProblemProblemSetLink.position'}"}), "(back_populates='problem_sets', link_model=\n    ProblemProblemSetLink, sa_relationship_kwargs={'order_by':\n    'ProblemProblemSetLink.position'})\n", (2106, 2251), False, 'from sqlmodel import Field, Relationship\n'), ((2327, 2369), 'sqlmodel.Relationship', 'Relationship', ([], {'back_populates': '"""problem_set"""'}), "(back_populates='problem_set')\n", (2339, 2369), False, 'from sqlmodel import Field, Relationship\n'), ((825, 861), 'sqlalchemy.schema.UniqueConstraint', 'UniqueConstraint', (['"""domain_id"""', '"""url"""'], {}), "('domain_id', 'url')\n", (841, 861), False, 'from sqlalchemy.schema import Column, ForeignKey, UniqueConstraint\n'), ((2576, 2661), 'joj.horse.models.link_tables.ProblemProblemSetLink.get_or_none', 'ProblemProblemSetLink.get_or_none', ([], {'problem_set_id': 'self.id', 'problem_id': 'problem.id'}), '(problem_set_id=self.id, problem_id=problem.id\n    )\n', (2609, 2661), False, 'from joj.horse.models.link_tables import ProblemProblemSetLink\n'), ((2855, 2923), 'joj.horse.models.link_tables.ProblemProblemSetLink', 'ProblemProblemSetLink', ([], {'problem_set_id': 'self.id', 'problem_id': 'problem.id'}), '(problem_set_id=self.id, problem_id=problem.id)\n', (2876, 2923), False, 'from joj.horse.models.link_tables import ProblemProblemSetLink\n'), ((938, 982), 'sqlalchemy.schema.ForeignKey', 'ForeignKey', (['"""domains.id"""'], {'ondelete': '"""CASCADE"""'}), "('domains.id', ondelete='CASCADE')\n", (948, 982), False, 'from sqlalchemy.schema import Column, ForeignKey, UniqueConstraint\n'), ((1177, 1220), 'sqlalchemy.schema.ForeignKey', 'ForeignKey', (['"""users.id"""'], {'ondelete': '"""SET NULL"""'}), "('users.id', ondelete='SET NULL')\n", (1187, 1220), False, 'from sqlalchemy.schema import Column, ForeignKey, UniqueConstraint\n'), ((2017, 2042), 'sqlalchemy.ext.orderinglist.ordering_list', 'ordering_list', (['"""position"""'], {}), "('position')\n", (2030, 2042), False, 'from sqlalchemy.ext.orderinglist import ordering_list\n'), ((2776, 2835), 'joj.horse.utils.errors.BizError', 'BizError', (['ErrorCode.IntegrityError', '"""problem already added"""'], {}), "(ErrorCode.IntegrityError, 'problem already added')\n", (2784, 2835), False, 'from joj.horse.utils.errors import BizError, ErrorCode\n'), ((2989, 3044), 'joj.horse.utils.errors.BizError', 'BizError', (['ErrorCode.IntegrityError', '"""problem not added"""'], {}), "(ErrorCode.IntegrityError, 'problem not added')\n", (2997, 3044), False, 'from joj.horse.utils.errors import BizError, ErrorCode\n')]