from __future__ import annotations

import uuid
from datetime import datetime
from typing import Any

from sqlalchemy import (
    CheckConstraint,
    DateTime,
    ForeignKeyConstraint,
    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 RiskDisposition(Base):
    """风险观察的当前处置投影；裁决结论和处置生命周期彼此独立。"""

    __tablename__ = "risk_dispositions"
    __table_args__ = (
        UniqueConstraint(
            "tenant_id",
            "id",
            name="uq_risk_dispositions_tenant_id",
        ),
        UniqueConstraint(
            "tenant_id",
            "observation_id",
            name="uq_risk_dispositions_tenant_observation",
        ),
        CheckConstraint(
            "adjudication IN ('unreviewed', 'confirmed', 'false_positive')",
            name="ck_risk_dispositions_adjudication",
        ),
        CheckConstraint(
            "lifecycle_status IN ('open', 'supplement_requested', "
            "'remediation_in_progress', 'waiver_requested', 'waived', "
            "'waiver_rejected', 'resolved')",
            name="ck_risk_dispositions_lifecycle",
        ),
        CheckConstraint("version >= 0", name="ck_risk_dispositions_version"),
        CheckConstraint(
            "(waiver_requester_id IS NULL AND waiver_requester_name IS NULL "
            "AND waiver_requested_at IS NULL AND waiver_reason IS NULL "
            "AND waiver_scope IS NULL AND waiver_expires_at IS NULL) OR "
            "(waiver_requester_id IS NOT NULL "
            "AND length(trim(waiver_requester_id)) > 0 "
            "AND waiver_requester_name IS NOT NULL "
            "AND length(trim(waiver_requester_name)) > 0 "
            "AND waiver_requested_at IS NOT NULL "
            "AND waiver_reason IS NOT NULL AND length(trim(waiver_reason)) > 0 "
            "AND waiver_scope IS NOT NULL AND length(trim(waiver_scope)) > 0 "
            "AND waiver_expires_at IS NOT NULL "
            "AND waiver_expires_at > waiver_requested_at)",
            name="ck_risk_dispositions_waiver_request",
        ),
        CheckConstraint(
            "(waiver_decision IS NULL AND waiver_decider_id IS NULL "
            "AND waiver_decider_name IS NULL AND waiver_decided_at IS NULL "
            "AND waiver_decision_reason IS NULL) OR "
            "(waiver_decision IN ('approved', 'rejected') "
            "AND waiver_requester_id IS NOT NULL "
            "AND waiver_decider_id IS NOT NULL "
            "AND length(trim(waiver_decider_id)) > 0 "
            "AND waiver_decider_name IS NOT NULL "
            "AND length(trim(waiver_decider_name)) > 0 "
            "AND waiver_decided_at IS NOT NULL "
            "AND waiver_decision_reason IS NOT NULL "
            "AND length(trim(waiver_decision_reason)) > 0)",
            name="ck_risk_dispositions_waiver_decision",
        ),
        CheckConstraint(
            "(lifecycle_status != 'waived' OR waiver_decision = 'approved') AND "
            "(lifecycle_status != 'waiver_rejected' OR waiver_decision = 'rejected')",
            name="ck_risk_dispositions_waiver_lifecycle",
        ),
        ForeignKeyConstraint(
            ["tenant_id", "observation_id"],
            ["risk_observations.tenant_id", "risk_observations.id"],
            ondelete="RESTRICT",
            name="fk_risk_dispositions_tenant_observation",
        ),
        Index(
            "ix_risk_dispositions_tenant_lifecycle_due",
            "tenant_id",
            "lifecycle_status",
            "due_at",
        ),
        Index("ix_risk_dispositions_assignee", "tenant_id", "assignee"),
        Index(
            "ix_risk_dispositions_tenant_waiver_expiry",
            "tenant_id",
            "lifecycle_status",
            "waiver_expires_at",
        ),
    )

    id: Mapped[str] = mapped_column(String(36), primary_key=True, default=_new_id)
    tenant_id: Mapped[str] = mapped_column(String(64), nullable=False)
    observation_id: Mapped[str] = mapped_column(String(36), nullable=False)
    adjudication: Mapped[str] = mapped_column(
        String(24),
        nullable=False,
        default="unreviewed",
        server_default="unreviewed",
    )
    lifecycle_status: Mapped[str] = mapped_column(
        String(32),
        nullable=False,
        default="open",
        server_default="open",
    )
    version: Mapped[int] = mapped_column(
        Integer,
        nullable=False,
        default=0,
        server_default="0",
    )
    assignee: Mapped[str | None] = mapped_column(String(120), nullable=True)
    due_at: Mapped[datetime | None] = mapped_column(DateTime(timezone=True), nullable=True)
    resolution: Mapped[str | None] = mapped_column(Text(), nullable=True)
    waiver_requester_id: Mapped[str | None] = mapped_column(String(120), nullable=True)
    waiver_requester_name: Mapped[str | None] = mapped_column(String(120), nullable=True)
    waiver_requested_at: Mapped[datetime | None] = mapped_column(
        DateTime(timezone=True), nullable=True
    )
    waiver_reason: Mapped[str | None] = mapped_column(Text(), nullable=True)
    waiver_scope: Mapped[str | None] = mapped_column(Text(), nullable=True)
    waiver_expires_at: Mapped[datetime | None] = mapped_column(
        DateTime(timezone=True), nullable=True
    )
    waiver_conditions_json: Mapped[list[str]] = mapped_column(
        JSON, nullable=False, default=list
    )
    waiver_decision: Mapped[str | None] = mapped_column(String(20), nullable=True)
    waiver_decider_id: Mapped[str | None] = mapped_column(String(120), nullable=True)
    waiver_decider_name: Mapped[str | None] = mapped_column(String(120), nullable=True)
    waiver_decided_at: Mapped[datetime | None] = mapped_column(
        DateTime(timezone=True), nullable=True
    )
    waiver_decision_reason: Mapped[str | None] = mapped_column(Text(), nullable=True)
    created_at: Mapped[datetime] = mapped_column(
        DateTime(timezone=True),
        nullable=False,
        server_default=func.now(),
    )
    updated_at: Mapped[datetime] = mapped_column(
        DateTime(timezone=True),
        nullable=False,
        server_default=func.now(),
        onupdate=func.now(),
    )

    observation = relationship("RiskObservation", back_populates="disposition")
    events = relationship(
        "RiskDispositionEvent",
        back_populates="disposition",
        order_by="asc(RiskDispositionEvent.version)",
        lazy="selectin",
        passive_deletes=True,
    )


class RiskDispositionEvent(Base):
    """只追加的风险处置事实；每个版本只对应一个类型化动作。"""

    __tablename__ = "risk_disposition_events"
    __table_args__ = (
        UniqueConstraint(
            "tenant_id",
            "request_id",
            name="uq_risk_disposition_events_tenant_request",
        ),
        UniqueConstraint(
            "disposition_id",
            "version",
            name="uq_risk_disposition_events_version",
        ),
        CheckConstraint(
            "action IN ('confirm', 'false_positive', 'request_supplement', "
            "'start_remediation', 'resolve', 'request_waiver', "
            "'approve_waiver', 'reject_waiver')",
            name="ck_risk_disposition_events_action",
        ),
        CheckConstraint("version > 0", name="ck_risk_disposition_events_version"),
        ForeignKeyConstraint(
            ["tenant_id", "disposition_id"],
            ["risk_dispositions.tenant_id", "risk_dispositions.id"],
            ondelete="RESTRICT",
            name="fk_risk_disposition_events_tenant_disposition",
        ),
        ForeignKeyConstraint(
            ["tenant_id", "observation_id"],
            ["risk_observations.tenant_id", "risk_observations.id"],
            ondelete="RESTRICT",
            name="fk_risk_disposition_events_tenant_observation",
        ),
        Index(
            "ix_risk_disposition_events_tenant_observation_time",
            "tenant_id",
            "observation_id",
            "created_at",
        ),
    )

    id: Mapped[str] = mapped_column(String(36), primary_key=True, default=_new_id)
    tenant_id: Mapped[str] = mapped_column(String(64), nullable=False)
    disposition_id: Mapped[str] = mapped_column(String(36), nullable=False, index=True)
    observation_id: Mapped[str] = mapped_column(String(36), nullable=False)
    version: Mapped[int] = mapped_column(Integer, nullable=False)
    action: Mapped[str] = mapped_column(String(32), nullable=False)
    actor_id: Mapped[str] = mapped_column(String(120), nullable=False)
    actor_name: Mapped[str] = mapped_column(String(120), nullable=False)
    request_id: Mapped[str] = mapped_column(String(120), nullable=False)
    payload_fingerprint: Mapped[str] = mapped_column(String(64), nullable=False)
    comment: Mapped[str | None] = mapped_column(Text(), nullable=True)
    before_json: Mapped[dict[str, Any]] = mapped_column(JSON, nullable=False, default=dict)
    after_json: Mapped[dict[str, Any]] = mapped_column(JSON, nullable=False, default=dict)
    response_json: Mapped[dict[str, Any] | None] = mapped_column(JSON, nullable=True)
    created_at: Mapped[datetime] = mapped_column(
        DateTime(timezone=True),
        nullable=False,
        server_default=func.now(),
    )

    disposition = relationship("RiskDisposition", back_populates="events")
