"""失败签名、失败实例、Evidence Bundle、审计。"""
from __future__ import annotations

import datetime as _dt
from typing import Any, Dict, List, Optional

from sqlalchemy import (
    DateTime,
    Float,
    ForeignKey,
    Index,
    Integer,
    JSON,
    String,
    Text,
    UniqueConstraint,
)
from sqlalchemy.orm import Mapped, mapped_column

from ..enums import FailureClass, IntegrityState
from .base import Base, TimestampMixin, pk


class FailureSignature(Base, TimestampMixin):
    """失败签名 —— 聚类锚点。

    8 要素哈希（方案 §8.3）。只用错误码会把"掉盘"和"脚本超时"归成一类，
    聚类就废了；用原始日志则每条日志都是新签名。所以用模板化后的日志指纹。
    """

    __tablename__ = "failure_signatures"
    __table_args__ = (UniqueConstraint("hash", name="uq_signature_hash"),)

    id: Mapped[str] = pk("sig")
    hash: Mapped[str] = mapped_column(String(64), nullable=False)
    title: Mapped[str] = mapped_column(String(255), nullable=False)

    # 8 要素
    workflow_step: Mapped[str] = mapped_column(String(64), nullable=False)
    error_codes: Mapped[List[Any]] = mapped_column(JSON, default=list)
    log_templates: Mapped[List[Any]] = mapped_column(JSON, default=list)
    host_state: Mapped[str] = mapped_column(String(32), default="UNKNOWN")
    dut_enumeration_state: Mapped[str] = mapped_column(String(32), default="UNKNOWN")
    data_integrity_state: Mapped[str] = mapped_column(String(16), default=IntegrityState.NOT_CHECKED.value)
    recovery_outcome: Mapped[Optional[str]] = mapped_column(String(16))
    environment_fingerprint_hash: Mapped[Optional[str]] = mapped_column(String(64))

    failure_class: Mapped[str] = mapped_column(String(24), default=FailureClass.UNKNOWN.value)
    occurrences: Mapped[int] = mapped_column(Integer, default=0, nullable=False)
    first_seen_at: Mapped[Optional[_dt.datetime]] = mapped_column(DateTime)
    last_seen_at: Mapped[Optional[_dt.datetime]] = mapped_column(DateTime)

    # 人工确认后的处置：如已提单号、已知问题、误报
    triage_note: Mapped[Optional[str]] = mapped_column(Text)
    issue_ref: Mapped[Optional[str]] = mapped_column(String(64))


class RunFailure(Base):
    """一次具体的失败实例，挂在签名下面。"""

    __tablename__ = "run_failures"
    __table_args__ = (Index("ix_failure_run", "run_id"),)

    id: Mapped[str] = pk("fail")
    run_id: Mapped[str] = mapped_column(ForeignKey("runs.id"), nullable=False)
    signature_id: Mapped[Optional[str]] = mapped_column(ForeignKey("failure_signatures.id"))
    step_id: Mapped[Optional[str]] = mapped_column(String(32))
    loop_index: Mapped[Optional[int]] = mapped_column(Integer)
    ts: Mapped[_dt.datetime] = mapped_column(DateTime, nullable=False)

    failure_class: Mapped[str] = mapped_column(String(24), default=FailureClass.UNKNOWN.value)
    summary: Mapped[str] = mapped_column(Text, nullable=False)
    detail: Mapped[Dict[str, Any]] = mapped_column(JSON, default=dict)
    # 记录固件版本，A/B 对比直接从这里聚合
    firmware_label: Mapped[Optional[str]] = mapped_column(String(64))


class EvidenceBundle(Base, TimestampMixin):
    """证据包。completeness < 1 说明有关键字段缺失 —— MVP 硬指标是 ≥95%。"""

    __tablename__ = "evidence_bundles"

    id: Mapped[str] = pk("bundle")
    run_id: Mapped[str] = mapped_column(ForeignKey("runs.id"), nullable=False)
    uri: Mapped[str] = mapped_column(String(512), nullable=False)
    manifest: Mapped[Dict[str, Any]] = mapped_column(JSON, default=dict)
    size_bytes: Mapped[int] = mapped_column(Integer, default=0)
    completeness: Mapped[float] = mapped_column(Float, default=0.0)
    missing_fields: Mapped[List[Any]] = mapped_column(JSON, default=list)


class AuditLog(Base):
    """审计：谁、什么时候、用哪个模板版本、对什么、做了什么、结果如何。

    方案 §6.6 的硬要求。与决策放在同一处 —— 见 ADR-0004。
    """

    __tablename__ = "audit_log"
    __table_args__ = (Index("ix_audit_ts", "ts"),)

    id: Mapped[str] = pk("audit")
    ts: Mapped[_dt.datetime] = mapped_column(DateTime, nullable=False)
    actor: Mapped[str] = mapped_column(String(64), nullable=False)
    action: Mapped[str] = mapped_column(String(64), nullable=False)
    target_type: Mapped[Optional[str]] = mapped_column(String(32))
    target_id: Mapped[Optional[str]] = mapped_column(String(32))
    command_template: Mapped[Optional[str]] = mapped_column(String(64))
    template_version: Mapped[Optional[str]] = mapped_column(String(16))
    params: Mapped[Dict[str, Any]] = mapped_column(JSON, default=dict)
    result: Mapped[Optional[str]] = mapped_column(String(32))
    approved_by: Mapped[Optional[str]] = mapped_column(String(64))
    reason: Mapped[Optional[str]] = mapped_column(Text)
