"""Norm Database Models"""
import uuid
from datetime import datetime, timezone
from typing import Optional, List, Dict, Any
from sqlalchemy import String, Text, Integer, ForeignKey, CheckConstraint
from sqlalchemy.dialects.postgresql import UUID, JSONB
from sqlalchemy.orm import Mapped, mapped_column, relationship
from normbase.db.connection import Base


def utc_now() -> datetime:
    """Return UTC-naive datetime without using deprecated utcnow()."""
    return datetime.now(timezone.utc).replace(tzinfo=None)


class Norm(Base):
    """Norm database model"""
    __tablename__ = "norms"

    id: Mapped[uuid.UUID] = mapped_column(
        UUID(as_uuid=True), 
        primary_key=True, 
        default=uuid.uuid4
    )
    title: Mapped[str] = mapped_column(String(255), nullable=False)
    field_id: Mapped[Optional[uuid.UUID]] = mapped_column(
        UUID(as_uuid=True),
        ForeignKey("fields.id", ondelete="SET NULL"),
        comment="Information field this norm belongs to",
    )
    norm_type: Mapped[str] = mapped_column(
        String(50), 
        nullable=False,
        comment="obligation/permission/prohibition"
    )
    authority_level: Mapped[Optional[str]] = mapped_column(
        String(50),
        comment="legal/industrial/organizational/group"
    )
    formality_level: Mapped[Optional[str]] = mapped_column(
        String(20),
        nullable=True,
        comment="informal/formal/technical",
    )
    
    # Core norm elements
    subject: Mapped[Dict[str, Any]] = mapped_column(JSONB, default=dict)
    context: Mapped[Dict[str, Any]] = mapped_column(JSONB, default=dict)
    condition: Mapped[Dict[str, Any]] = mapped_column(JSONB, default=dict, nullable=False)
    action: Mapped[Dict[str, Any]] = mapped_column(JSONB, default=dict, nullable=False)
    consequence: Mapped[Dict[str, Any]] = mapped_column(JSONB, default=dict, nullable=False)
    
    # Metadata
    source: Mapped[Optional[str]] = mapped_column(Text)  # Brief source (e.g., "EU AI Act Article 5(1)(a)")
    source_details: Mapped[Optional[Dict[str, Any]]] = mapped_column(
        JSONB(none_as_null=True)
    )  # Detailed source info
    version: Mapped[int] = mapped_column(Integer, default=1, nullable=False)
    status: Mapped[str] = mapped_column(
        String(20), 
        default="draft",
        nullable=False,
        comment="draft/active/deprecated/archived",
    )
    
    # Relations to other norms
    case_refs: Mapped[List[str]] = mapped_column(JSONB, default=list, nullable=False)
    tags: Mapped[List["NormTag"]] = relationship(
        "NormTag",
        back_populates="norm",
        cascade="all, delete-orphan",
    )
    
    # Timestamps
    created_by: Mapped[Optional[str]] = mapped_column(String(100))
    created_at: Mapped[datetime] = mapped_column(
        default=utc_now,
        nullable=False
    )
    updated_at: Mapped[datetime] = mapped_column(
        default=utc_now,
        onupdate=utc_now,
        nullable=False
    )
    
    # Relationships
    versions: Mapped[List["NormVersion"]] = relationship(
        "NormVersion",
        back_populates="norm",
        cascade="all, delete-orphan"
    )
    
    __table_args__ = (
        CheckConstraint(
            "norm_type IN ('obligation', 'permission', 'prohibition')",
            name="ck_norm_type"
        ),
        CheckConstraint(
            "authority_level IN ('legal', 'industrial', 'organizational', 'group') OR authority_level IS NULL",
            name="ck_authority_level"
        ),
        CheckConstraint(
            "formality_level IS NULL OR formality_level IN ('informal', 'formal', 'technical')",
            name="ck_formality_level"
        ),
        CheckConstraint(
            "status IN ('draft', 'active', 'deprecated', 'archived')",
            name="ck_status"
        ),
        CheckConstraint(
            "version >= 1",
            name="ck_norm_version"
        ),
        CheckConstraint(
            "jsonb_typeof(subject) = 'object'",
            name="ck_norm_subject_object"
        ),
        CheckConstraint(
            "jsonb_typeof(context) = 'object'",
            name="ck_norm_context_object"
        ),
        CheckConstraint(
            "jsonb_typeof(condition) = 'object'",
            name="ck_norm_condition_object"
        ),
        CheckConstraint(
            "jsonb_typeof(action) = 'object'",
            name="ck_norm_action_object"
        ),
        CheckConstraint(
            "jsonb_typeof(consequence) = 'object'",
            name="ck_norm_consequence_object"
        ),
        CheckConstraint(
            "jsonb_typeof(case_refs) = 'array'",
            name="ck_norm_case_refs_array"
        ),
        CheckConstraint(
            "source_details IS NULL OR jsonb_typeof(source_details) = 'object'",
            name="ck_norm_source_details_object"
        ),
        CheckConstraint(
            "created_by IS NULL "
            "OR created_by LIKE 'user:%' "
            "OR created_by LIKE 'org:%' "
            "OR created_by LIKE 'system:%' "
            "OR created_by LIKE 'external:%'",
            name="ck_norm_created_by_format",
        ),
    )
    
    def __repr__(self) -> str:
        return f"<Norm(id={self.id}, title={self.title}, type={self.norm_type})>"


class NormVersion(Base):
    """Norm version history"""
    __tablename__ = "norm_versions"
    
    id: Mapped[uuid.UUID] = mapped_column(
        UUID(as_uuid=True), 
        primary_key=True, 
        default=uuid.uuid4
    )
    norm_id: Mapped[uuid.UUID] = mapped_column(
        UUID(as_uuid=True),
        ForeignKey("norms.id", ondelete="CASCADE"),
        nullable=False
    )
    version: Mapped[int] = mapped_column(Integer, nullable=False)
    snapshot: Mapped[Dict[str, Any]] = mapped_column(JSONB, nullable=False)
    change_note: Mapped[Optional[str]] = mapped_column(Text)
    created_at: Mapped[datetime] = mapped_column(default=utc_now)
    
    # Relationship
    norm: Mapped["Norm"] = relationship("Norm", back_populates="versions")
    
    def __repr__(self) -> str:
        return f"<NormVersion(norm_id={self.norm_id}, version={self.version})>"


class Field(Base):
    """Information field namespace for scoping norms."""
    __tablename__ = "fields"

    id: Mapped[uuid.UUID] = mapped_column(
        UUID(as_uuid=True),
        primary_key=True,
        default=uuid.uuid4,
    )
    name: Mapped[Optional[str]] = mapped_column(String(255), unique=True)
    org_id: Mapped[Optional[uuid.UUID]] = mapped_column(UUID(as_uuid=True))
    description: Mapped[Optional[str]] = mapped_column(Text)
    status: Mapped[str] = mapped_column(
        String(20),
        nullable=False,
        default="active",
        comment="active/inactive/archived",
    )
    created_at: Mapped[datetime] = mapped_column(default=utc_now, nullable=False)
    updated_at: Mapped[datetime] = mapped_column(
        default=utc_now,
        onupdate=utc_now,
        nullable=False,
    )
    __table_args__ = (
        CheckConstraint(
            "status IN ('active', 'inactive', 'archived')",
            name="ck_field_status",
        ),
    )

    def __repr__(self) -> str:
        return f"<Field(id={self.id}, name={self.name})>"


class NormTag(Base):
    """Norm tags for structured retrieval filters."""
    __tablename__ = "norm_tags"

    id: Mapped[uuid.UUID] = mapped_column(
        UUID(as_uuid=True),
        primary_key=True,
        default=uuid.uuid4,
    )
    norm_id: Mapped[uuid.UUID] = mapped_column(
        UUID(as_uuid=True),
        ForeignKey("norms.id", ondelete="CASCADE"),
        nullable=False,
        index=True,
    )
    tag: Mapped[str] = mapped_column(String(100), nullable=False)
    tag_category: Mapped[str] = mapped_column(
        String(50),
        nullable=False,
        comment="free-form tag category for retrieval facets",
    )

    norm: Mapped["Norm"] = relationship("Norm", back_populates="tags")

    def __repr__(self) -> str:
        return f"<NormTag(norm_id={self.norm_id}, category={self.tag_category}, tag={self.tag})>"


class NormRelation(Base):
    """Norm-to-norm relationships"""
    __tablename__ = "norm_relations"
    
    source_norm_id: Mapped[uuid.UUID] = mapped_column(
        UUID(as_uuid=True),
        ForeignKey("norms.id", ondelete="CASCADE"),
        primary_key=True
    )
    target_norm_id: Mapped[uuid.UUID] = mapped_column(
        UUID(as_uuid=True),
        ForeignKey("norms.id", ondelete="CASCADE"),
        primary_key=True
    )
    relation_type: Mapped[str] = mapped_column(
        String(50),
        primary_key=True,
        comment="conflicts_with/derives_from/supersedes/requires"
    )
    resolution: Mapped[Optional[str]] = mapped_column(String(50))
    
    __table_args__ = (
        CheckConstraint(
            "relation_type IN ('conflicts_with', 'derives_from', 'supersedes', 'requires')",
            name="ck_relation_type"
        ),
    )
    
    def __repr__(self) -> str:
        return f"<NormRelation({self.source_norm_id} -{self.relation_type}-> {self.target_norm_id})>"


class AuditLog(Base):
    """API audit trail"""
    __tablename__ = "audit_logs"
    
    id: Mapped[uuid.UUID] = mapped_column(
        UUID(as_uuid=True), 
        primary_key=True, 
        default=uuid.uuid4
    )
    api_key_id: Mapped[Optional[uuid.UUID]] = mapped_column(UUID(as_uuid=True))
    tool_name: Mapped[Optional[str]] = mapped_column(String(100))
    request: Mapped[Optional[Dict[str, Any]]] = mapped_column(JSONB)
    response: Mapped[Optional[Dict[str, Any]]] = mapped_column(JSONB)
    latency_ms: Mapped[Optional[int]] = mapped_column(Integer)
    created_at: Mapped[datetime] = mapped_column(default=utc_now)
    
    def __repr__(self) -> str:
        return f"<AuditLog(id={self.id}, tool={self.tool_name})>"


class APIKey(Base):
    """API key for agent authentication"""
    __tablename__ = "api_keys"
    
    id: Mapped[uuid.UUID] = mapped_column(
        UUID(as_uuid=True), 
        primary_key=True, 
        default=uuid.uuid4
    )
    key_hash: Mapped[str] = mapped_column(String(255), nullable=False)
    name: Mapped[Optional[str]] = mapped_column(String(100))
    agent_id: Mapped[Optional[str]] = mapped_column(String(100))
    scopes: Mapped[List[str]] = mapped_column(JSONB, default=lambda: ["*"])
    is_active: Mapped[bool] = mapped_column(default=True)
    created_at: Mapped[datetime] = mapped_column(default=utc_now)
    expires_at: Mapped[Optional[datetime]] = mapped_column()
    
    def __repr__(self) -> str:
        return f"<APIKey(id={self.id}, name={self.name})>"


class NormUsageLog(Base):
    """Norm usage outcome log for governance feedback loop."""
    __tablename__ = "norm_usage_logs"

    id: Mapped[uuid.UUID] = mapped_column(
        UUID(as_uuid=True),
        primary_key=True,
        default=uuid.uuid4
    )
    usage_token: Mapped[str] = mapped_column(String(100), nullable=False, index=True)
    norm_id: Mapped[uuid.UUID] = mapped_column(
        UUID(as_uuid=True),
        ForeignKey("norms.id", ondelete="CASCADE"),
        nullable=False
    )
    agent_id: Mapped[Optional[str]] = mapped_column(String(100))
    project_id: Mapped[Optional[str]] = mapped_column(String(100))
    task_context: Mapped[Optional[str]] = mapped_column(Text)
    outcome: Mapped[str] = mapped_column(
        String(20),
        nullable=False,
        comment="complied/violated/exception/inapplicable"
    )
    details: Mapped[Optional[Dict[str, Any]]] = mapped_column(JSONB)
    created_at: Mapped[datetime] = mapped_column(default=utc_now, nullable=False)

    __table_args__ = (
        CheckConstraint(
            "outcome IN ('complied', 'violated', 'exception', 'inapplicable')",
            name="ck_norm_usage_outcome",
        ),
    )

    def __repr__(self) -> str:
        return (
            f"<NormUsageLog(id={self.id}, norm_id={self.norm_id}, "
            f"outcome={self.outcome})>"
        )


class NormUsageToken(Base):
    """Issued retrieval token for strong outcome-binding checks."""
    __tablename__ = "norm_usage_tokens"

    usage_token: Mapped[str] = mapped_column(String(100), primary_key=True)
    norm_ids: Mapped[List[str]] = mapped_column(JSONB, default=list, nullable=False)
    reported_norm_ids: Mapped[List[str]] = mapped_column(JSONB, default=list, nullable=False)
    agent_id: Mapped[Optional[str]] = mapped_column(String(100))
    key_id: Mapped[Optional[str]] = mapped_column(String(100))
    created_at: Mapped[datetime] = mapped_column(default=utc_now, nullable=False)
    expires_at: Mapped[datetime] = mapped_column(nullable=False)
    closed_at: Mapped[Optional[datetime]] = mapped_column()

    def __repr__(self) -> str:
        return (
            f"<NormUsageToken(token={self.usage_token}, "
            f"norm_count={len(self.norm_ids or [])})>"
        )
