"""Plan of Action (POA) models.

Current baseline entities:
- PoaPlan
- PoaEvent
- PoaPlanTimezone
- PoaEventDependency
"""

import enum
from datetime import datetime
from typing import TYPE_CHECKING, Any, Optional

from sqlalchemy import (
    Boolean,
    CheckConstraint,
    DateTime,
    Enum,
    ForeignKey,
    Index,
    Integer,
    String,
    Text,
    UniqueConstraint,
)
from sqlalchemy.dialects.postgresql import JSONB
from sqlalchemy.orm import Mapped, mapped_column, relationship

from prism_models.base import BaseModel

if TYPE_CHECKING:
    from prism_models.chat import Contact


class PoaPlanStatus(str, enum.Enum):
    DRAFT = "draft"
    ACTIVE = "active"
    ARCHIVED = "archived"


class PoaEventType(str, enum.Enum):
    FLIGHT = "flight"
    LAYOVER = "layover"
    GROUND_TRANSPORT = "ground_transport"
    HOTEL = "hotel"
    BEDSIDE = "bedside"
    MEDICATION = "medication"
    PRE_FLIGHT_CHECK = "pre_flight_check"
    POST_FLIGHT_CHECK = "post_flight_check"
    OTHER = "other"


class PoaDependencyType(str, enum.Enum):
    PRECEDES = "precedes"
    REQUIRES_BUFFER = "requires_buffer"
    BLOCKS = "blocks"


class PoaPlan(BaseModel):
    """Top-level POA plan entity."""

    case_number: Mapped[str] = mapped_column(String(64), nullable=False, index=True)
    member_name: Mapped[str] = mapped_column(String(160), nullable=False, index=True)
    mission_title: Mapped[str | None] = mapped_column(String(200), nullable=True)

    status: Mapped[PoaPlanStatus] = mapped_column(
        Enum(PoaPlanStatus, native_enum=False),
        nullable=False,
        default=PoaPlanStatus.DRAFT,
        index=True,
    )

    starts_at: Mapped[datetime | None] = mapped_column(DateTime(timezone=True), nullable=True)
    ends_at: Mapped[datetime | None] = mapped_column(DateTime(timezone=True), nullable=True)

    created_by_contact_id: Mapped[int | None] = mapped_column(
        Integer,
        ForeignKey("contact.id", ondelete="SET NULL"),
        nullable=True,
        index=True,
    )
    updated_by_contact_id: Mapped[int | None] = mapped_column(
        Integer,
        ForeignKey("contact.id", ondelete="SET NULL"),
        nullable=True,
        index=True,
    )

    events: Mapped[list["PoaEvent"]] = relationship(
        "PoaEvent",
        back_populates="plan",
        cascade="all, delete-orphan",
        order_by="PoaEvent.start_at",
    )
    timezones: Mapped[list["PoaPlanTimezone"]] = relationship(
        "PoaPlanTimezone",
        back_populates="plan",
        cascade="all, delete-orphan",
        order_by="PoaPlanTimezone.display_order",
    )
    event_dependencies: Mapped[list["PoaEventDependency"]] = relationship(
        "PoaEventDependency",
        back_populates="plan",
        cascade="all, delete-orphan",
    )

    created_by_contact: Mapped[Optional["Contact"]] = relationship(
        "Contact", foreign_keys=[created_by_contact_id]
    )
    updated_by_contact: Mapped[Optional["Contact"]] = relationship(
        "Contact", foreign_keys=[updated_by_contact_id]
    )

    __table_args__ = (
        CheckConstraint("char_length(trim(case_number)) > 0", name="poa_plan_case_number_not_blank"),
        CheckConstraint("char_length(trim(member_name)) > 0", name="poa_plan_member_name_not_blank"),
        CheckConstraint(
            "ends_at IS NULL OR starts_at IS NULL OR ends_at > starts_at",
            name="poa_plan_time_window_valid",
        ),
        Index("ix_poa_plan_status_updated", "status", "updated_at"),
    )


class PoaEvent(BaseModel):
    """Timeline event associated with a POA plan."""

    plan_id: Mapped[int] = mapped_column(
        Integer,
        ForeignKey("poa_plan.id", ondelete="CASCADE"),
        nullable=False,
        index=True,
    )

    event_type: Mapped[PoaEventType] = mapped_column(
        Enum(PoaEventType, native_enum=False),
        nullable=False,
        index=True,
    )
    title: Mapped[str] = mapped_column(String(200), nullable=False)

    start_at: Mapped[datetime] = mapped_column(DateTime(timezone=True), nullable=False)
    start_tz: Mapped[str] = mapped_column(String(80), nullable=False)
    end_at: Mapped[datetime] = mapped_column(DateTime(timezone=True), nullable=False)
    end_tz: Mapped[str] = mapped_column(String(80), nullable=False)

    is_completed: Mapped[bool] = mapped_column(Boolean, nullable=False, default=False)

    notes: Mapped[str | None] = mapped_column(Text, nullable=True)
    location: Mapped[str | None] = mapped_column(String(240), nullable=True)
    contact_id: Mapped[int | None] = mapped_column(
        Integer,
        ForeignKey("contact.id", ondelete="SET NULL"),
        nullable=True,
        index=True,
    )
    contact: Mapped[Optional["Contact"]] = relationship("Contact", foreign_keys=[contact_id])
    details_json: Mapped[dict[str, Any] | None] = mapped_column(JSONB, nullable=True, default=dict)

    stack_row: Mapped[int] = mapped_column(Integer, nullable=False, default=0)
    sort_order: Mapped[int] = mapped_column(Integer, nullable=False, default=0)

    plan: Mapped["PoaPlan"] = relationship("PoaPlan", back_populates="events")
    outgoing_dependencies: Mapped[list["PoaEventDependency"]] = relationship(
        "PoaEventDependency",
        back_populates="from_event",
        foreign_keys="PoaEventDependency.from_event_id",
    )
    incoming_dependencies: Mapped[list["PoaEventDependency"]] = relationship(
        "PoaEventDependency",
        back_populates="to_event",
        foreign_keys="PoaEventDependency.to_event_id",
    )

    __table_args__ = (
        CheckConstraint("char_length(trim(title)) > 0", name="poa_event_title_not_blank"),
        CheckConstraint("end_at > start_at", name="poa_event_time_valid"),
        CheckConstraint("stack_row >= 0", name="poa_event_stack_row_non_negative"),
        Index("ix_poa_event_plan_time", "plan_id", "start_at", "end_at"),
        Index("ix_poa_event_plan_sort", "plan_id", "sort_order", "start_at"),
    )


class PoaPlanTimezone(BaseModel):
    """Ordered timezone references for a POA plan."""

    plan_id: Mapped[int] = mapped_column(
        Integer,
        ForeignKey("poa_plan.id", ondelete="CASCADE"),
        nullable=False,
        index=True,
    )
    iana_zone: Mapped[str] = mapped_column(String(80), nullable=False)
    display_order: Mapped[int] = mapped_column(Integer, nullable=False, default=0)

    plan: Mapped["PoaPlan"] = relationship("PoaPlan", back_populates="timezones")

    __table_args__ = (
        UniqueConstraint("plan_id", "iana_zone", name="uq_poa_plan_timezone_zone"),
        UniqueConstraint("plan_id", "display_order", name="uq_poa_plan_timezone_order"),
        CheckConstraint("display_order >= 0", name="poa_plan_timezone_order_non_negative"),
        Index("ix_poa_plan_timezone_plan_order", "plan_id", "display_order"),
    )


class PoaEventDependency(BaseModel):
    """Explicit sequencing and buffer constraints between POA events."""

    plan_id: Mapped[int] = mapped_column(
        Integer,
        ForeignKey("poa_plan.id", ondelete="CASCADE"),
        nullable=False,
        index=True,
    )
    from_event_id: Mapped[int] = mapped_column(
        Integer,
        ForeignKey("poa_event.id", ondelete="CASCADE"),
        nullable=False,
        index=True,
    )
    to_event_id: Mapped[int] = mapped_column(
        Integer,
        ForeignKey("poa_event.id", ondelete="CASCADE"),
        nullable=False,
        index=True,
    )

    dependency_type: Mapped[PoaDependencyType] = mapped_column(
        Enum(PoaDependencyType, native_enum=False),
        nullable=False,
        default=PoaDependencyType.PRECEDES,
        index=True,
    )
    min_buffer_minutes: Mapped[int | None] = mapped_column(Integer, nullable=True)

    created_by_contact_id: Mapped[int | None] = mapped_column(
        Integer,
        ForeignKey("contact.id", ondelete="SET NULL"),
        nullable=True,
        index=True,
    )

    plan: Mapped["PoaPlan"] = relationship("PoaPlan", back_populates="event_dependencies")
    from_event: Mapped["PoaEvent"] = relationship(
        "PoaEvent",
        back_populates="outgoing_dependencies",
        foreign_keys=[from_event_id],
    )
    to_event: Mapped["PoaEvent"] = relationship(
        "PoaEvent",
        back_populates="incoming_dependencies",
        foreign_keys=[to_event_id],
    )
    created_by_contact: Mapped[Optional["Contact"]] = relationship(
        "Contact", foreign_keys=[created_by_contact_id]
    )

    __table_args__ = (
        CheckConstraint("from_event_id <> to_event_id", name="poa_event_dependency_not_self"),
        CheckConstraint(
            "min_buffer_minutes IS NULL OR min_buffer_minutes >= 0",
            name="poa_event_dependency_buffer_non_negative",
        ),
        CheckConstraint(
            "dependency_type != 'requires_buffer' OR min_buffer_minutes IS NOT NULL",
            name="poa_event_dependency_requires_buffer_value",
        ),
        UniqueConstraint(
            "from_event_id",
            "to_event_id",
            "dependency_type",
            name="uq_poa_event_dependency",
        ),
        Index("ix_poa_event_dependency_plan", "plan_id"),
        Index("ix_poa_event_dependency_to_event", "to_event_id"),
    )
