from __future__ import annotations

import uuid
from datetime import datetime
from typing import TYPE_CHECKING, Any

import sqlalchemy as sa
from sqlalchemy.orm import Mapped, foreign, mapped_column, relationship

from ai.backend.common.data.notification import (
    EmailSpec,
    NotificationChannelType,
    NotificationRuleType,
    WebhookSpec,
)
from ai.backend.manager.data.notification import (
    NotificationChannelData,
    NotificationRuleData,
)
from ai.backend.manager.models.base import (
    GUID,
    Base,
)

if TYPE_CHECKING:
    from ai.backend.manager.models.user import UserRow


__all__ = (
    "NotificationChannelRow",
    "NotificationRuleRow",
)


# ========== ORM Models ==========


def _get_notification_channel_rules_join_condition() -> sa.ColumnElement[bool]:
    from ai.backend.manager.models.notification import NotificationRuleRow

    return NotificationChannelRow.id == foreign(NotificationRuleRow.channel_id)


def _get_notification_channel_creator_join_condition() -> sa.ColumnElement[bool]:
    from ai.backend.manager.models.user import UserRow

    return foreign(NotificationChannelRow.created_by) == UserRow.uuid


class NotificationChannelRow(Base):  # type: ignore[misc]
    __tablename__ = "notification_channels"

    id: Mapped[uuid.UUID] = mapped_column(
        "id", GUID, primary_key=True, server_default=sa.text("uuid_generate_v4()")
    )
    name: Mapped[str] = mapped_column("name", sa.String(length=256), nullable=False)
    description: Mapped[str | None] = mapped_column("description", sa.Text, nullable=True)
    channel_type: Mapped[str] = mapped_column(
        "channel_type",
        sa.String(length=64),
        nullable=False,
    )
    config: Mapped[dict[str, Any]] = mapped_column(
        "config",
        sa.JSON(none_as_null=True),
        nullable=False,
    )
    enabled: Mapped[bool] = mapped_column(
        "enabled", sa.Boolean, nullable=False, default=True, index=True
    )
    created_by: Mapped[uuid.UUID] = mapped_column("created_by", GUID, nullable=False)
    created_at: Mapped[datetime] = mapped_column(
        "created_at",
        sa.DateTime(timezone=True),
        nullable=False,
        default=sa.func.now(),
    )
    updated_at: Mapped[datetime] = mapped_column(
        "updated_at",
        sa.DateTime(timezone=True),
        nullable=False,
        default=sa.func.now(),
        onupdate=sa.func.now(),
    )

    # Relationships
    rules: Mapped[list[NotificationRuleRow]] = relationship(
        "NotificationRuleRow",
        back_populates="channel",
        primaryjoin=_get_notification_channel_rules_join_condition,
        foreign_keys=[id],
    )
    creator: Mapped[UserRow] = relationship(
        "UserRow",
        primaryjoin=_get_notification_channel_creator_join_condition,
        foreign_keys=[created_by],
        uselist=False,
    )

    def to_data(self) -> NotificationChannelData:
        """Convert Row to domain model data."""
        # Parse channel_type string to enum
        channel_type_enum = NotificationChannelType(self.channel_type)

        # Parse config based on channel_type
        parsed_config: WebhookSpec | EmailSpec
        match channel_type_enum:
            case NotificationChannelType.WEBHOOK:
                parsed_config = WebhookSpec.model_validate(self.config)
            case NotificationChannelType.EMAIL:
                parsed_config = EmailSpec.model_validate(self.config)

        return NotificationChannelData(
            id=self.id,
            name=self.name,
            description=self.description,
            channel_type=channel_type_enum,
            spec=parsed_config,
            enabled=self.enabled,
            created_by=self.created_by,
            created_at=self.created_at,
            updated_at=self.updated_at,
        )


def _get_notification_rule_channel_join_condition() -> sa.ColumnElement[bool]:
    from ai.backend.manager.models.notification import NotificationChannelRow

    return foreign(NotificationRuleRow.channel_id) == NotificationChannelRow.id


def _get_notification_rule_creator_join_condition() -> sa.ColumnElement[bool]:
    from ai.backend.manager.models.user import UserRow

    return foreign(NotificationRuleRow.created_by) == UserRow.uuid


class NotificationRuleRow(Base):  # type: ignore[misc]
    __tablename__ = "notification_rules"

    id: Mapped[uuid.UUID] = mapped_column(
        "id", GUID, primary_key=True, server_default=sa.text("uuid_generate_v4()")
    )
    name: Mapped[str] = mapped_column("name", sa.String(length=256), nullable=False)
    description: Mapped[str | None] = mapped_column("description", sa.Text, nullable=True)
    rule_type: Mapped[str] = mapped_column(
        "rule_type", sa.String(length=256), nullable=False, index=True
    )
    channel_id: Mapped[uuid.UUID] = mapped_column("channel_id", GUID, nullable=False)
    message_template: Mapped[str] = mapped_column("message_template", sa.Text, nullable=False)
    enabled: Mapped[bool] = mapped_column(
        "enabled", sa.Boolean, nullable=False, default=True, index=True
    )
    created_by: Mapped[uuid.UUID] = mapped_column("created_by", GUID, nullable=False)
    created_at: Mapped[datetime] = mapped_column(
        "created_at",
        sa.DateTime(timezone=True),
        nullable=False,
        default=sa.func.now(),
    )
    updated_at: Mapped[datetime] = mapped_column(
        "updated_at",
        sa.DateTime(timezone=True),
        nullable=False,
        default=sa.func.now(),
        onupdate=sa.func.now(),
    )

    # Relationships
    channel: Mapped[NotificationChannelRow] = relationship(
        "NotificationChannelRow",
        back_populates="rules",
        primaryjoin=_get_notification_rule_channel_join_condition,
        foreign_keys=[channel_id],
    )
    creator: Mapped[UserRow] = relationship(
        "UserRow",
        primaryjoin=_get_notification_rule_creator_join_condition,
        foreign_keys=[created_by],
        uselist=False,
    )

    def to_data(self) -> NotificationRuleData:
        """Convert Row to domain model data."""
        # Parse rule_type string to enum
        rule_type_enum = NotificationRuleType(self.rule_type)

        return NotificationRuleData(
            id=self.id,
            name=self.name,
            description=self.description,
            rule_type=rule_type_enum,
            channel=self.channel.to_data(),
            message_template=self.message_template,
            enabled=self.enabled,
            created_by=self.created_by,
            created_at=self.created_at,
            updated_at=self.updated_at,
        )
