"""
Visit order and sample models.

Tables in this file:
  visit_order   — the core order record (one per patient visit)
  order_test    — individual tests added to an order
  order_package — packages added to an order
  sample        — physical sample collected for an order

Order Architecture
──────────────────
One visit = one VisitOrder. A VisitOrder is the central entity that
ties together: patient, appointment, tests, packages, sample, report,
bill, and referral.

Pricing Snapshot Pattern
────────────────────────
Both order_test and order_package capture the price at the time the
order was created (price_at_order). This is critical:

  If a test price changes tomorrow, existing orders must NOT be affected.
  The bill is raised against price_at_order, not the current test.price.

  This means price_at_order is immutable once set. Any price correction
  requires a credit note or adjustment at the billing layer — NOT an
  update to price_at_order.

Order Number Format
───────────────────
Format: ORD{YY}{6-digit-seq}  e.g. ORD260001
Generated in the service layer (year-aware sequential). Unique per lab.

Referral
────────
referral_id is nullable. When set, the order earns a commission for the
referral source (handled in the commission module, Phase 4).
referred_by_custom is a free-text fallback for informal referrals not
registered as a Referral entity (e.g., "Referred by Dr. X from Walk-in").

Soft Delete Decisions
─────────────────────
  VisitOrder   → YES: orders anchor reports, bills, and payments. Must be retained.
  OrderTest    → NO:  status-based lifecycle (cancelled status replaces deletion).
  OrderPackage → NO:  same as OrderTest.
  Sample       → NO:  physical evidence — never deleted; statuses manage lifecycle.

Lab Scoping
───────────
  VisitOrder → YES (LabScopedMixin)
  OrderTest  → NO  (scoped via order_id → lab_id)
  OrderPackage→ NO  (scoped via order_id → lab_id)
  Sample     → YES (LabScopedMixin) — direct FK for performance on sample lookups
"""

import datetime
import enum
import uuid
from decimal import Decimal

import sqlalchemy as sa
from sqlalchemy import (
    JSON,
    Date,
    ForeignKey,
    Index,
    Numeric,
    String,
    Text,
    UniqueConstraint,
)
from sqlalchemy.dialects.postgresql import TIMESTAMP, UUID
from sqlalchemy.orm import Mapped, mapped_column, relationship

from app.models.base import (
    Base,
    LabScopedMixin,
    SoftDeleteMixin,
    TimestampMixin,
    UUIDPrimaryKeyMixin,
)

# ── Enums ──────────────────────────────────────────────────────────────────────


class OrderStatus(enum.StrEnum):
    """
    Lifecycle state of a visit order.

    pending          → order created, awaiting sample
    confirmed        → order reviewed and confirmed by staff
    sample_collected → physical sample has been taken
    processing       → sample is in the lab, tests being run
    completed        → all test results entered, report ready
    cancelled        → order cancelled (no bill settled)
    """

    PENDING = "pending"
    CONFIRMED = "confirmed"
    SAMPLE_COLLECTED = "sample_collected"
    PROCESSING = "processing"
    COMPLETED = "completed"
    CANCELLED = "cancelled"


class OrderTestStatus(enum.StrEnum):
    """
    Per-test status within an order.
    Allows individual tests to progress independently within a multi-test order.

    draft      → added to order but not started
    processing → sample received, test being run
    verified   → result entered and verified by pathologist/doctor
    released   → result released to patient report
    cancelled  → test removed from order (price_at_order is voided at billing)
    """

    DRAFT = "draft"
    PROCESSING = "processing"
    VERIFIED = "verified"
    RELEASED = "released"
    CANCELLED = "cancelled"


class SampleStatus(enum.StrEnum):
    """Physical sample lifecycle."""

    PENDING = "pending"  # Awaiting collection
    COLLECTED = "collected"  # Sample taken from patient
    PROCESSING = "processing"  # In the lab
    COMPLETED = "completed"  # Testing done on this sample


OrderStatusType = sa.Enum(
    OrderStatus,
    name="order_status",
    native_enum=True,
    create_constraint=True,
    values_callable=lambda obj: [e.value for e in obj],
)
OrderTestStatusType = sa.Enum(
    OrderTestStatus,
    name="order_test_status",
    native_enum=True,
    create_constraint=True,
    values_callable=lambda obj: [e.value for e in obj],
)
SampleStatusType = sa.Enum(
    SampleStatus,
    name="sample_status",
    native_enum=True,
    create_constraint=True,
    values_callable=lambda obj: [e.value for e in obj],
)


# ── Models ─────────────────────────────────────────────────────────────────────


class VisitOrder(UUIDPrimaryKeyMixin, TimestampMixin, SoftDeleteMixin, LabScopedMixin, Base):
    """
    Central record for a patient's visit to the lab.

    A VisitOrder binds together:
      patient → tests + packages → sample → report → bill

    One patient visit = one VisitOrder. Multiple tests are added as
    OrderTest/OrderPackage rows, not as separate orders.

    The appointment_id is nullable: walk-in patients have no prior appointment.
    The referral_id is nullable: not all visits come via referral.
    """

    __tablename__ = "visit_order"
    __table_args__ = (
        UniqueConstraint("lab_id", "order_number", name="uq_visit_order_lab_id_order_number"),
        Index("idx_orders_lab_status", "lab_id", "status", sa.text("created_at DESC")),
    )

    patient_id: Mapped[uuid.UUID] = mapped_column(
        UUID(as_uuid=True),
        ForeignKey("patient.id", ondelete="RESTRICT"),
        nullable=False,
        index=True,
        comment="FK → patient.id",
    )
    appointment_id: Mapped[uuid.UUID | None] = mapped_column(
        UUID(as_uuid=True),
        ForeignKey("appointment.id", ondelete="SET NULL"),
        nullable=True,
        index=True,
        comment="FK → appointment.id. NULL for walk-in orders.",
    )
    referral_id: Mapped[uuid.UUID | None] = mapped_column(
        UUID(as_uuid=True),
        ForeignKey("referral.id", ondelete="SET NULL"),
        nullable=True,
        index=True,
        comment="FK → referral.id. NULL if not referred via a registered referral source.",
    )
    order_number: Mapped[str] = mapped_column(
        String(30),
        nullable=False,
        index=True,
        comment="Human-readable order ID: ORD{YY}{6-digit-seq} e.g. ORD260001",
    )
    status: Mapped[OrderStatus] = mapped_column(
        OrderStatusType,
        nullable=False,
        default=OrderStatus.PENDING,
        server_default="pending",
        index=True,
        comment="Order lifecycle state",
    )
    referred_by_custom: Mapped[str | None] = mapped_column(
        String(300),
        nullable=True,
        comment="Free-text referral note (e.g., 'Dr. Smith, City Hospital') when not using referral table",
    )
    next_date: Mapped[datetime.date | None] = mapped_column(
        Date,
        nullable=True,
        comment="Optional follow-up date for the patient",
    )
    destination_country: Mapped[str | None] = mapped_column(
        String(100),
        nullable=True,
        comment="Country to which the report will be submitted (printed on report header)",
    )
    notes: Mapped[str | None] = mapped_column(
        Text,
        nullable=True,
        comment="Internal notes from staff",
    )

    @property
    def is_abroad(self) -> bool:
        """Convenience flag indicating pre-departure / abroad visa medical."""
        return bool(self.destination_country and self.destination_country.strip())

    # Relationships
    lab: Mapped["Lab"] = relationship(  # type: ignore[name-defined]
        "Lab",
        foreign_keys="[VisitOrder.lab_id]",
        lazy="selectin",
    )
    patient: Mapped["Patient"] = relationship(  # type: ignore[name-defined]
        "Patient",
        foreign_keys="[VisitOrder.patient_id]",
        lazy="selectin",
    )
    appointment: Mapped["Appointment | None"] = relationship(  # type: ignore[name-defined]
        "Appointment",
        foreign_keys="[VisitOrder.appointment_id]",
        lazy="selectin",
    )
    referral: Mapped["Referral | None"] = relationship(  # type: ignore[name-defined]
        "Referral",
        foreign_keys="[VisitOrder.referral_id]",
        lazy="selectin",
    )
    order_tests: Mapped[list["OrderTest"]] = relationship(
        back_populates="order",
        lazy="noload",
        cascade="all, delete-orphan",
    )
    order_packages: Mapped[list["OrderPackage"]] = relationship(
        back_populates="order",
        lazy="noload",
        cascade="all, delete-orphan",
    )
    samples: Mapped[list["Sample"]] = relationship(
        back_populates="order",
        lazy="noload",
    )
    bill: Mapped["Bill | None"] = relationship(  # type: ignore[name-defined]
        "Bill",
        primaryjoin="VisitOrder.id == Bill.order_id",
        foreign_keys="Bill.order_id",
        uselist=False,
        viewonly=True,
        lazy="noload",
    )
    reports: Mapped[list["Report"]] = relationship(  # type: ignore[name-defined]
        "Report",
        primaryjoin="VisitOrder.id == Report.order_id",
        foreign_keys="Report.order_id",
        viewonly=True,
        lazy="noload",
    )

    def __repr__(self) -> str:
        return (
            f"<VisitOrder id={self.id!s:.8} order_number={self.order_number!r} status={self.status.value!r}>"
        )


class OrderTest(UUIDPrimaryKeyMixin, TimestampMixin, Base):
    """
    A single test added to a visit order.

    price_at_order is a snapshot of test.price at the time of order creation.
    It is immutable — billing always uses this value, never the current test.price.

    status tracks this individual test through the lab workflow independently
    of other tests in the same order (a complex order may have some tests
    completed before others).

    No soft delete: use status=CANCELLED instead of deletion.
    """

    __tablename__ = "order_test"
    __table_args__ = (UniqueConstraint("order_id", "test_id", name="uq_order_test_order_id_test_id"),)

    order_id: Mapped[uuid.UUID] = mapped_column(
        UUID(as_uuid=True),
        ForeignKey("visit_order.id", ondelete="CASCADE"),
        nullable=False,
        index=True,
        comment="FK → visit_order.id",
    )
    test_id: Mapped[uuid.UUID] = mapped_column(
        UUID(as_uuid=True),
        ForeignKey("test.id", ondelete="RESTRICT"),
        nullable=False,
        index=True,
        comment="FK → test.id",
    )
    price_at_order: Mapped[Decimal] = mapped_column(
        Numeric(10, 2),
        nullable=False,
        comment="Price snapshot from test.price at order creation. Immutable.",
    )
    selected_parameter_ids: Mapped[list[str] | None] = mapped_column(
        JSON,
        nullable=True,
        comment="Optional analyte scope: parameter UUID strings ordered for this test. NULL = whole panel.",
    )
    status: Mapped[OrderTestStatus] = mapped_column(
        OrderTestStatusType,
        nullable=False,
        default=OrderTestStatus.DRAFT,
        server_default="draft",
        index=True,
        comment="Per-test status within this order",
    )

    # Relationships
    order: Mapped["VisitOrder"] = relationship(
        back_populates="order_tests",
        lazy="selectin",
    )
    test: Mapped["Test"] = relationship(  # type: ignore[name-defined]
        "Test",
        lazy="selectin",
    )

    def __repr__(self) -> str:
        return (
            f"<OrderTest order_id={self.order_id!s:.8} "
            f"test_id={self.test_id!s:.8} "
            f"status={self.status.value!r}>"
        )


class OrderPackage(UUIDPrimaryKeyMixin, TimestampMixin, Base):
    """
    A package added to a visit order.

    price_at_order is a snapshot of package.price at order creation.
    Immutable post-creation.

    No per-package-item status: a package is booked as a unit.
    Individual test statuses are tracked via the order_test rows
    that correspond to the tests within the package.

    No soft delete: remove by updating order status or voiding at billing.
    """

    __tablename__ = "order_package"
    __table_args__ = (
        UniqueConstraint(
            "order_id",
            "package_id",
            name="uq_order_package_order_id_package_id",
        ),
    )

    order_id: Mapped[uuid.UUID] = mapped_column(
        UUID(as_uuid=True),
        ForeignKey("visit_order.id", ondelete="CASCADE"),
        nullable=False,
        index=True,
        comment="FK → visit_order.id",
    )
    package_id: Mapped[uuid.UUID] = mapped_column(
        UUID(as_uuid=True),
        ForeignKey("test_package.id", ondelete="RESTRICT"),
        nullable=False,
        index=True,
        comment="FK → test_package.id",
    )
    price_at_order: Mapped[Decimal] = mapped_column(
        Numeric(10, 2),
        nullable=False,
        comment="Price snapshot from package.price at order creation. Immutable.",
    )

    # Relationships
    order: Mapped["VisitOrder"] = relationship(
        back_populates="order_packages",
        lazy="selectin",
    )
    package: Mapped["TestPackage"] = relationship(  # type: ignore[name-defined]
        "TestPackage",
        lazy="selectin",
    )

    def __repr__(self) -> str:
        return f"<OrderPackage order_id={self.order_id!s:.8} package_id={self.package_id!s:.8}>"


class Sample(UUIDPrimaryKeyMixin, TimestampMixin, LabScopedMixin, Base):
    """
    A physical sample collected from a patient for an order.

    sample_code: human-readable lab identifier (e.g., S/26/000001)
    barcode: scanner-readable code printed on the sample tube label
             Format: SAM-{YYYYMMDD}-{6-digit-seq}  e.g. SAM-20260920-000001
             Globally unique, indexed. Scan → lookup by this field.

    sample_type: free-text string for flexibility.
                 Common values: blood, urine, stool, sputum, swab, serum,
                 plasma, csf, tissue, saliva. Not an enum to allow custom types.

    collected_by: FK → user (the phlebotomist). Nullable — set at collection time.
    collected_at: TIMESTAMPTZ — the actual collection timestamp. Nullable initially.

    No soft delete: physical samples are never "deleted" — they exist.
                    Status manages the lifecycle.
    Lab scoping: YES — direct lab_id for efficient branch-level sample lookups
                       (barcode scanning at any branch needs to scope by lab).
    """

    __tablename__ = "sample"
    __table_args__ = (
        UniqueConstraint("lab_id", "sample_code", name="uq_sample_lab_id_sample_code"),
        UniqueConstraint("barcode", name="uq_sample_barcode"),
        Index("idx_sample_lab_id", "lab_id"),
        Index("idx_sample_barcode_lab", "barcode", "lab_id"),
    )

    order_id: Mapped[uuid.UUID] = mapped_column(
        UUID(as_uuid=True),
        ForeignKey("visit_order.id", ondelete="RESTRICT"),
        nullable=False,
        index=True,
        comment="FK → visit_order.id — the order this sample was collected for",
    )
    sample_code: Mapped[str] = mapped_column(
        String(30),
        nullable=False,
        index=True,
        comment="Human-readable code: S/{YY}/{6-digit-seq} e.g. S/26/000001",
    )
    barcode: Mapped[str] = mapped_column(
        String(50),
        nullable=False,
        index=True,
        comment=(
            "Scanner-readable barcode value: SAM-{YYYYMMDD}-{NNNNNN} "
            "Printed on tube label. Lookup by this field on scan."
        ),
    )
    sample_type: Mapped[str] = mapped_column(
        String(100),
        nullable=False,
        index=True,
        comment="Sample type (e.g., blood, urine, stool). Free-text for flexibility.",
    )
    status: Mapped[SampleStatus] = mapped_column(
        SampleStatusType,
        nullable=False,
        default=SampleStatus.PENDING,
        server_default="pending",
        index=True,
        comment="Physical sample lifecycle state",
    )
    collected_by: Mapped[uuid.UUID | None] = mapped_column(
        UUID(as_uuid=True),
        ForeignKey("user.id", ondelete="SET NULL"),
        nullable=True,
        index=True,
        comment="FK → user.id (phlebotomist). Set at sample collection time.",
    )
    collected_at: Mapped[datetime.datetime | None] = mapped_column(
        TIMESTAMP(timezone=True),
        nullable=True,
        comment="Actual sample collection timestamp (TIMESTAMPTZ). Set at collection.",
    )
    notes: Mapped[str | None] = mapped_column(
        Text,
        nullable=True,
        comment="Haemolysis, lipemia, quantity notes, or collection remarks",
    )

    # Relationships
    lab: Mapped["Lab"] = relationship(  # type: ignore[name-defined]
        "Lab",
        foreign_keys="[Sample.lab_id]",
        lazy="selectin",
    )
    order: Mapped["VisitOrder"] = relationship(
        back_populates="samples",
        lazy="selectin",
    )
    collected_by_user: Mapped["User | None"] = relationship(  # type: ignore[name-defined]
        "User",
        foreign_keys="[Sample.collected_by]",
        lazy="selectin",
    )

    def __repr__(self) -> str:
        return f"<Sample id={self.id!s:.8} barcode={self.barcode!r} status={self.status.value!r}>"
