from __future__ import annotations

import uuid
from datetime import datetime
from typing import Any

from sqlalchemy import (
    DateTime,
    Float,
    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


class RiskObservation(Base):
    __tablename__ = "risk_observations"
    __table_args__ = (
        UniqueConstraint(
            "tenant_id",
            "id",
            name="uq_risk_observations_tenant_id",
        ),
        UniqueConstraint(
            "tenant_id",
            "observation_key",
            name="uq_risk_observations_tenant_key",
        ),
        Index("ix_risk_observations_tenant_status", "tenant_id", "status", "created_at"),
        Index("ix_risk_observations_subject", "subject_type", "subject_key"),
        Index("ix_risk_observations_signal_level", "risk_signal", "risk_level"),
        Index("ix_risk_observations_status_created", "status", "created_at"),
    )

    id: Mapped[str] = mapped_column(String(36), primary_key=True, default=lambda: str(uuid.uuid4()))
    tenant_id: Mapped[str] = mapped_column(String(64), default="default", index=True)
    observation_key: Mapped[str] = mapped_column(String(160), index=True)
    subject_type: Mapped[str] = mapped_column(String(50), index=True)
    subject_key: Mapped[str] = mapped_column(String(160), index=True)
    subject_label: Mapped[str] = mapped_column(String(160), default="")
    claim_id: Mapped[str | None] = mapped_column(
        String(36),
        nullable=True,
        index=True,
    )
    claim_no: Mapped[str] = mapped_column(String(80), default="", index=True)
    run_id: Mapped[str | None] = mapped_column(String(80), nullable=True, index=True)
    execution_log_id: Mapped[str | None] = mapped_column(String(36), nullable=True, index=True)

    risk_type: Mapped[str] = mapped_column(String(80), index=True)
    risk_signal: Mapped[str] = mapped_column(String(100), index=True)
    title: Mapped[str] = mapped_column(String(200), default="")
    description: Mapped[str] = mapped_column(Text(), default="")
    risk_score: Mapped[int] = mapped_column(Integer, default=0, index=True)
    risk_level: Mapped[str] = mapped_column(String(20), index=True)
    confidence_score: Mapped[float] = mapped_column(Float, default=0.0)

    control_stage: Mapped[str] = mapped_column(String(50), default="")
    control_mode: Mapped[str] = mapped_column(String(50), default="")
    automation_mode: Mapped[str] = mapped_column(String(50), default="")
    source: Mapped[str] = mapped_column(String(60), default="", index=True)
    algorithm_version: Mapped[str] = mapped_column(String(80), default="", index=True)
    status: Mapped[str] = mapped_column(String(30), default="pending_review", index=True)
    feedback_status: Mapped[str] = mapped_column(String(30), default="unreviewed", index=True)

    contribution_scores_json: Mapped[dict[str, Any]] = mapped_column(JSON, default=dict)
    baseline_json: Mapped[dict[str, Any]] = mapped_column(JSON, default=dict)
    evidence_json: Mapped[list[Any]] = mapped_column(JSON, default=list)
    graph_node_keys_json: Mapped[list[Any]] = mapped_column(JSON, default=list)
    graph_edge_keys_json: Mapped[list[Any]] = mapped_column(JSON, default=list)
    policy_refs_json: Mapped[list[Any]] = mapped_column(JSON, default=list)
    similar_case_claim_ids_json: Mapped[list[Any]] = mapped_column(JSON, default=list)
    ontology_json: Mapped[dict[str, Any]] = mapped_column(JSON, default=dict)
    decision_trace_json: Mapped[dict[str, Any]] = mapped_column(JSON, default=dict)

    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(),
    )

    claim = relationship(
        "ExpenseClaim",
        primaryjoin="foreign(RiskObservation.claim_id) == ExpenseClaim.id",
        foreign_keys=[claim_id],
        viewonly=True,
    )
    feedback_items = relationship(
        "RiskObservationFeedback",
        back_populates="observation",
        cascade="all, delete-orphan",
        order_by="desc(RiskObservationFeedback.created_at)",
    )
    disposition = relationship(
        "RiskDisposition",
        back_populates="observation",
        passive_deletes=True,
        uselist=False,
    )

    @property
    def sampling_strategy(self) -> dict[str, Any]:
        value = (self.decision_trace_json or {}).get("sampling_strategy")
        return dict(value) if isinstance(value, dict) else {}

    @property
    def evaluation_case_id(self) -> str:
        return _json_text((self.decision_trace_json or {}).get("evaluation_case_id"))

    @property
    def ontology_parse_id(self) -> str:
        return _json_text((self.ontology_json or {}).get("ontology_parse_id"))

    @property
    def ontology_version(self) -> str:
        return _json_text((self.ontology_json or {}).get("ontology_version"))

    @property
    def domain(self) -> str:
        return _json_text((self.ontology_json or {}).get("domain"))

    @property
    def scenario(self) -> str:
        return _json_text((self.ontology_json or {}).get("scenario"))

    @property
    def intent(self) -> str:
        return _json_text((self.ontology_json or {}).get("intent"))

    @property
    def ontology_entities_json(self) -> list[Any]:
        value = (self.ontology_json or {}).get("ontology_entities_json")
        if value is None:
            value = (self.ontology_json or {}).get("entities")
        return list(value) if isinstance(value, list) else []

    @property
    def risk_signals_json(self) -> list[Any]:
        value = (self.ontology_json or {}).get("risk_signals_json")
        if value is None:
            value = (self.ontology_json or {}).get("risk_signals")
        return list(value) if isinstance(value, list) else []

    @property
    def canonical_subject_key(self) -> str:
        return _json_text((self.ontology_json or {}).get("canonical_subject_key"))


class RiskObservationFeedback(Base):
    __tablename__ = "risk_observation_feedback"
    __table_args__ = (
        Index("ix_risk_observation_feedback_type_created", "feedback_type", "created_at"),
    )

    id: Mapped[str] = mapped_column(String(36), primary_key=True, default=lambda: str(uuid.uuid4()))
    observation_id: Mapped[str] = mapped_column(
        ForeignKey("risk_observations.id"),
        index=True,
    )
    feedback_type: Mapped[str] = mapped_column(String(30), index=True)
    action: Mapped[str] = mapped_column(String(50), default="")
    actor: Mapped[str] = mapped_column(String(100), default="")
    comment: Mapped[str | None] = mapped_column(Text(), nullable=True)
    payload_json: Mapped[dict[str, Any]] = mapped_column(JSON, default=dict)
    created_at: Mapped[datetime] = mapped_column(DateTime(timezone=True), server_default=func.now())

    observation = relationship("RiskObservation", back_populates="feedback_items")

    @property
    def decision(self) -> str:
        return _json_text((self.payload_json or {}).get("decision")) or self.feedback_type

    @property
    def candidate_rule_source(self) -> str:
        return _json_text((self.payload_json or {}).get("candidate_rule_source"))

    @property
    def confidence_score(self) -> float:
        try:
            return float((self.payload_json or {}).get("confidence_score") or 0)
        except (TypeError, ValueError):
            return 0.0

    @property
    def escalation_target(self) -> str:
        return _json_text((self.payload_json or {}).get("escalation_target"))

    @property
    def supplement_required(self) -> bool:
        return bool((self.payload_json or {}).get("supplement_required"))


def _json_text(value: Any) -> str:
    return str(value or "").strip()
