"""
Memory layer entities for AI Service.
[AC-AISVC-13] SQLModel entities for chat sessions and messages with tenant isolation.
"""

import uuid
from datetime import datetime
from enum import Enum
from typing import Any

from sqlalchemy import JSON, Column
from sqlmodel import Field, Index, SQLModel


class ChatSession(SQLModel, table=True):
    """
    [AC-AISVC-13] Chat session entity with tenant isolation.
    Primary key: (tenant_id, session_id) composite unique constraint.
    """

    __tablename__ = "chat_sessions"
    __table_args__ = (
        Index("ix_chat_sessions_tenant_session", "tenant_id", "session_id", unique=True),
    )

    id: uuid.UUID = Field(default_factory=uuid.uuid4, primary_key=True)
    tenant_id: str = Field(..., description="Tenant ID for multi-tenant isolation", index=True)
    session_id: str = Field(..., description="Session ID for conversation tracking")
    channel_type: str | None = Field(default=None, description="Channel type: wechat, douyin, jd")
    metadata_: dict[str, Any] | None = Field(
        default=None,
        sa_column=Column("metadata", JSON, nullable=True),
        description="Session metadata"
    )
    created_at: datetime = Field(default_factory=datetime.utcnow, description="Session creation time")
    updated_at: datetime = Field(default_factory=datetime.utcnow, description="Last update time")


class ChatMessage(SQLModel, table=True):
    """
    [AC-AISVC-13] Chat message entity with tenant isolation.
    Messages are scoped by (tenant_id, session_id) for multi-tenant security.
    [v0.7.0] Extended with monitoring fields for Dashboard statistics.
    [v0.8.0] Extended with route_trace for hybrid routing observability.
    """

    __tablename__ = "chat_messages"
    __table_args__ = (
        Index("ix_chat_messages_tenant_session", "tenant_id", "session_id"),
        Index("ix_chat_messages_tenant_session_created", "tenant_id", "session_id", "created_at"),
        Index("ix_chat_messages_tenant_template", "tenant_id", "prompt_template_id"),
        Index("ix_chat_messages_tenant_intent", "tenant_id", "intent_rule_id"),
        Index("ix_chat_messages_tenant_flow", "tenant_id", "flow_instance_id"),
    )

    id: uuid.UUID = Field(default_factory=uuid.uuid4, primary_key=True)
    tenant_id: str = Field(..., description="Tenant ID for multi-tenant isolation", index=True)
    session_id: str = Field(..., description="Session ID for conversation tracking", index=True)
    role: str = Field(..., description="Message role: user or assistant")
    content: str = Field(..., description="Message content")
    prompt_tokens: int | None = Field(default=None, description="Number of prompt tokens used")
    completion_tokens: int | None = Field(default=None, description="Number of completion tokens used")
    total_tokens: int | None = Field(default=None, description="Total tokens used")
    latency_ms: int | None = Field(default=None, description="Response latency in milliseconds")
    first_token_ms: int | None = Field(default=None, description="Time to first token in milliseconds (for streaming)")
    is_error: bool = Field(default=False, description="Whether this message is an error response")
    error_message: str | None = Field(default=None, description="Error message if any")
    created_at: datetime = Field(default_factory=datetime.utcnow, description="Message creation time")

    prompt_template_id: uuid.UUID | None = Field(
        default=None,
        description="[v0.7.0] ID of the Prompt template used",
        foreign_key="prompt_templates.id",
    )
    intent_rule_id: uuid.UUID | None = Field(
        default=None,
        description="[v0.7.0] ID of the Intent rule that matched",
        foreign_key="intent_rules.id",
    )
    flow_instance_id: uuid.UUID | None = Field(
        default=None,
        description="[v0.7.0] ID of the Flow instance if flow was active",
        foreign_key="flow_instances.id",
    )
    guardrail_triggered: bool = Field(
        default=False,
        description="[v0.7.0] Whether output guardrail was triggered"
    )
    guardrail_words: dict[str, Any] | None = Field(
        default=None,
        sa_column=Column("guardrail_words", JSON, nullable=True),
        description="[v0.7.0] Guardrail trigger details: words, categories, strategy"
    )
    route_trace: dict[str, Any] | None = Field(
        default=None,
        sa_column=Column("route_trace", JSON, nullable=True),
        description="[v0.8.0] Intent routing trace log for hybrid routing observability"
    )


class ChatSessionCreate(SQLModel):
    """Schema for creating a new chat session."""

    tenant_id: str
    session_id: str
    channel_type: str | None = None
    metadata_: dict[str, Any] | None = None


class ChatMessageCreate(SQLModel):
    """Schema for creating a new chat message."""

    tenant_id: str
    session_id: str
    role: str
    content: str


class SharedSession(SQLModel, table=True):
    """
    [AC-IDMP-SHARE] Shared session entity for dialogue sharing.
    Allows sharing chat sessions via unique token with expiration and concurrent user limits.
    """

    __tablename__ = "shared_sessions"
    __table_args__ = (
        Index("ix_shared_sessions_share_token", "share_token", unique=True),
        Index("ix_shared_sessions_tenant_session", "tenant_id", "session_id"),
        Index("ix_shared_sessions_expires_at", "expires_at"),
    )

    id: uuid.UUID = Field(default_factory=uuid.uuid4, primary_key=True)
    share_token: str = Field(..., description="Unique share token (UUID)", index=True)
    session_id: str = Field(..., description="Associated session ID", index=True)
    tenant_id: str = Field(..., description="Tenant ID for multi-tenant isolation", index=True)
    title: str | None = Field(default=None, description="Share title")
    description: str | None = Field(default=None, description="Share description")
    expires_at: datetime = Field(..., description="Expiration time")
    is_active: bool = Field(default=True, description="Whether share is active")
    max_concurrent_users: int = Field(default=10, description="Maximum concurrent users allowed")
    current_users: int = Field(default=0, description="Current number of online users")
    created_at: datetime = Field(default_factory=datetime.utcnow, description="Creation time")
    updated_at: datetime = Field(default_factory=datetime.utcnow, description="Last update time")


class DocumentStatus(str, Enum):
    PENDING = "pending"
    PROCESSING = "processing"
    COMPLETED = "completed"
    FAILED = "failed"


class IndexJobStatus(str, Enum):
    PENDING = "pending"
    PROCESSING = "processing"
    COMPLETED = "completed"
    FAILED = "failed"


class SessionStatus(str, Enum):
    ACTIVE = "active"
    CLOSED = "closed"
    EXPIRED = "expired"


class Tenant(SQLModel, table=True):
    """
    [AC-AISVC-10] Tenant entity for storing tenant information.
    Tenant ID format: name@ash@year (e.g., szmp@ash@2026)
    """

    __tablename__ = "tenants"

    id: uuid.UUID = Field(default_factory=uuid.uuid4, primary_key=True)
    tenant_id: str = Field(..., description="Full tenant ID (format: name@ash@year)", unique=True, index=True)
    name: str = Field(..., description="Tenant display name (first part of tenant_id)")
    year: str = Field(..., description="Year part from tenant_id")
    created_at: datetime = Field(default_factory=datetime.utcnow, description="Creation time")
    updated_at: datetime = Field(default_factory=datetime.utcnow, description="Last update time")


class KBType(str, Enum):
    PRODUCT = "product"
    FAQ = "faq"
    SCRIPT = "script"
    POLICY = "policy"
    GENERAL = "general"


class KnowledgeBase(SQLModel, table=True):
    """
    [AC-ASA-01, AC-AISVC-59] Knowledge base entity with tenant isolation.
    [v0.6.0] Extended with kb_type, priority, is_enabled, doc_count for multi-KB management.
    """

    __tablename__ = "knowledge_bases"
    __table_args__ = (
        Index("ix_knowledge_bases_tenant_kb_type", "tenant_id", "kb_type"),
    )

    id: uuid.UUID = Field(default_factory=uuid.uuid4, primary_key=True)
    tenant_id: str = Field(..., description="Tenant ID for multi-tenant isolation", index=True)
    name: str = Field(..., description="Knowledge base name")
    kb_type: str = Field(
        default=KBType.GENERAL.value,
        description="Knowledge base type: product/faq/script/policy/general"
    )
    description: str | None = Field(default=None, description="Knowledge base description")
    priority: int = Field(default=0, ge=0, description="Priority weight, higher value means higher priority")
    is_enabled: bool = Field(default=True, description="Whether the knowledge base is enabled")
    doc_count: int = Field(default=0, ge=0, description="Document count (cached statistic)")
    created_at: datetime = Field(default_factory=datetime.utcnow, description="Creation time")
    updated_at: datetime = Field(default_factory=datetime.utcnow, description="Last update time")


class Document(SQLModel, table=True):
    """
    [AC-ASA-01, AC-ASA-08] Document entity with tenant isolation.
    """

    __tablename__ = "documents"
    __table_args__ = (
        Index("ix_documents_tenant_kb", "tenant_id", "kb_id"),
        Index("ix_documents_tenant_status", "tenant_id", "status"),
    )

    id: uuid.UUID = Field(default_factory=uuid.uuid4, primary_key=True)
    tenant_id: str = Field(..., description="Tenant ID for multi-tenant isolation", index=True)
    kb_id: str = Field(..., description="Knowledge base ID")
    file_name: str = Field(..., description="Original file name")
    file_path: str | None = Field(default=None, description="Storage path")
    file_size: int | None = Field(default=None, description="File size in bytes")
    file_type: str | None = Field(default=None, description="File MIME type")
    status: str = Field(default=DocumentStatus.PENDING.value, description="Document status")
    error_msg: str | None = Field(default=None, description="Error message if failed")
    doc_metadata: dict | None = Field(default=None, sa_type=JSON, description="Document metadata as JSON")
    created_at: datetime = Field(default_factory=datetime.utcnow, description="Upload time")
    updated_at: datetime = Field(default_factory=datetime.utcnow, description="Last update time")


class IndexJob(SQLModel, table=True):
    """
    [AC-ASA-02] Index job entity for tracking document indexing progress.
    """

    __tablename__ = "index_jobs"
    __table_args__ = (
        Index("ix_index_jobs_tenant_doc", "tenant_id", "doc_id"),
        Index("ix_index_jobs_tenant_status", "tenant_id", "status"),
    )

    id: uuid.UUID = Field(default_factory=uuid.uuid4, primary_key=True)
    tenant_id: str = Field(..., description="Tenant ID for multi-tenant isolation", index=True)
    doc_id: uuid.UUID = Field(..., description="Document ID being indexed")
    status: str = Field(default=IndexJobStatus.PENDING.value, description="Job status")
    progress: int = Field(default=0, ge=0, le=100, description="Progress percentage")
    error_msg: str | None = Field(default=None, description="Error message if failed")
    created_at: datetime = Field(default_factory=datetime.utcnow, description="Job creation time")
    updated_at: datetime = Field(default_factory=datetime.utcnow, description="Last update time")


class KnowledgeBaseCreate(SQLModel):
    """Schema for creating a new knowledge base."""

    name: str
    kb_type: str = KBType.GENERAL.value
    description: str | None = None
    priority: int = 0


class KnowledgeBaseUpdate(SQLModel):
    """Schema for updating a knowledge base."""

    name: str | None = None
    kb_type: str | None = None
    description: str | None = None
    priority: int | None = None
    is_enabled: bool | None = None


class DocumentCreate(SQLModel):
    """Schema for creating a new document."""

    tenant_id: str
    kb_id: str
    file_name: str
    file_path: str | None = None
    file_size: int | None = None
    file_type: str | None = None


class ApiKey(SQLModel, table=True):
    """
    [AC-AISVC-50] API Key entity for lightweight authentication.
    Keys are loaded into memory on startup for fast validation.
    """

    __tablename__ = "api_keys"

    id: uuid.UUID = Field(default_factory=uuid.uuid4, primary_key=True)
    key: str = Field(..., description="API Key (unique)", unique=True, index=True)
    name: str = Field(..., description="Key name/description for identification")
    is_active: bool = Field(default=True, description="Whether the key is active")
    expires_at: datetime | None = Field(default=None, description="Expiration time; null means never expires")
    allowed_ips: list[str] | None = Field(
        default=None,
        sa_column=Column("allowed_ips", JSON, nullable=True),
        description="Optional IP allowlist for this key",
    )
    rate_limit_qpm: int | None = Field(default=60, description="Per-minute quota for this key")
    created_at: datetime = Field(default_factory=datetime.utcnow, description="Creation time")
    updated_at: datetime = Field(default_factory=datetime.utcnow, description="Last update time")


class ApiKeyCreate(SQLModel):
    """Schema for creating a new API key."""

    key: str
    name: str
    is_active: bool = True
    expires_at: datetime | None = None
    allowed_ips: list[str] | None = None
    rate_limit_qpm: int | None = 60


class TemplateVersionStatus(str, Enum):
    DRAFT = "draft"
    PUBLISHED = "published"
    ARCHIVED = "archived"


class PromptTemplate(SQLModel, table=True):
    """
    [AC-AISVC-51, AC-AISVC-52] Prompt template entity with tenant isolation.
    Main table for storing template metadata.
    [AC-IDSMETA-16] Extended with metadata field for unified storage structure.
    """

    __tablename__ = "prompt_templates"
    __table_args__ = (
        Index("ix_prompt_templates_tenant_scene", "tenant_id", "scene"),
    )

    id: uuid.UUID = Field(default_factory=uuid.uuid4, primary_key=True)
    tenant_id: str = Field(..., description="Tenant ID for multi-tenant isolation", index=True)
    name: str = Field(..., description="Template name, e.g., 'Default Customer Service Persona'")
    scene: str = Field(..., description="Scene tag: chat/rag_qa/greeting/farewell")
    description: str | None = Field(default=None, description="Template description")
    is_default: bool = Field(default=False, description="Whether this is the default template for the scene")
    metadata_: dict[str, Any] | None = Field(
        default=None,
        sa_column=Column("metadata", JSON, nullable=True),
        description="[AC-IDSMETA-16] Structured metadata for the prompt template"
    )
    created_at: datetime = Field(default_factory=datetime.utcnow, description="Creation time")
    updated_at: datetime = Field(default_factory=datetime.utcnow, description="Last update time")


class PromptTemplateVersion(SQLModel, table=True):
    """
    [AC-AISVC-53] Prompt template version entity.
    Stores versioned content with status management.
    """

    __tablename__ = "prompt_template_versions"
    __table_args__ = (
        Index("ix_template_versions_template_status", "template_id", "status"),
    )

    id: uuid.UUID = Field(default_factory=uuid.uuid4, primary_key=True)
    template_id: uuid.UUID = Field(
        ...,
        description="Foreign key to prompt_templates.id",
        foreign_key="prompt_templates.id",
        index=True
    )
    version: int = Field(..., description="Version number (auto-incremented per template)")
    status: str = Field(
        default=TemplateVersionStatus.DRAFT.value,
        description="Version status: draft/published/archived"
    )
    system_instruction: str = Field(
        ...,
        description="System instruction content with {{variable}} placeholders"
    )
    variables: list[dict[str, Any]] | None = Field(
        default=None,
        sa_column=Column("variables", JSON, nullable=True),
        description="Variable definitions, e.g., [{'name': 'persona_name', 'default': '小N'}]"
    )
    created_at: datetime = Field(default_factory=datetime.utcnow, description="Creation time")


class PromptTemplateCreate(SQLModel):
    """Schema for creating a new prompt template."""

    name: str
    scene: str
    description: str | None = None
    system_instruction: str
    variables: list[dict[str, Any]] | None = None
    is_default: bool = False
    metadata_: dict[str, Any] | None = None


class PromptTemplateUpdate(SQLModel):
    """Schema for updating a prompt template."""

    name: str | None = None
    scene: str | None = None
    description: str | None = None
    system_instruction: str | None = None
    variables: list[dict[str, Any]] | None = None
    is_default: bool | None = None
    metadata_: dict[str, Any] | None = None


class ResponseType(str, Enum):
    """[AC-AISVC-65] Response type for intent rules."""
    FIXED = "fixed"
    RAG = "rag"
    FLOW = "flow"
    TRANSFER = "transfer"


class IntentRule(SQLModel, table=True):
    """
    [AC-AISVC-65] Intent rule entity with tenant isolation.
    Supports keyword and regex matching for intent recognition.
    [AC-IDSMETA-16] Extended with metadata field for unified storage structure.
    [v0.8.0] Extended with intent_vector and semantic_examples for hybrid routing.
    """

    __tablename__ = "intent_rules"
    __table_args__ = (
        Index("ix_intent_rules_tenant_enabled_priority", "tenant_id", "is_enabled"),
    )

    id: uuid.UUID = Field(default_factory=uuid.uuid4, primary_key=True)
    tenant_id: str = Field(..., description="Tenant ID for multi-tenant isolation", index=True)
    name: str = Field(..., description="Intent name, e.g., 'Return Intent'")
    keywords: list[str] | None = Field(
        default=None,
        sa_column=Column("keywords", JSON, nullable=True),
        description="Keyword list for matching, e.g., ['return', 'refund']"
    )
    patterns: list[str] | None = Field(
        default=None,
        sa_column=Column("patterns", JSON, nullable=True),
        description="Regex pattern list for matching, e.g., ['return.*goods', 'how to return']"
    )
    priority: int = Field(default=0, description="Priority (higher value = higher priority)")
    response_type: str = Field(..., description="Response type: fixed/rag/flow/transfer")
    target_kb_ids: list[str] | None = Field(
        default=None,
        sa_column=Column("target_kb_ids", JSON, nullable=True),
        description="Target knowledge base IDs for rag type"
    )
    flow_id: uuid.UUID | None = Field(default=None, description="Flow ID for flow type")
    fixed_reply: str | None = Field(default=None, description="Fixed reply content for fixed type")
    transfer_message: str | None = Field(default=None, description="Transfer message for transfer type")
    is_enabled: bool = Field(default=True, description="Whether the rule is enabled")
    hit_count: int = Field(default=0, description="Hit count for statistics")
    metadata_: dict[str, Any] | None = Field(
        default=None,
        sa_column=Column("metadata", JSON, nullable=True),
        description="[AC-IDSMETA-16] Structured metadata for the intent rule"
    )
    intent_vector: list[float] | None = Field(
        default=None,
        sa_column=Column("intent_vector", JSON, nullable=True),
        description="[v0.8.0] Pre-computed intent vector for semantic matching"
    )
    semantic_examples: list[str] | None = Field(
        default=None,
        sa_column=Column("semantic_examples", JSON, nullable=True),
        description="[v0.8.0] Semantic example sentences for dynamic vector computation"
    )
    created_at: datetime = Field(default_factory=datetime.utcnow, description="Creation time")
    updated_at: datetime = Field(default_factory=datetime.utcnow, description="Last update time")


class IntentRuleCreate(SQLModel):
    """[AC-AISVC-65] Schema for creating a new intent rule."""

    name: str
    keywords: list[str] | None = None
    patterns: list[str] | None = None
    priority: int = 0
    response_type: str
    target_kb_ids: list[str] | None = None
    flow_id: str | None = None
    fixed_reply: str | None = None
    transfer_message: str | None = None
    metadata_: dict[str, Any] | None = None
    intent_vector: list[float] | None = None
    semantic_examples: list[str] | None = None


class IntentRuleUpdate(SQLModel):
    """[AC-AISVC-67] Schema for updating an intent rule."""

    name: str | None = None
    keywords: list[str] | None = None
    patterns: list[str] | None = None
    priority: int | None = None
    response_type: str | None = None
    target_kb_ids: list[str] | None = None
    flow_id: str | None = None
    fixed_reply: str | None = None
    transfer_message: str | None = None
    is_enabled: bool | None = None
    metadata_: dict[str, Any] | None = None
    intent_vector: list[float] | None = None
    semantic_examples: list[str] | None = None


class IntentMatchResult:
    """
    [AC-AISVC-69] Result of intent matching.
    Contains the matched rule and match details.
    """

    def __init__(
        self,
        rule: IntentRule,
        match_type: str,
        matched: str,
    ):
        self.rule = rule
        self.match_type = match_type
        self.matched = matched

    def to_dict(self) -> dict[str, Any]:
        return {
            "rule_id": str(self.rule.id),
            "rule_name": self.rule.name,
            "match_type": self.match_type,
            "matched": self.matched,
            "response_type": self.rule.response_type,
        }


class ForbiddenWordCategory(str, Enum):
    """[AC-AISVC-78] Forbidden word category."""
    COMPETITOR = "competitor"
    SENSITIVE = "sensitive"
    POLITICAL = "political"
    CUSTOM = "custom"


class ForbiddenWordStrategy(str, Enum):
    """[AC-AISVC-78] Forbidden word replacement strategy."""
    MASK = "mask"
    REPLACE = "replace"
    BLOCK = "block"


class ForbiddenWord(SQLModel, table=True):
    """
    [AC-AISVC-78] Forbidden word entity with tenant isolation.
    Supports mask/replace/block strategies for output filtering.
    """

    __tablename__ = "forbidden_words"
    __table_args__ = (
        Index("ix_forbidden_words_tenant_enabled", "tenant_id", "is_enabled"),
    )

    id: uuid.UUID = Field(default_factory=uuid.uuid4, primary_key=True)
    tenant_id: str = Field(..., description="Tenant ID for multi-tenant isolation", index=True)
    word: str = Field(..., description="Forbidden word to detect")
    category: str = Field(..., description="Category: competitor/sensitive/political/custom")
    strategy: str = Field(..., description="Replacement strategy: mask/replace/block")
    replacement: str | None = Field(default=None, description="Replacement text for 'replace' strategy")
    fallback_reply: str | None = Field(default=None, description="Fallback reply for 'block' strategy")
    is_enabled: bool = Field(default=True, description="Whether the word is enabled")
    hit_count: int = Field(default=0, ge=0, description="Hit count for statistics")
    created_at: datetime = Field(default_factory=datetime.utcnow, description="Creation time")
    updated_at: datetime = Field(default_factory=datetime.utcnow, description="Last update time")


class ForbiddenWordCreate(SQLModel):
    """[AC-AISVC-78] Schema for creating a new forbidden word."""

    word: str
    category: str
    strategy: str
    replacement: str | None = None
    fallback_reply: str | None = None


class ForbiddenWordUpdate(SQLModel):
    """[AC-AISVC-80] Schema for updating a forbidden word."""

    word: str | None = None
    category: str | None = None
    strategy: str | None = None
    replacement: str | None = None
    fallback_reply: str | None = None
    is_enabled: bool | None = None


class BehaviorRuleCategory(str, Enum):
    """[AC-AISVC-84] Behavior rule category."""
    COMPLIANCE = "compliance"
    TONE = "tone"
    BOUNDARY = "boundary"
    CUSTOM = "custom"


class BehaviorRule(SQLModel, table=True):
    """
    [AC-AISVC-84] Behavior rule entity with tenant isolation.
    These rules are injected into Prompt system instruction as LLM behavior constraints.
    """

    __tablename__ = "behavior_rules"
    __table_args__ = (
        Index("ix_behavior_rules_tenant_enabled", "tenant_id", "is_enabled"),
    )

    id: uuid.UUID = Field(default_factory=uuid.uuid4, primary_key=True)
    tenant_id: str = Field(..., description="Tenant ID for multi-tenant isolation", index=True)
    rule_text: str = Field(
        ...,
        description="Behavior constraint description, e.g., 'Do not promise specific compensation'"
    )
    category: str = Field(..., description="Category: compliance/tone/boundary/custom")
    is_enabled: bool = Field(default=True, description="Whether the rule is enabled")
    created_at: datetime = Field(default_factory=datetime.utcnow, description="Creation time")
    updated_at: datetime = Field(default_factory=datetime.utcnow, description="Last update time")


class BehaviorRuleCreate(SQLModel):
    """[AC-AISVC-84] Schema for creating a new behavior rule."""

    rule_text: str
    category: str


class BehaviorRuleUpdate(SQLModel):
    """[AC-AISVC-85] Schema for updating a behavior rule."""

    rule_text: str | None = None
    category: str | None = None
    is_enabled: bool | None = None


class GuardrailResult:
    """
    [AC-AISVC-82] Result of guardrail filtering.
    Contains filtered reply and trigger information.
    """

    def __init__(
        self,
        reply: str,
        blocked: bool = False,
        triggered_words: list[str] | None = None,
        triggered_categories: list[str] | None = None,
    ):
        self.reply = reply
        self.blocked = blocked
        self.triggered_words = triggered_words or []
        self.triggered_categories = triggered_categories or []

    def to_dict(self) -> dict[str, Any]:
        return {
            "reply": self.reply,
            "blocked": self.blocked,
            "triggered_words": self.triggered_words,
            "triggered_categories": self.triggered_categories,
            "guardrail_triggered": len(self.triggered_words) > 0,
        }


class InputScanResult:
    """
    [AC-AISVC-83] Result of input scanning.
    Contains flagged status and matched words (for logging only, no blocking).
    """

    def __init__(
        self,
        flagged: bool = False,
        matched_words: list[str] | None = None,
        matched_categories: list[str] | None = None,
    ):
        self.flagged = flagged
        self.matched_words = matched_words or []
        self.matched_categories = matched_categories or []

    def to_dict(self) -> dict[str, Any]:
        return {
            "input_flagged": self.flagged,
            "matched_words": self.matched_words,
            "matched_categories": self.matched_categories,
        }


class FlowInstanceStatus(str, Enum):
    """[AC-AISVC-74~AC-AISVC-77] Flow instance status."""
    ACTIVE = "active"
    COMPLETED = "completed"
    TIMEOUT = "timeout"
    CANCELLED = "cancelled"


class TimeoutAction(str, Enum):
    """[AC-AISVC-71] Timeout action for flow steps."""
    REPEAT = "repeat"
    SKIP = "skip"
    TRANSFER = "transfer"


class ScriptFlow(SQLModel, table=True):
    """
    [AC-AISVC-71] Script flow entity with tenant isolation.
    Stores flow definition with steps in JSONB format.
    [AC-IDSMETA-16] Extended with metadata field for unified storage structure.
    """

    __tablename__ = "script_flows"
    __table_args__ = (
        Index("ix_script_flows_tenant_enabled", "tenant_id", "is_enabled"),
    )

    id: uuid.UUID = Field(default_factory=uuid.uuid4, primary_key=True)
    tenant_id: str = Field(..., description="Tenant ID for multi-tenant isolation", index=True)
    name: str = Field(..., description="Flow name")
    description: str | None = Field(default=None, description="Flow description")
    steps: list[dict[str, Any]] = Field(
        default=[],
        sa_column=Column("steps", JSON, nullable=False),
        description="Flow steps list with step_no, content, wait_input, timeout_seconds"
    )
    is_enabled: bool = Field(default=True, description="Whether the flow is enabled")
    metadata_: dict[str, Any] | None = Field(
        default=None,
        sa_column=Column("metadata", JSON, nullable=True),
        description="[AC-IDSMETA-16] Structured metadata for the script flow"
    )
    created_at: datetime = Field(default_factory=datetime.utcnow, description="Creation time")
    updated_at: datetime = Field(default_factory=datetime.utcnow, description="Last update time")


class FlowInstance(SQLModel, table=True):
    """
    [AC-AISVC-74] Flow instance entity for runtime state.
    Tracks active flow execution per session.
    """

    __tablename__ = "flow_instances"
    __table_args__ = (
        Index("ix_flow_instances_tenant_session", "tenant_id", "session_id"),
        Index("ix_flow_instances_tenant_status", "tenant_id", "status"),
    )

    id: uuid.UUID = Field(default_factory=uuid.uuid4, primary_key=True)
    tenant_id: str = Field(..., description="Tenant ID for multi-tenant isolation", index=True)
    session_id: str = Field(..., description="Session ID for conversation tracking", index=True)
    flow_id: uuid.UUID = Field(
        ...,
        description="Foreign key to script_flows.id",
        foreign_key="script_flows.id",
        index=True
    )
    current_step: int = Field(default=1, ge=1, description="Current step number (1-indexed)")
    status: str = Field(
        default=FlowInstanceStatus.ACTIVE.value,
        description="Instance status: active/completed/timeout/cancelled"
    )
    context: dict[str, Any] | None = Field(
        default=None,
        sa_column=Column("context", JSON, nullable=True),
        description="Flow execution context, stores user inputs"
    )
    started_at: datetime = Field(default_factory=datetime.utcnow, description="Instance start time")
    updated_at: datetime = Field(default_factory=datetime.utcnow, description="Last update time")
    completed_at: datetime | None = Field(default=None, description="Completion time (nullable)")


class ScriptMode(str, Enum):
    """[AC-IDS-01] Script generation mode for flow steps."""
    FIXED = "fixed"
    FLEXIBLE = "flexible"
    TEMPLATE = "template"


class FlowStep(SQLModel):
    """
    [AC-AISVC-71] Schema for a single flow step.
    [AC-IDS-01] Extended with intent-driven script generation fields.
    """

    step_no: int = Field(..., ge=1, description="Step number (1-indexed)")
    content: str = Field(..., description="Script content for this step")
    wait_input: bool = Field(default=True, description="Whether to wait for user input")
    timeout_seconds: int = Field(default=120, ge=1, description="Timeout in seconds")
    timeout_action: str = Field(
        default=TimeoutAction.REPEAT.value,
        description="Action on timeout: repeat/skip/transfer"
    )
    next_conditions: list[dict[str, Any]] | None = Field(
        default=None,
        description="Conditions for next step: [{'keywords': [...], 'goto_step': N}]"
    )
    default_next: int | None = Field(default=None, description="Default next step if no condition matches")

    script_mode: str = Field(
        default=ScriptMode.FIXED.value,
        description="[AC-IDS-01] Script mode: fixed/flexible/template"
    )
    intent: str | None = Field(
        default=None,
        description="[AC-IDS-01] Step intent for flexible mode (e.g., '获取用户姓名')"
    )
    intent_description: str | None = Field(
        default=None,
        description="[AC-IDS-01] Detailed intent description for better AI understanding"
    )
    script_constraints: list[str] | None = Field(
        default=None,
        description="[AC-IDS-01] Script constraints for flexible mode (e.g., ['必须礼貌', '语气自然'])"
    )
    expected_variables: list[str] | None = Field(
        default=None,
        description="[AC-IDS-01] Expected variables to extract from user input"
    )
    rag_config: dict[str, Any] | None = Field(
        default=None,
        description="RAG configuration for this step: {'enabled': true, 'tag_filter': {'grade': '${context.grade}', 'type': '痛点'}}"
    )
    allowed_kb_ids: list[str] | None = Field(
        default=None,
        description="[Step-KB-Binding] Allowed knowledge base IDs for this step. If set, KB search will be restricted to these KBs."
    )
    preferred_kb_ids: list[str] | None = Field(
        default=None,
        description="[Step-KB-Binding] Preferred knowledge base IDs for this step. These KBs will be searched first."
    )
    kb_query_hint: str | None = Field(
        default=None,
        description="[Step-KB-Binding] Query hint for KB search in this step, helps improve retrieval accuracy."
    )
    max_kb_calls_per_step: int | None = Field(
        default=None,
        ge=1,
        le=5,
        description="[Step-KB-Binding] Max KB calls allowed per step. Default is 1 if not set."
    )


class ScriptFlowCreate(SQLModel):
    """[AC-AISVC-71] Schema for creating a new script flow."""

    name: str
    description: str | None = None
    steps: list[dict[str, Any]]
    is_enabled: bool = True
    metadata_: dict[str, Any] | None = None


class ScriptFlowUpdate(SQLModel):
    """[AC-AISVC-73] Schema for updating a script flow."""

    name: str | None = None
    description: str | None = None
    steps: list[dict[str, Any]] | None = None
    is_enabled: bool | None = None
    metadata_: dict[str, Any] | None = None


class FlowAdvanceResult:
    """
    [AC-AISVC-75] Result of flow step advancement.
    """

    def __init__(
        self,
        completed: bool,
        message: str | None = None,
        current_step: int | None = None,
        total_steps: int | None = None,
        timeout_action: str | None = None,
    ):
        self.completed = completed
        self.message = message
        self.current_step = current_step
        self.total_steps = total_steps
        self.timeout_action = timeout_action

    def to_dict(self) -> dict[str, Any]:
        result = {
            "completed": self.completed,
        }
        if self.message is not None:
            result["message"] = self.message
        if self.current_step is not None:
            result["current_step"] = self.current_step
        if self.total_steps is not None:
            result["total_steps"] = self.total_steps
        if self.timeout_action is not None:
            result["timeout_action"] = self.timeout_action
        return result


class FlowTestRecordStatus(str, Enum):
    """[AC-AISVC-93] Flow test record status."""
    SUCCESS = "success"
    PARTIAL = "partial"
    FAILED = "failed"


class FlowTestRecord(SQLModel, table=True):
    """
    [AC-AISVC-93] Flow test record entity for complete 12-step execution logging.
    Records are retained for 7 days (TTL cleanup via background task).
    """

    __tablename__ = "flow_test_records"
    __table_args__ = (
        Index("ix_flow_test_records_tenant_created", "tenant_id", "created_at"),
        Index("ix_flow_test_records_session", "session_id"),
    )

    id: uuid.UUID = Field(default_factory=uuid.uuid4, primary_key=True)
    tenant_id: str = Field(..., description="Tenant ID for multi-tenant isolation", index=True)
    session_id: str = Field(..., description="Session ID for test session")
    status: str = Field(
        default=FlowTestRecordStatus.SUCCESS.value,
        description="Overall status: success/partial/failed"
    )
    steps: list[dict[str, Any]] = Field(
        default=[],
        sa_column=Column("steps", JSON, nullable=False),
        description="12-step execution logs with step, name, status, duration_ms, input, output, error, metadata"
    )
    final_response: dict[str, Any] | None = Field(
        default=None,
        sa_column=Column("final_response", JSON, nullable=True),
        description="Final ChatResponse with reply, confidence, should_transfer"
    )
    total_duration_ms: int | None = Field(default=None, description="Total execution time in milliseconds")
    created_at: datetime = Field(default_factory=datetime.utcnow, description="Record creation time", index=True)


class FlowTestStepResult(SQLModel):
    """[AC-AISVC-93] Schema for a single step result in flow test."""

    step: int = Field(..., description="Step number (1-12)")
    name: str = Field(..., description="Step name")
    status: str = Field(..., description="Execution status: success/failed/skipped")
    duration_ms: int | None = Field(default=None, description="Execution time in milliseconds")
    input: dict[str, Any] | None = Field(default=None, description="Step input data")
    output: dict[str, Any] | None = Field(default=None, description="Step output data")
    error: str | None = Field(default=None, description="Error message if failed")
    step_metadata: dict[str, Any] | None = Field(default=None, description="Step metadata (matched rule, template, etc.)")


class ExportTaskStatus(str, Enum):
    """[AC-AISVC-110] Export task status."""
    PROCESSING = "processing"
    COMPLETED = "completed"
    FAILED = "failed"


class ExportTask(SQLModel, table=True):
    """
    [AC-AISVC-110] Export task entity for conversation export.
    Supports async export with file download.
    """

    __tablename__ = "export_tasks"
    __table_args__ = (
        Index("ix_export_tasks_tenant_status", "tenant_id", "status"),
        Index("ix_export_tasks_tenant_created", "tenant_id", "created_at"),
    )

    id: uuid.UUID = Field(default_factory=uuid.uuid4, primary_key=True)
    tenant_id: str = Field(..., description="Tenant ID for multi-tenant isolation", index=True)
    status: str = Field(
        default=ExportTaskStatus.PROCESSING.value,
        description="Task status: processing/completed/failed"
    )
    file_path: str | None = Field(default=None, description="Path to exported file")
    file_name: str | None = Field(default=None, description="Generated file name")
    file_size: int | None = Field(default=None, description="File size in bytes")
    total_rows: int | None = Field(default=None, description="Total rows exported")
    format: str = Field(default="json", description="Export format: json/csv")
    filters: dict[str, Any] | None = Field(
        default=None,
        sa_column=Column("filters", JSON, nullable=True),
        description="Export filters applied"
    )
    error_message: str | None = Field(default=None, description="Error message if failed")
    expires_at: datetime | None = Field(default=None, description="File expiration time (for cleanup)")
    created_at: datetime = Field(default_factory=datetime.utcnow, description="Task creation time")
    completed_at: datetime | None = Field(default=None, description="Completion time")


class ExportTaskCreate(SQLModel):
    """[AC-AISVC-110] Schema for creating an export task."""

    format: str = "json"
    filters: dict[str, Any] | None = None


class ConversationDetail(SQLModel):
    """[AC-AISVC-109] Schema for conversation detail with execution chain."""

    conversation_id: uuid.UUID
    session_id: str
    user_message: str
    ai_reply: str | None = None
    triggered_rules: list[dict[str, Any]] | None = None
    used_template: dict[str, Any] | None = None
    used_flow: dict[str, Any] | None = None
    execution_time_ms: int | None = None
    confidence: float | None = None
    should_transfer: bool = False
    execution_steps: list[dict[str, Any]] | None = None
    created_at: datetime


class MetadataFieldType(str, Enum):
    """元数据字段类型"""
    STRING = "string"
    NUMBER = "number"
    BOOLEAN = "boolean"
    ENUM = "enum"
    ARRAY_ENUM = "array_enum"


class FieldRole(str, Enum):
    """
    [AC-MRS-01] 字段角色枚举
    用于标识元数据字段的职责分层
    """
    RESOURCE_FILTER = "resource_filter"
    SLOT = "slot"
    PROMPT_VAR = "prompt_var"
    ROUTING_SIGNAL = "routing_signal"


class MetadataFieldStatus(str, Enum):
    """[AC-IDSMETA-13] 元数据字段状态"""
    DRAFT = "draft"
    ACTIVE = "active"
    DEPRECATED = "deprecated"


class MetadataScope(str, Enum):
    """[AC-IDSMETA-15] 元数据字段适用范围"""
    KB_DOCUMENT = "kb_document"
    INTENT_RULE = "intent_rule"
    SCRIPT_FLOW = "script_flow"
    PROMPT_TEMPLATE = "prompt_template"


class MetadataField(SQLModel):
    """元数据字段定义（非持久化，用于嵌套结构）"""
    name: str = Field(..., description="字段名称，如 grade, subject, industry")
    label: str = Field(..., description="字段显示名称，如 年级, 学科, 行业")
    field_type: str = Field(
        default=MetadataFieldType.STRING.value,
        description="字段类型: string/number/boolean/enum/array_enum"
    )
    options: list[str] | None = Field(
        default=None,
        description="选项列表，用于 enum/array_enum 类型，如 ['初一', '初二', '初三']"
    )
    required: bool = Field(default=False, description="是否必填")
    default_value: str | None = Field(default=None, description="默认值")
    description: str | None = Field(default=None, description="字段描述")
    sort_order: int = Field(default=0, description="排序顺序")


class MetadataFieldDefinition(SQLModel, table=True):
    """
    [AC-IDSMETA-13] 元数据字段定义表
    [AC-MRS-01,02,03] 支持字段角色分层配置
    每个字段独立存储，支持字段级状态管理（draft/active/deprecated）
    """

    __tablename__ = "metadata_field_definitions"
    __table_args__ = (
        Index("ix_metadata_field_definitions_tenant", "tenant_id"),
        Index("ix_metadata_field_definitions_tenant_status", "tenant_id", "status"),
        Index("ix_metadata_field_definitions_tenant_field_key", "tenant_id", "field_key", unique=True),
    )

    id: uuid.UUID = Field(default_factory=uuid.uuid4, primary_key=True)
    tenant_id: str = Field(..., description="Tenant ID for multi-tenant isolation", index=True)
    field_key: str = Field(
        ...,
        description="字段键名，仅允许小写字母数字下划线，如 grade, subject, industry",
        min_length=1,
        max_length=64,
    )
    label: str = Field(..., description="字段显示名称", min_length=1, max_length=64)
    type: str = Field(
        default=MetadataFieldType.STRING.value,
        description="字段类型: string/number/boolean/enum/array_enum"
    )
    required: bool = Field(default=False, description="是否必填")
    options: list[str] | None = Field(
        default=None,
        sa_column=Column("options", JSON, nullable=True),
        description="选项列表，用于 enum/array_enum 类型"
    )
    default_value: str | None = Field(default=None, description="默认值", sa_column=Column("default_value", JSON, nullable=True))
    scope: list[str] = Field(
        default_factory=lambda: [MetadataScope.KB_DOCUMENT.value],
        sa_column=Column("scope", JSON, nullable=False),
        description="适用范围: kb_document/intent_rule/script_flow/prompt_template"
    )
    is_filterable: bool = Field(default=True, description="是否可用于过滤")
    is_rank_feature: bool = Field(default=False, description="是否用于排序特征")
    usage_description: str | None = Field(default=None, description="用途说明")
    field_roles: list[str] = Field(
        default_factory=list,
        sa_column=Column("field_roles", JSON, nullable=False, server_default="'[]'"),
        description="[AC-MRS-01] 字段角色列表: resource_filter/slot/prompt_var/routing_signal"
    )
    status: str = Field(
        default=MetadataFieldStatus.DRAFT.value,
        description="字段状态: draft/active/deprecated"
    )
    version: int = Field(default=1, description="版本号")
    created_at: datetime = Field(default_factory=datetime.utcnow, description="创建时间")
    updated_at: datetime = Field(default_factory=datetime.utcnow, description="更新时间")


class MetadataFieldDefinitionCreate(SQLModel):
    """[AC-IDSMETA-13] [AC-MRS-01] 创建元数据字段定义"""

    field_key: str = Field(..., min_length=1, max_length=64)
    label: str = Field(..., min_length=1, max_length=64)
    type: str = Field(default=MetadataFieldType.STRING.value)
    required: bool = Field(default=False)
    options: list[str] | None = None
    default_value: str | int | float | bool | None = None
    scope: list[str] = Field(default_factory=lambda: [MetadataScope.KB_DOCUMENT.value])
    is_filterable: bool = Field(default=True)
    is_rank_feature: bool = Field(default=False)
    usage_description: str | None = None
    field_roles: list[str] = Field(default_factory=list)
    status: str = Field(default=MetadataFieldStatus.DRAFT.value)


class MetadataFieldDefinitionUpdate(SQLModel):
    """[AC-IDSMETA-14] [AC-MRS-01] 更新元数据字段定义"""

    label: str | None = Field(default=None, min_length=1, max_length=64)
    required: bool | None = None
    options: list[str] | None = None
    default_value: str | int | float | bool | None = None
    scope: list[str] | None = None
    is_filterable: bool | None = None
    is_rank_feature: bool | None = None
    usage_description: str | None = None
    field_roles: list[str] | None = None
    status: str | None = None


class ExtractStrategy(str, Enum):
    """
    [AC-MRS-07] 槽位值提取策略
    """
    RULE = "rule"
    LLM = "llm"
    USER_INPUT = "user_input"


class ExtractFailureType(str, Enum):
    """
    [AC-MRS-07-UPGRADE] 提取失败类型
    统一失败分类，用于追踪和日志
    """
    EXTRACT_EMPTY = "EXTRACT_EMPTY"              # 提取结果为空
    EXTRACT_PARSE_FAIL = "EXTRACT_PARSE_FAIL"    # 解析失败
    EXTRACT_VALIDATION_FAIL = "EXTRACT_VALIDATION_FAIL"  # 校验失败
    EXTRACT_RUNTIME_ERROR = "EXTRACT_RUNTIME_ERROR"      # 运行时错误


class SlotValueSource(str, Enum):
    """
    [AC-MRS-09] 槽位值来源
    """
    USER_CONFIRMED = "user_confirmed"
    RULE_EXTRACTED = "rule_extracted"
    LLM_INFERRED = "llm_inferred"
    DEFAULT = "default"


class SlotDefinition(SQLModel, table=True):
    """
    [AC-MRS-07,08] 槽位定义表
    独立的槽位定义模型，与元数据字段解耦但可复用
    [AC-MRS-07-UPGRADE] 支持提取策略链 extract_strategies
    """

    __tablename__ = "slot_definitions"
    __table_args__ = (
        Index("ix_slot_definitions_tenant", "tenant_id"),
        Index("ix_slot_definitions_tenant_key", "tenant_id", "slot_key", unique=True),
        Index("ix_slot_definitions_linked_field", "linked_field_id"),
    )

    id: uuid.UUID = Field(default_factory=uuid.uuid4, primary_key=True)
    tenant_id: str = Field(..., description="Tenant ID for multi-tenant isolation", index=True)
    slot_key: str = Field(
        ...,
        description="槽位键名，可与元数据字段 field_key 关联",
        min_length=1,
        max_length=100,
    )
    display_name: str | None = Field(
        default=None,
        description="槽位名称，给运营/教研看的中文名，例：grade -> '当前年级'",
        max_length=100,
    )
    description: str | None = Field(
        default=None,
        description="槽位说明，解释这个槽位采集什么、用于哪里",
        max_length=500,
    )
    type: str = Field(
        default=MetadataFieldType.STRING.value,
        description="槽位类型: string/number/boolean/enum/array_enum"
    )
    required: bool = Field(default=False, description="是否必填槽位")
    # [AC-MRS-07-UPGRADE] 保留旧字段用于兼容读取
    extract_strategy: str | None = Field(
        default=None,
        description="[兼容字段] 提取策略: rule/llm/user_input，已废弃，请使用 extract_strategies"
    )
    # [AC-MRS-07-UPGRADE] 新增策略链字段
    extract_strategies: list[str] | None = Field(
        default=None,
        sa_column=Column("extract_strategies", JSON, nullable=True),
        description="[AC-MRS-07-UPGRADE] 提取策略链：有序数组，元素为 rule/llm/user_input，按顺序执行直到成功"
    )
    validation_rule: str | None = Field(
        default=None,
        description="校验规则（正则或 JSON Schema）"
    )
    ask_back_prompt: str | None = Field(
        default=None,
        description="追问提示语模板"
    )
    default_value: dict[str, Any] | None = Field(
        default=None,
        sa_column=Column("default_value", JSON, nullable=True),
        description="默认值"
    )
    linked_field_id: uuid.UUID | None = Field(
        default=None,
        description="关联的元数据字段 ID",
        foreign_key="metadata_field_definitions.id",
    )
    created_at: datetime = Field(default_factory=datetime.utcnow, description="创建时间")
    updated_at: datetime = Field(default_factory=datetime.utcnow, description="更新时间")

    def get_effective_strategies(self) -> list[str]:
        """
        [AC-MRS-07-UPGRADE] 获取有效的提取策略链
        优先使用 extract_strategies，如果不存在则兼容读取 extract_strategy
        """
        if self.extract_strategies and len(self.extract_strategies) > 0:
            return self.extract_strategies
        if self.extract_strategy:
            return [self.extract_strategy]
        return []

    def validate_strategies(self) -> tuple[bool, str]:
        """
        [AC-MRS-07-UPGRADE] 校验提取策略链的有效性

        Returns:
            Tuple of (是否有效, 错误信息)
        """
        valid_strategies = {"rule", "llm", "user_input"}
        strategies = self.get_effective_strategies()

        if not strategies:
            return True, ""  # 空策略链视为有效（使用默认行为）

        # 校验至少1个策略
        if len(strategies) == 0:
            return False, "提取策略链不能为空"

        # 校验不允许重复策略
        if len(strategies) != len(set(strategies)):
            return False, "提取策略链中不允许重复的策略"

        # 校验策略值有效
        invalid = [s for s in strategies if s not in valid_strategies]
        if invalid:
            return False, f"无效的提取策略: {invalid}，有效值为: {list(valid_strategies)}"

        return True, ""


class SlotDefinitionCreate(SQLModel):
    """[AC-MRS-07,08] 创建槽位定义"""

    slot_key: str = Field(..., min_length=1, max_length=100)
    display_name: str | None = Field(
        default=None,
        description="槽位名称，给运营/教研看的中文名",
        max_length=100,
    )
    description: str | None = Field(
        default=None,
        description="槽位说明，解释这个槽位采集什么、用于哪里",
        max_length=500,
    )
    type: str = Field(default=MetadataFieldType.STRING.value)
    required: bool = Field(default=False)
    # [AC-MRS-07-UPGRADE] 支持策略链
    extract_strategies: list[str] | None = Field(
        default=None,
        description="提取策略链：有序数组，元素为 rule/llm/user_input，按顺序执行直到成功"
    )
    # [AC-MRS-07-UPGRADE] 保留旧字段用于兼容
    extract_strategy: str | None = Field(
        default=None,
        description="[兼容字段] 单提取策略，已废弃，请使用 extract_strategies"
    )
    validation_rule: str | None = None
    ask_back_prompt: str | None = None
    default_value: dict[str, Any] | None = None
    linked_field_id: str | None = None


class SlotDefinitionUpdate(SQLModel):
    """[AC-MRS-07] 更新槽位定义"""

    display_name: str | None = Field(
        default=None,
        description="槽位名称，给运营/教研看的中文名",
        max_length=100,
    )
    description: str | None = Field(
        default=None,
        description="槽位说明，解释这个槽位采集什么、用于哪里",
        max_length=500,
    )
    type: str | None = None
    required: bool | None = None
    # [AC-MRS-07-UPGRADE] 支持策略链
    extract_strategies: list[str] | None = Field(
        default=None,
        description="提取策略链：有序数组，元素为 rule/llm/user_input，按顺序执行直到成功"
    )
    # [AC-MRS-07-UPGRADE] 保留旧字段用于兼容
    extract_strategy: str | None = Field(
        default=None,
        description="[兼容字段] 单提取策略，已废弃，请使用 extract_strategies"
    )
    validation_rule: str | None = None
    ask_back_prompt: str | None = None
    default_value: dict[str, Any] | None = None
    linked_field_id: str | None = None


class SlotValue(SQLModel):
    """
    [AC-MRS-09] 运行时槽位值
    """

    key: str = Field(..., description="槽位键名")
    value: Any = Field(..., description="槽位值")
    source: str = Field(
        default=SlotValueSource.DEFAULT.value,
        description="来源: user_confirmed/rule_extracted/llm_inferred/default"
    )
    confidence: float = Field(
        default=1.0,
        ge=0.0,
        le=1.0,
        description="置信度 0.0~1.0"
    )
    updated_at: datetime = Field(
        default_factory=datetime.utcnow,
        description="最后更新时间"
    )


class MetadataSchema(SQLModel, table=True):
    """
    元数据模式定义（保留兼容性）
    每个租户可以定义自己的元数据字段配置
    """

    __tablename__ = "metadata_schemas"
    __table_args__ = (
        Index("ix_metadata_schemas_tenant", "tenant_id"),
    )

    id: uuid.UUID = Field(default_factory=uuid.uuid4, primary_key=True)
    tenant_id: str = Field(..., description="Tenant ID for multi-tenant isolation", index=True)
    name: str = Field(..., description="模式名称，如 教育行业元数据")
    description: str | None = Field(default=None, description="模式描述")
    fields: list[dict[str, Any]] = Field(
        default=[],
        sa_column=Column("fields", JSON, nullable=False),
        description="字段定义列表"
    )
    is_default: bool = Field(default=False, description="是否为租户默认模式")
    is_enabled: bool = Field(default=True, description="是否启用")
    created_at: datetime = Field(default_factory=datetime.utcnow, description="创建时间")
    updated_at: datetime = Field(default_factory=datetime.utcnow, description="更新时间")


class MetadataSchemaCreate(SQLModel):
    """创建元数据模式"""

    name: str
    description: str | None = None
    fields: list[dict[str, Any]]
    is_default: bool = False


class MetadataSchemaUpdate(SQLModel):
    """更新元数据模式"""

    name: str | None = None
    description: str | None = None
    fields: list[dict[str, Any]] | None = None
    is_default: bool | None = None
    is_enabled: bool | None = None


class MetadataFieldCreate(SQLModel):
    """创建元数据字段"""

    name: str
    label: str
    field_type: str = "string"
    options: list[str] | None = None
    required: bool = False
    default_value: str | None = None
    description: str | None = None
    sort_order: int = 0


class DecompositionTemplateStatus(str, Enum):
    """[AC-IDSMETA-22] 拆解模板状态"""
    DRAFT = "draft"
    PUBLISHED = "published"
    ARCHIVED = "archived"


class DecompositionTemplate(SQLModel, table=True):
    """
    [AC-IDSMETA-22] 拆解模板表
    用于将待录入文本按固定模板拆解为结构化数据
    """

    __tablename__ = "decomposition_templates"
    __table_args__ = (
        Index("ix_decomposition_templates_tenant", "tenant_id"),
        Index("ix_decomposition_templates_tenant_status", "tenant_id", "status"),
    )

    id: uuid.UUID = Field(default_factory=uuid.uuid4, primary_key=True)
    tenant_id: str = Field(..., description="Tenant ID for multi-tenant isolation", index=True)
    name: str = Field(..., description="模板名称")
    description: str | None = Field(default=None, description="模板描述")
    version: int = Field(default=1, description="版本号")
    status: str = Field(
        default=DecompositionTemplateStatus.DRAFT.value,
        description="模板状态: draft/published/archived"
    )
    template_schema: dict[str, Any] = Field(
        default_factory=dict,
        sa_column=Column("template_schema", JSON, nullable=False),
        description="输出模板结构定义，包含字段名、类型、描述等"
    )
    extraction_hints: dict[str, Any] | None = Field(
        default=None,
        sa_column=Column("extraction_hints", JSON, nullable=True),
        description="提取提示，用于指导 LLM 提取特定字段"
    )
    example_input: str | None = Field(default=None, description="示例输入文本")
    example_output: dict[str, Any] | None = Field(
        default=None,
        sa_column=Column("example_output", JSON, nullable=True),
        description="示例输出 JSON"
    )
    created_at: datetime = Field(default_factory=datetime.utcnow, description="创建时间")
    updated_at: datetime = Field(default_factory=datetime.utcnow, description="更新时间")


class DecompositionTemplateCreate(SQLModel):
    """[AC-IDSMETA-22] 创建拆解模板"""

    name: str
    description: str | None = None
    template_schema: dict[str, Any]
    extraction_hints: dict[str, Any] | None = None
    example_input: str | None = None
    example_output: dict[str, Any] | None = None


class DecompositionTemplateUpdate(SQLModel):
    """[AC-IDSMETA-22] 更新拆解模板"""

    name: str | None = None
    description: str | None = None
    template_schema: dict[str, Any] | None = None
    extraction_hints: dict[str, Any] | None = None
    example_input: str | None = None
    example_output: dict[str, Any] | None = None
    status: str | None = None


class DecompositionRequest(SQLModel):
    """[AC-IDSMETA-21] 拆解请求"""

    text: str = Field(..., description="待拆解的文本")
    template_id: str | None = Field(default=None, description="指定模板 ID（可选）")
    hints: dict[str, Any] | None = Field(default=None, description="额外提取提示")


class DecompositionResult(SQLModel):
    """[AC-IDSMETA-21] 拆解结果"""

    success: bool = Field(..., description="是否成功")
    data: dict[str, Any] | None = Field(default=None, description="拆解后的结构化数据")
    template_id: str | None = Field(default=None, description="使用的模板 ID")
    template_version: int | None = Field(default=None, description="使用的模板版本")
    confidence: float | None = Field(default=None, description="拆解置信度")
    error: str | None = Field(default=None, description="错误信息")
    latency_ms: int | None = Field(default=None, description="处理耗时（毫秒）")


class HighRiskScenarioType(str, Enum):
    """[AC-IDMP-20] 高风险场景类型"""
    REFUND = "refund"
    COMPLAINT_ESCALATION = "complaint_escalation"
    PRIVACY_SENSITIVE_PROMISE = "privacy_sensitive_promise"
    TRANSFER = "transfer"


class HighRiskPolicy(SQLModel, table=True):
    """
    [AC-IDMP-20] 高风险场景策略配置
    定义高风险场景的最小集，支持动态配置
    """

    __tablename__ = "high_risk_policies"
    __table_args__ = (
        Index("ix_high_risk_policies_tenant", "tenant_id"),
        Index("ix_high_risk_policies_tenant_enabled", "tenant_id", "is_enabled"),
    )

    id: uuid.UUID = Field(default_factory=uuid.uuid4, primary_key=True)
    tenant_id: str = Field(..., description="Tenant ID for multi-tenant isolation", index=True)
    scenario: str = Field(..., description="场景类型: refund/complaint_escalation/privacy_sensitive_promise/transfer")
    handler_mode: str = Field(
        default="micro_flow",
        description="处理模式: micro_flow/transfer"
    )
    flow_id: uuid.UUID | None = Field(
        default=None,
        description="微流程ID (handler_mode=micro_flow时使用)",
        foreign_key="script_flows.id"
    )
    transfer_message: str | None = Field(
        default=None,
        description="转人工消息 (handler_mode=transfer时使用)"
    )
    keywords: list[str] | None = Field(
        default=None,
        sa_column=Column("keywords", JSON, nullable=True),
        description="触发关键词列表"
    )
    patterns: list[str] | None = Field(
        default=None,
        sa_column=Column("patterns", JSON, nullable=True),
        description="触发正则模式列表"
    )
    priority: int = Field(default=0, description="优先级 (值越高优先级越高)")
    is_enabled: bool = Field(default=True, description="是否启用")
    created_at: datetime = Field(default_factory=datetime.utcnow, description="创建时间")
    updated_at: datetime = Field(default_factory=datetime.utcnow, description="更新时间")


class HighRiskPolicyCreate(SQLModel):
    """[AC-IDMP-20] 创建高风险策略"""

    scenario: str
    handler_mode: str = "micro_flow"
    flow_id: str | None = None
    transfer_message: str | None = None
    keywords: list[str] | None = None
    patterns: list[str] | None = None
    priority: int = 0


class HighRiskPolicyUpdate(SQLModel):
    """[AC-IDMP-20] 更新高风险策略"""

    handler_mode: str | None = None
    flow_id: str | None = None
    transfer_message: str | None = None
    keywords: list[str] | None = None
    patterns: list[str] | None = None
    priority: int | None = None
    is_enabled: bool | None = None


class SessionModeRecord(SQLModel, table=True):
    """
    [AC-IDMP-09] 会话模式记录
    记录会话的当前模式状态
    """

    __tablename__ = "session_mode_records"
    __table_args__ = (
        Index("ix_session_mode_records_tenant_session", "tenant_id", "session_id", unique=True),
    )

    id: uuid.UUID = Field(default_factory=uuid.uuid4, primary_key=True)
    tenant_id: str = Field(..., description="Tenant ID for multi-tenant isolation", index=True)
    session_id: str = Field(..., description="会话ID", index=True)
    mode: str = Field(
        default="BOT_ACTIVE",
        description="会话模式: BOT_ACTIVE/HUMAN_ACTIVE"
    )
    reason: str | None = Field(default=None, description="模式切换原因")
    switched_at: datetime | None = Field(default=None, description="模式切换时间")
    created_at: datetime = Field(default_factory=datetime.utcnow, description="创建时间")
    updated_at: datetime = Field(default_factory=datetime.utcnow, description="更新时间")


class MidAuditLog(SQLModel, table=True):
    """
    [AC-IDMP-07] 中台审计日志
    记录 generation/request 维度的审计字段
    """

    __tablename__ = "mid_audit_logs"
    __table_args__ = (
        Index("ix_mid_audit_logs_tenant_session", "tenant_id", "session_id"),
        Index("ix_mid_audit_logs_tenant_request", "tenant_id", "request_id"),
        Index("ix_mid_audit_logs_tenant_generation", "tenant_id", "generation_id"),
        Index("ix_mid_audit_logs_created", "created_at"),
    )

    id: uuid.UUID = Field(default_factory=uuid.uuid4, primary_key=True)
    tenant_id: str = Field(..., description="Tenant ID for multi-tenant isolation", index=True)
    session_id: str = Field(..., description="会话ID", index=True)
    request_id: str = Field(..., description="请求ID", index=True)
    generation_id: str = Field(..., description="生成ID", index=True)
    mode: str = Field(..., description="执行模式: agent/micro_flow/fixed/transfer")
    intent: str | None = Field(default=None, description="意图")
    tool_calls: list[dict[str, Any]] | None = Field(
        default=None,
        sa_column=Column("tool_calls", JSON, nullable=True),
        description="工具调用记录"
    )
    guardrail_triggered: bool = Field(default=False, description="护栏是否触发")
    fallback_reason_code: str | None = Field(default=None, description="降级原因码")
    react_iterations: int | None = Field(default=None, description="ReAct循环次数")
    high_risk_scenario: str | None = Field(default=None, description="触发的高风险场景")
    latency_ms: int | None = Field(default=None, description="总耗时(ms)")
    created_at: datetime = Field(default_factory=datetime.utcnow, description="创建时间", index=True)


class SceneSlotBundleStatus(str, Enum):
    """[AC-SCENE-SLOT-01] 场景槽位包状态"""
    DRAFT = "draft"
    ACTIVE = "active"
    DEPRECATED = "deprecated"


class SceneSlotBundle(SQLModel, table=True):
    """
    [AC-SCENE-SLOT-01] 场景-槽位映射配置
    定义每个场景需要采集的槽位集合

    三层关系：
    - 层1：slot ↔ metadata（通过 linked_field_id）
    - 层2：scene ↔ slot_bundle（本模型）
    - 层3：step.expected_variables ↔ slot_key（话术步骤引用）
    """

    __tablename__ = "scene_slot_bundles"
    __table_args__ = (
        Index("ix_scene_slot_bundles_tenant", "tenant_id"),
        Index("ix_scene_slot_bundles_tenant_scene", "tenant_id", "scene_key", unique=True),
        Index("ix_scene_slot_bundles_tenant_status", "tenant_id", "status"),
    )

    id: uuid.UUID = Field(default_factory=uuid.uuid4, primary_key=True)
    tenant_id: str = Field(..., description="Tenant ID for multi-tenant isolation", index=True)
    scene_key: str = Field(
        ...,
        description="场景标识，如 'open_consult', 'refund_apply', 'course_recommend'",
        min_length=1,
        max_length=100,
    )
    scene_name: str = Field(
        ...,
        description="场景名称，如 '开放咨询', '退款申请', '课程推荐'",
        min_length=1,
        max_length=100,
    )
    description: str | None = Field(
        default=None,
        description="场景描述"
    )
    required_slots: list[str] = Field(
        default_factory=list,
        sa_column=Column("required_slots", JSON, nullable=False),
        description="必填槽位 slot_key 列表"
    )
    optional_slots: list[str] = Field(
        default_factory=list,
        sa_column=Column("optional_slots", JSON, nullable=False),
        description="可选槽位 slot_key 列表"
    )
    slot_priority: list[str] | None = Field(
        default=None,
        sa_column=Column("slot_priority", JSON, nullable=True),
        description="槽位采集优先级顺序（slot_key 列表）"
    )
    completion_threshold: float = Field(
        default=1.0,
        ge=0.0,
        le=1.0,
        description="完成阈值（0.0-1.0），必填槽位填充比例达到此值视为完成"
    )
    ask_back_order: str = Field(
        default="priority",
        description="追问顺序策略: priority/required_first/parallel"
    )
    status: str = Field(
        default=SceneSlotBundleStatus.DRAFT.value,
        description="状态: draft/active/deprecated"
    )
    version: int = Field(default=1, description="版本号")
    created_at: datetime = Field(default_factory=datetime.utcnow, description="创建时间")
    updated_at: datetime = Field(default_factory=datetime.utcnow, description="更新时间")


class SceneSlotBundleCreate(SQLModel):
    """[AC-SCENE-SLOT-01] 创建场景槽位包"""

    scene_key: str = Field(..., min_length=1, max_length=100)
    scene_name: str = Field(..., min_length=1, max_length=100)
    description: str | None = None
    required_slots: list[str] = Field(default_factory=list)
    optional_slots: list[str] = Field(default_factory=list)
    slot_priority: list[str] | None = None
    completion_threshold: float = Field(default=1.0, ge=0.0, le=1.0)
    ask_back_order: str = Field(default="priority")
    status: str = Field(default=SceneSlotBundleStatus.DRAFT.value)


class SceneSlotBundleUpdate(SQLModel):
    """[AC-SCENE-SLOT-01] 更新场景槽位包"""

    scene_name: str | None = Field(default=None, min_length=1, max_length=100)
    description: str | None = None
    required_slots: list[str] | None = None
    optional_slots: list[str] | None = None
    slot_priority: list[str] | None = None
    completion_threshold: float | None = Field(default=None, ge=0.0, le=1.0)
    ask_back_order: str | None = None
    status: str | None = None
