"""(Basic) Message Tables/Models.

Models of the Traction tables for Messages (layer over AcaPy basic messaging).

"""
import uuid
from datetime import datetime
from typing import List, Optional

from sqlalchemy.orm import selectinload
from sqlmodel import Field, Relationship
from sqlalchemy import (
    Column,
    func,
    String,
    select,
    desc,
    text,
)
from sqlalchemy.dialects.postgresql import UUID, TIMESTAMP, ARRAY
from sqlmodel.ext.asyncio.session import AsyncSession

from api.db.models.base import BaseModel
from api.db.models.v1.contact import Contact

from api.endpoints.models.v1.errors import (
    NotFoundError,
)


class Message(BaseModel, table=True):
    """Message.

    Model for the Message table (postgresql specific dialects in use).
    This will track Messages for the Tenants (between contacts).

    Attributes:
      message_id: Traction ID for message OR when receiving, it is the AcaPy message_id
      tenant_id: Traction Tenant ID
      contact_id: Traction Contact ID
      status: Business and Tenant indicator for Credential state; independent of AcaPy
        Basic Message Exchange state
      role: sender or recipient
      deleted: Issuer Credential "soft" delete indicator.
      tags: Set by tenant for arbitrary grouping of Credentials
      content: actual content of the message
      state: The underlying AcaPy message exchange state
      sent_time: sent_time data in AcaPy payload
      created_at: Timestamp when record was created in Traction
      updated_at: Timestamp when record was last modified in Traction
    """

    message_id: uuid.UUID = Field(
        sa_column=Column(
            UUID(as_uuid=True),
            primary_key=True,
            server_default=text("gen_random_uuid()"),
        )
    )
    tenant_id: uuid.UUID = Field(foreign_key="tenant.id", index=True)
    contact_id: uuid.UUID = Field(foreign_key="contact.contact_id", index=True)
    status: str = Field(nullable=False)
    role: str = Field(nullable=False)
    deleted: bool = Field(nullable=False, default=False)
    tags: List[str] = Field(sa_column=Column(ARRAY(String)))
    content: str = Field(nullable=True)
    revocation_comment: str = Field(nullable=True)

    # acapy data ---
    state: str = Field(nullable=False)
    sent_time: datetime = Field(sa_column=Column(TIMESTAMP, nullable=True))
    # --- acapy data

    # relationships ---
    contact: Optional[Contact] = Relationship(back_populates="messages")
    # --- relationships

    created_at: datetime = Field(
        sa_column=Column(TIMESTAMP, nullable=False, server_default=func.now())
    )
    updated_at: datetime = Field(
        sa_column=Column(
            TIMESTAMP, nullable=False, server_default=func.now(), onupdate=func.now()
        )
    )

    @classmethod
    async def get_by_id(
        cls: "Message",
        db: AsyncSession,
        tenant_id: uuid.UUID,
        message_id: uuid.UUID,
        deleted: bool | None = False,
    ) -> "Message":
        """Get Message by id.

        Find and return the database Message record

        Args:
          db: database session
          tenant_id: Traction ID of tenant making the call
          message_id: Traction ID of Message

        Returns: The Traction Message (db) record

        Raises:
          NotFoundError: if the Message cannot be found by ID and deleted
          flag
        """

        q = (
            select(cls)
            .where(cls.tenant_id == tenant_id)
            .where(cls.message_id == message_id)
            .where(cls.deleted == deleted)
            .options(selectinload(cls.contact))
        )
        q_result = await db.execute(q)
        db_rec = q_result.scalar_one_or_none()
        if not db_rec:
            raise NotFoundError(
                code="message.id_not_found",
                title="Message does not exist",
                detail=f"Message does not exist for id<{message_id}>",
            )
        return db_rec

    @classmethod
    async def list_by_contact_id(
        cls: "Message",
        db: AsyncSession,
        tenant_id: uuid.UUID,
        contact_id: uuid.UUID,
    ) -> List["Message"]:
        """List by Contact ID.

        Find and return list of Message records for Contact.

          tenant_id: Traction ID of tenant making the call
          contact_id: Traction ID of Contact

        Returns: List of Traction Message (db) records in descending order
        """

        q = (
            select(cls)
            .where(cls.contact_id == contact_id)
            .where(cls.tenant_id == tenant_id)
            .options(selectinload(cls.contact))
            .order_by(desc(cls.updated_at))
        )
        q_result = await db.execute(q)
        db_recs = q_result.scalars()
        return db_recs

    @classmethod
    async def list_by_tenant_id(
        cls: "Message",
        db: AsyncSession,
        tenant_id: uuid.UUID,
    ) -> List["Message"]:
        """List by Tenant ID.

        Find and return list of Message records for Tenant.

          tenant_id: Traction ID of tenant making the call

        Returns: List of Traction Message (db) records in descending order
        """

        q = (
            select(cls)
            .where(cls.tenant_id == tenant_id)
            .options(selectinload(cls.contact))
            .order_by(desc(cls.updated_at))
        )
        q_result = await db.execute(q)
        db_recs = q_result.scalars()
        return db_recs

sqlalchemy.dialects.postgresql.ARRAY sqlalchemy.select sqlalchemy.desc sqlalchemy.orm.selectinload sqlalchemy.func.now sqlalchemy.Column sqlmodel.Field sqlalchemy.text sqlalchemy.dialects.postgresql.UUID api.endpoints.models.v1.errors.NotFoundError sqlmodel.Relationship
[((1808, 1850), 'sqlmodel.Field', 'Field', ([], {'foreign_key': '"""tenant.id"""', 'index': '(True)'}), "(foreign_key='tenant.id', index=True)\n", (1813, 1850), False, 'from sqlmodel import Field, Relationship\n'), ((1879, 1930), 'sqlmodel.Field', 'Field', ([], {'foreign_key': '"""contact.contact_id"""', 'index': '(True)'}), "(foreign_key='contact.contact_id', index=True)\n", (1884, 1930), False, 'from sqlmodel import Field, Relationship\n'), ((1949, 1970), 'sqlmodel.Field', 'Field', ([], {'nullable': '(False)'}), '(nullable=False)\n', (1954, 1970), False, 'from sqlmodel import Field, Relationship\n'), ((1987, 2008), 'sqlmodel.Field', 'Field', ([], {'nullable': '(False)'}), '(nullable=False)\n', (1992, 2008), False, 'from sqlmodel import Field, Relationship\n'), ((2029, 2065), 'sqlmodel.Field', 'Field', ([], {'nullable': '(False)', 'default': '(False)'}), '(nullable=False, default=False)\n', (2034, 2065), False, 'from sqlmodel import Field, Relationship\n'), ((2146, 2166), 'sqlmodel.Field', 'Field', ([], {'nullable': '(True)'}), '(nullable=True)\n', (2151, 2166), False, 'from sqlmodel import Field, Relationship\n'), ((2197, 2217), 'sqlmodel.Field', 'Field', ([], {'nullable': '(True)'}), '(nullable=True)\n', (2202, 2217), False, 'from sqlmodel import Field, Relationship\n'), ((2257, 2278), 'sqlmodel.Field', 'Field', ([], {'nullable': '(False)'}), '(nullable=False)\n', (2262, 2278), False, 'from sqlmodel import Field, Relationship\n'), ((2434, 2473), 'sqlmodel.Relationship', 'Relationship', ([], {'back_populates': '"""messages"""'}), "(back_populates='messages')\n", (2446, 2473), False, 'from sqlmodel import Field, Relationship\n'), ((2321, 2353), 'sqlalchemy.Column', 'Column', (['TIMESTAMP'], {'nullable': '(True)'}), '(TIMESTAMP, nullable=True)\n', (2327, 2353), False, 'from sqlalchemy import Column, func, String, select, desc, text\n'), ((3593, 3618), 'sqlalchemy.orm.selectinload', 'selectinload', (['cls.contact'], {}), '(cls.contact)\n', (3605, 3618), False, 'from sqlalchemy.orm import selectinload\n'), ((3757, 3890), 'api.endpoints.models.v1.errors.NotFoundError', 'NotFoundError', ([], {'code': '"""message.id_not_found"""', 'title': '"""Message does not exist"""', 'detail': 'f"""Message does not exist for id<{message_id}>"""'}), "(code='message.id_not_found', title='Message does not exist',\n    detail=f'Message does not exist for id<{message_id}>')\n", (3770, 3890), False, 'from api.endpoints.models.v1.errors import NotFoundError\n'), ((4652, 4672), 'sqlalchemy.desc', 'desc', (['cls.updated_at'], {}), '(cls.updated_at)\n', (4656, 4672), False, 'from sqlalchemy import Column, func, String, select, desc, text\n'), ((5335, 5355), 'sqlalchemy.desc', 'desc', (['cls.updated_at'], {}), '(cls.updated_at)\n', (5339, 5355), False, 'from sqlalchemy import Column, func, String, select, desc, text\n'), ((1661, 1679), 'sqlalchemy.dialects.postgresql.UUID', 'UUID', ([], {'as_uuid': '(True)'}), '(as_uuid=True)\n', (1665, 1679), False, 'from sqlalchemy.dialects.postgresql import UUID, TIMESTAMP, ARRAY\n'), ((2111, 2124), 'sqlalchemy.dialects.postgresql.ARRAY', 'ARRAY', (['String'], {}), '(String)\n', (2116, 2124), False, 'from sqlalchemy.dialects.postgresql import UUID, TIMESTAMP, ARRAY\n'), ((1738, 1763), 'sqlalchemy.text', 'text', (['"""gen_random_uuid()"""'], {}), "('gen_random_uuid()')\n", (1742, 1763), False, 'from sqlalchemy import Column, func, String, select, desc, text\n'), ((2600, 2610), 'sqlalchemy.func.now', 'func.now', ([], {}), '()\n', (2608, 2610), False, 'from sqlalchemy import Column, func, String, select, desc, text\n'), ((2732, 2742), 'sqlalchemy.func.now', 'func.now', ([], {}), '()\n', (2740, 2742), False, 'from sqlalchemy import Column, func, String, select, desc, text\n'), ((2753, 2763), 'sqlalchemy.func.now', 'func.now', ([], {}), '()\n', (2761, 2763), False, 'from sqlalchemy import Column, func, String, select, desc, text\n'), ((4603, 4628), 'sqlalchemy.orm.selectinload', 'selectinload', (['cls.contact'], {}), '(cls.contact)\n', (4615, 4628), False, 'from sqlalchemy.orm import selectinload\n'), ((5286, 5311), 'sqlalchemy.orm.selectinload', 'selectinload', (['cls.contact'], {}), '(cls.contact)\n', (5298, 5311), False, 'from sqlalchemy.orm import selectinload\n'), ((5206, 5217), 'sqlalchemy.select', 'select', (['cls'], {}), '(cls)\n', (5212, 5217), False, 'from sqlalchemy import Column, func, String, select, desc, text\n'), ((3421, 3432), 'sqlalchemy.select', 'select', (['cls'], {}), '(cls)\n', (3427, 3432), False, 'from sqlalchemy import Column, func, String, select, desc, text\n'), ((4474, 4485), 'sqlalchemy.select', 'select', (['cls'], {}), '(cls)\n', (4480, 4485), False, 'from sqlalchemy import Column, func, String, select, desc, text\n')]