from __future__ import annotations

import uuid
from datetime import datetime
from typing import Any

from sqlalchemy import (
    Boolean,
    CheckConstraint,
    DateTime,
    ForeignKey,
    ForeignKeyConstraint,
    Index,
    String,
    Text,
    UniqueConstraint,
    func,
)
from sqlalchemy.orm import Mapped, mapped_column, relationship
from sqlalchemy.types import JSON

from app.core.agent_asset_scope import AGENT_ASSET_PLATFORM_TENANT_ID
from app.db.base_class import Base


class AgentAsset(Base):
    __tablename__ = "agent_assets"
    __table_args__ = (
        UniqueConstraint(
            "tenant_id",
            "scope",
            "id",
            name="uq_agent_assets_tenant_scope_id",
        ),
        UniqueConstraint(
            "tenant_id",
            "scope",
            "code",
            name="uq_agent_assets_tenant_scope_code",
        ),
        CheckConstraint(
            "(scope = 'platform' AND tenant_id = 'platform') OR "
            "(scope = 'tenant' AND tenant_id <> 'platform')",
            name="ck_agent_assets_scope_tenant",
        ),
        Index("ix_agent_assets_scope_tenant", "scope", "tenant_id"),
    )

    id: Mapped[str] = mapped_column(String(36), primary_key=True, default=lambda: str(uuid.uuid4()))
    tenant_id: Mapped[str] = mapped_column(
        String(64),
        ForeignKey("tenants.tenant_id", ondelete="RESTRICT"),
        nullable=False,
        default=AGENT_ASSET_PLATFORM_TENANT_ID,
        server_default=AGENT_ASSET_PLATFORM_TENANT_ID,
    )
    scope: Mapped[str] = mapped_column(
        String(16),
        nullable=False,
        default="platform",
        server_default="platform",
    )
    asset_type: Mapped[str] = mapped_column(String(20), index=True)
    code: Mapped[str] = mapped_column(String(100), index=True)
    name: Mapped[str] = mapped_column(String(200))
    description: Mapped[str] = mapped_column(Text(), default="")
    domain: Mapped[str] = mapped_column(String(50), index=True)
    scenario_json: Mapped[list[Any]] = mapped_column(JSON, default=list)
    owner: Mapped[str] = mapped_column(String(100))
    reviewer: Mapped[str | None] = mapped_column(String(100), nullable=True)
    status: Mapped[str] = mapped_column(String(20), index=True, default="draft")
    current_version: Mapped[str | None] = mapped_column(String(30), nullable=True)
    published_version: Mapped[str | None] = mapped_column(String(30), nullable=True)
    working_version: Mapped[str | None] = mapped_column(String(30), nullable=True)
    config_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()
    )

    versions = relationship(
        "AgentAssetVersion",
        back_populates="asset",
        cascade="all, delete-orphan",
        order_by="desc(AgentAssetVersion.created_at)",
    )
    reviews = relationship(
        "AgentAssetReview",
        back_populates="asset",
        cascade="all, delete-orphan",
        order_by="desc(AgentAssetReview.created_at)",
    )
    scheduled_runs = relationship("AgentRun", back_populates="task_asset")
    test_runs = relationship(
        "AgentAssetTestRun",
        back_populates="asset",
        cascade="all, delete-orphan",
        order_by="desc(AgentAssetTestRun.created_at)",
    )
    rule_feedback_items = relationship(
        "AgentAssetRuleFeedback",
        back_populates="asset",
        cascade="all, delete-orphan",
        order_by="desc(AgentAssetRuleFeedback.created_at)",
    )


class AgentAssetOnlyOfficeSession(Base):
    __tablename__ = "agent_asset_onlyoffice_sessions"
    __table_args__ = (
        CheckConstraint(
            "(resource_scope = 'platform' AND tenant_id = 'platform') OR "
            "(resource_scope = 'tenant' AND tenant_id <> 'platform')",
            name="ck_agent_asset_onlyoffice_sessions_scope_tenant",
        ),
        CheckConstraint(
            "status IN ('active', 'processing', 'consumed', 'failed', 'revoked')",
            name="ck_agent_asset_onlyoffice_sessions_status",
        ),
        CheckConstraint(
            "(status = 'active' AND claimed_at IS NULL AND consumed_at IS NULL) OR "
            "(status IN ('processing', 'failed') AND claimed_at IS NOT NULL "
            "AND consumed_at IS NULL) OR "
            "(status = 'consumed' AND claimed_at IS NOT NULL AND consumed_at IS NOT NULL) OR "
            "(status = 'revoked' AND consumed_at IS NULL)",
            name="ck_agent_asset_onlyoffice_sessions_lifecycle",
        ),
        Index(
            "ix_agent_asset_onlyoffice_sessions_tenant_asset",
            "tenant_id",
            "resource_scope",
            "asset_id",
            "created_at",
        ),
        Index(
            "ix_agent_asset_onlyoffice_sessions_status_expiry",
            "status",
            "expires_at",
        ),
    )

    jti: Mapped[str] = mapped_column(String(36), primary_key=True)
    tenant_id: Mapped[str] = mapped_column(
        String(64),
        ForeignKey("tenants.tenant_id", ondelete="CASCADE"),
        nullable=False,
    )
    resource_scope: Mapped[str] = mapped_column(String(16), nullable=False)
    asset_id: Mapped[str] = mapped_column(String(100), nullable=False)
    document_key: Mapped[str] = mapped_column(String(200), nullable=False)
    document_version: Mapped[str] = mapped_column(String(30), nullable=False)
    document_fingerprint: Mapped[str] = mapped_column(String(160), nullable=False)
    audience: Mapped[str] = mapped_column(String(80), nullable=False)
    writable: Mapped[bool] = mapped_column(Boolean, nullable=False)
    status: Mapped[str] = mapped_column(String(16), nullable=False)
    actor: Mapped[str] = mapped_column(String(160), nullable=False)
    expires_at: Mapped[datetime] = mapped_column(DateTime(timezone=True), nullable=False)
    claimed_at: Mapped[datetime | None] = mapped_column(DateTime(timezone=True), nullable=True)
    consumed_at: Mapped[datetime | None] = mapped_column(DateTime(timezone=True), nullable=True)
    failure_reason: Mapped[str] = mapped_column(Text(), nullable=False, default="")
    created_at: Mapped[datetime] = mapped_column(
        DateTime(timezone=True), server_default=func.now(), nullable=False
    )


class AgentAssetVersion(Base):
    __tablename__ = "agent_asset_versions"
    __table_args__ = (
        UniqueConstraint("asset_id", "version", name="uq_agent_asset_versions_asset_version"),
        CheckConstraint(
            "(scope = 'platform' AND tenant_id = 'platform') OR "
            "(scope = 'tenant' AND tenant_id <> 'platform')",
            name="ck_agent_asset_versions_scope_tenant",
        ),
        ForeignKeyConstraint(
            ["tenant_id", "scope", "asset_id"],
            ["agent_assets.tenant_id", "agent_assets.scope", "agent_assets.id"],
            ondelete="CASCADE",
            name="fk_agent_asset_versions_tenant_asset",
        ),
        Index("ix_agent_asset_versions_tenant_asset", "tenant_id", "scope", "asset_id"),
    )

    id: Mapped[str] = mapped_column(String(36), primary_key=True, default=lambda: str(uuid.uuid4()))
    tenant_id: Mapped[str] = mapped_column(
        String(64),
        ForeignKey("tenants.tenant_id", ondelete="RESTRICT"),
        nullable=False,
        default=AGENT_ASSET_PLATFORM_TENANT_ID,
        server_default=AGENT_ASSET_PLATFORM_TENANT_ID,
    )
    scope: Mapped[str] = mapped_column(
        String(16), nullable=False, default="platform", server_default="platform"
    )
    asset_id: Mapped[str] = mapped_column(String(36), index=True)
    version: Mapped[str] = mapped_column(String(30))
    content: Mapped[str] = mapped_column(Text())
    content_type: Mapped[str] = mapped_column(String(20))
    change_note: Mapped[str | None] = mapped_column(Text(), nullable=True)
    created_by: Mapped[str] = mapped_column(String(100))
    created_at: Mapped[datetime] = mapped_column(DateTime(timezone=True), server_default=func.now())

    asset = relationship("AgentAsset", back_populates="versions")


class AgentAssetReview(Base):
    __tablename__ = "agent_asset_reviews"
    __table_args__ = (
        CheckConstraint(
            "(scope = 'platform' AND tenant_id = 'platform') OR "
            "(scope = 'tenant' AND tenant_id <> 'platform')",
            name="ck_agent_asset_reviews_scope_tenant",
        ),
        ForeignKeyConstraint(
            ["tenant_id", "scope", "asset_id"],
            ["agent_assets.tenant_id", "agent_assets.scope", "agent_assets.id"],
            ondelete="CASCADE",
            name="fk_agent_asset_reviews_tenant_asset",
        ),
        Index("ix_agent_asset_reviews_tenant_asset", "tenant_id", "scope", "asset_id"),
    )

    id: Mapped[str] = mapped_column(String(36), primary_key=True, default=lambda: str(uuid.uuid4()))
    tenant_id: Mapped[str] = mapped_column(
        String(64),
        ForeignKey("tenants.tenant_id", ondelete="RESTRICT"),
        nullable=False,
        default=AGENT_ASSET_PLATFORM_TENANT_ID,
        server_default=AGENT_ASSET_PLATFORM_TENANT_ID,
    )
    scope: Mapped[str] = mapped_column(
        String(16), nullable=False, default="platform", server_default="platform"
    )
    asset_id: Mapped[str] = mapped_column(String(36), index=True)
    version: Mapped[str] = mapped_column(String(30))
    reviewer: Mapped[str] = mapped_column(String(100))
    review_status: Mapped[str] = mapped_column(String(20), index=True)
    review_note: Mapped[str | None] = mapped_column(Text(), nullable=True)
    reviewed_at: Mapped[datetime | None] = mapped_column(DateTime(timezone=True), nullable=True)
    created_at: Mapped[datetime] = mapped_column(DateTime(timezone=True), server_default=func.now())

    asset = relationship("AgentAsset", back_populates="reviews")


class AgentAssetTestRun(Base):
    __tablename__ = "agent_asset_test_runs"
    __table_args__ = (
        CheckConstraint(
            "(scope = 'platform' AND tenant_id = 'platform') OR "
            "(scope = 'tenant' AND tenant_id <> 'platform')",
            name="ck_agent_asset_test_runs_scope_tenant",
        ),
        Index("ix_agent_asset_test_runs_tenant_asset", "tenant_id", "scope", "asset_id"),
    )

    id: Mapped[str] = mapped_column(String(36), primary_key=True, default=lambda: str(uuid.uuid4()))
    tenant_id: Mapped[str] = mapped_column(
        String(64),
        ForeignKey("tenants.tenant_id", ondelete="RESTRICT"),
        nullable=False,
        default=AGENT_ASSET_PLATFORM_TENANT_ID,
        server_default=AGENT_ASSET_PLATFORM_TENANT_ID,
    )
    scope: Mapped[str] = mapped_column(
        String(16), nullable=False, default="platform", server_default="platform"
    )
    asset_id: Mapped[str] = mapped_column(
        ForeignKey("agent_assets.id", ondelete="CASCADE"), index=True
    )
    version: Mapped[str] = mapped_column(String(30), index=True)
    test_type: Mapped[str] = mapped_column(String(30), index=True)
    status: Mapped[str] = mapped_column(String(20), index=True)
    passed: Mapped[bool] = mapped_column(Boolean, default=False, index=True)
    summary: Mapped[str] = mapped_column(Text(), default="")
    input_json: Mapped[dict[str, Any]] = mapped_column(JSON, default=dict)
    result_json: Mapped[dict[str, Any]] = mapped_column(JSON, default=dict)
    created_by: Mapped[str] = mapped_column(String(100))
    created_at: Mapped[datetime] = mapped_column(DateTime(timezone=True), server_default=func.now())

    asset = relationship("AgentAsset", back_populates="test_runs")


class AgentAssetRuleFeedback(Base):
    __tablename__ = "agent_asset_rule_feedback"
    __table_args__ = (
        Index("ix_agent_asset_rule_feedback_asset_version", "asset_id", "version"),
        Index("ix_agent_asset_rule_feedback_type_status", "feedback_type", "status"),
        Index(
            "ix_agent_asset_rule_feedback_tenant_asset",
            "tenant_id",
            "scope",
            "asset_id",
        ),
        CheckConstraint(
            "(scope = 'platform' AND tenant_id = 'platform') OR "
            "(scope = 'tenant' AND tenant_id <> 'platform')",
            name="ck_agent_asset_rule_feedback_scope_tenant",
        ),
    )

    id: Mapped[str] = mapped_column(String(36), primary_key=True, default=lambda: str(uuid.uuid4()))
    feedback_id: Mapped[str] = mapped_column(
        String(50),
        unique=True,
        index=True,
        default=lambda: f"arf_{uuid.uuid4().hex[:16]}",
    )
    tenant_id: Mapped[str] = mapped_column(
        String(64),
        ForeignKey("tenants.tenant_id", ondelete="RESTRICT"),
        nullable=False,
        default=AGENT_ASSET_PLATFORM_TENANT_ID,
        server_default=AGENT_ASSET_PLATFORM_TENANT_ID,
    )
    scope: Mapped[str] = mapped_column(
        String(16), nullable=False, default="platform", server_default="platform"
    )
    asset_id: Mapped[str] = mapped_column(
        ForeignKey("agent_assets.id", ondelete="CASCADE"), index=True
    )
    version: Mapped[str] = mapped_column(String(30), index=True)
    feedback_type: Mapped[str] = mapped_column(String(30), index=True)
    status: Mapped[str] = mapped_column(String(30), default="open", index=True)
    subject_type: Mapped[str] = mapped_column(String(50), default="", index=True)
    subject_key: Mapped[str] = mapped_column(String(160), default="", index=True)
    subject_label: Mapped[str] = mapped_column(String(200), default="")
    actual_result_json: Mapped[dict[str, Any]] = mapped_column(JSON, default=dict)
    expected_result_json: Mapped[dict[str, Any]] = mapped_column(JSON, default=dict)
    comment: Mapped[str | None] = mapped_column(Text(), nullable=True)
    payload_json: Mapped[dict[str, Any]] = mapped_column(JSON, default=dict)
    created_by: Mapped[str] = mapped_column(String(100), default="", index=True)
    created_at: Mapped[datetime] = mapped_column(
        DateTime(timezone=True), server_default=func.now(), index=True
    )

    asset = relationship("AgentAsset", back_populates="rule_feedback_items")
