from __future__ import annotations

import uuid
from datetime import datetime
from typing import Any

from sqlalchemy import DateTime, ForeignKey, Index, Integer, String, Text, UniqueConstraint, func
from sqlalchemy.orm import Mapped, mapped_column, relationship
from sqlalchemy.types import JSON

from app.db.base_class import Base


def _new_id() -> str:
    return str(uuid.uuid4())


class ExpenseCase(Base):
    __tablename__ = "expense_cases"
    __table_args__ = (
        UniqueConstraint("tenant_id", "id", name="uq_expense_cases_tenant_id"),
        UniqueConstraint("tenant_id", "case_no", name="uq_expense_cases_tenant_case_no"),
        Index("ix_expense_cases_tenant_stage", "tenant_id", "current_stage"),
        Index("ix_expense_cases_tenant_status", "tenant_id", "status"),
    )

    id: Mapped[str] = mapped_column(String(36), primary_key=True, default=_new_id)
    tenant_id: Mapped[str] = mapped_column(String(64), nullable=False, index=True)
    case_no: Mapped[str] = mapped_column(String(80), nullable=False)
    scene_code: Mapped[str] = mapped_column(String(50), nullable=False, default="other")
    title: Mapped[str] = mapped_column(String(200), nullable=False)
    owner_employee_id: Mapped[str | None] = mapped_column(String(36), nullable=True, index=True)
    current_stage: Mapped[str] = mapped_column(String(40), nullable=False, default="claiming")
    status: Mapped[str] = mapped_column(String(30), nullable=False, default="active")
    created_at: Mapped[datetime] = mapped_column(DateTime(timezone=True), server_default=func.now())
    updated_at: Mapped[datetime] = mapped_column(
        DateTime(timezone=True), server_default=func.now(), onupdate=func.now()
    )

    links = relationship(
        "ExpenseCaseLink",
        back_populates="expense_case",
        cascade="all, delete-orphan",
        order_by="asc(ExpenseCaseLink.created_at)",
    )
    events = relationship(
        "BusinessEvent",
        back_populates="expense_case",
        cascade="all, delete-orphan",
        order_by="asc(BusinessEvent.occurred_at)",
    )


class ExpenseCaseLink(Base):
    __tablename__ = "expense_case_links"
    __table_args__ = (
        UniqueConstraint("resource_type", "resource_id", name="uq_expense_case_links_resource"),
        Index("ix_expense_case_links_tenant_case", "tenant_id", "expense_case_id"),
    )

    id: Mapped[str] = mapped_column(String(36), primary_key=True, default=_new_id)
    tenant_id: Mapped[str] = mapped_column(String(64), nullable=False, index=True)
    expense_case_id: Mapped[str] = mapped_column(
        ForeignKey("expense_cases.id", ondelete="CASCADE"), nullable=False
    )
    resource_type: Mapped[str] = mapped_column(String(50), nullable=False)
    resource_id: Mapped[str] = mapped_column(String(100), nullable=False)
    relation_type: Mapped[str] = mapped_column(String(50), nullable=False)
    created_at: Mapped[datetime] = mapped_column(DateTime(timezone=True), server_default=func.now())

    expense_case = relationship("ExpenseCase", back_populates="links")


class BusinessEvent(Base):
    __tablename__ = "business_events"
    __table_args__ = (
        UniqueConstraint(
            "tenant_id",
            "expense_case_id",
            "id",
            name="uq_business_events_tenant_case_id",
        ),
        UniqueConstraint(
            "tenant_id",
            "aggregate_type",
            "aggregate_id",
            "event_type",
            "idempotency_key",
            name="uq_business_event_idempotency",
        ),
        Index("ix_business_events_tenant_case_time", "tenant_id", "expense_case_id", "occurred_at"),
        Index("ix_business_events_outbox", "delivery_status", "occurred_at"),
        Index("ix_business_events_aggregate", "aggregate_type", "aggregate_id"),
    )

    id: Mapped[str] = mapped_column(String(36), primary_key=True, default=_new_id)
    tenant_id: Mapped[str] = mapped_column(String(64), nullable=False, index=True)
    expense_case_id: Mapped[str] = mapped_column(
        ForeignKey("expense_cases.id", ondelete="CASCADE"), nullable=False
    )
    aggregate_type: Mapped[str] = mapped_column(String(50), nullable=False)
    aggregate_id: Mapped[str] = mapped_column(String(100), nullable=False)
    event_type: Mapped[str] = mapped_column(String(80), nullable=False, index=True)
    event_version: Mapped[int] = mapped_column(Integer, nullable=False, default=1)
    idempotency_key: Mapped[str] = mapped_column(String(120), nullable=False)
    correlation_id: Mapped[str] = mapped_column(String(64), nullable=False, index=True)
    causation_id: Mapped[str | None] = mapped_column(String(64), nullable=True)
    actor_id: Mapped[str] = mapped_column(String(120), nullable=False)
    actor_type: Mapped[str] = mapped_column(String(30), nullable=False, default="user")
    payload_json: Mapped[dict[str, Any]] = mapped_column(JSON, nullable=False, default=dict)
    delivery_status: Mapped[str] = mapped_column(String(20), nullable=False, default="pending")
    delivery_attempts: Mapped[int] = mapped_column(Integer, nullable=False, default=0)
    last_delivery_error: Mapped[str | None] = mapped_column(Text(), nullable=True)
    occurred_at: Mapped[datetime] = mapped_column(
        DateTime(timezone=True), server_default=func.now()
    )
    published_at: Mapped[datetime | None] = mapped_column(DateTime(timezone=True), nullable=True)

    expense_case = relationship("ExpenseCase", back_populates="events")
