"""创建DMS七张核心表。

Revision ID: 0001_initial_schema
Revises:
Create Date: 2026-07-23
"""

from __future__ import annotations

from collections.abc import Sequence

from alembic import op
import sqlalchemy as sa
from sqlalchemy.dialects import mysql

revision: str = "0001_initial_schema"
down_revision: str | None = None
branch_labels: str | Sequence[str] | None = None
depends_on: str | Sequence[str] | None = None


def _business_columns() -> list[sa.Column]:
    return [
        sa.Column("id", sa.BigInteger(), autoincrement=True, nullable=False),
        sa.Column("created_by", sa.BigInteger(), nullable=True),
        sa.Column("updated_by", sa.BigInteger(), nullable=True),
        sa.Column(
            "created_at",
            mysql.DATETIME(fsp=3),
            server_default=sa.text("CURRENT_TIMESTAMP(3)"),
            nullable=False,
        ),
        sa.Column(
            "updated_at",
            mysql.DATETIME(fsp=3),
            server_default=sa.text("CURRENT_TIMESTAMP(3)"),
            nullable=False,
        ),
        sa.Column("deleted_at", mysql.DATETIME(fsp=3), nullable=True),
        sa.Column(
            "is_deleted",
            sa.Boolean(),
            server_default=sa.text("0"),
            nullable=False,
        ),
        sa.Column(
            "row_version",
            sa.Integer(),
            server_default=sa.text("0"),
            nullable=False,
        ),
        sa.PrimaryKeyConstraint("id"),
    ]


def upgrade() -> None:
    op.create_table(
        "sys_organization",
        *_business_columns(),
        sa.Column("parent_id", sa.BigInteger(), nullable=True),
        sa.Column("org_code", sa.String(length=64), nullable=False),
        sa.Column("org_name", sa.String(length=128), nullable=False),
        sa.Column("org_path", sa.String(length=1000), nullable=False),
        sa.Column(
            "sort_no",
            sa.Integer(),
            server_default=sa.text("0"),
            nullable=False,
        ),
        sa.Column(
            "status",
            sa.String(length=20),
            server_default=sa.text("'ENABLED'"),
            nullable=False,
        ),
        sa.CheckConstraint(
            "status IN ('ENABLED', 'DISABLED')",
            name="organization_status",
        ),
        sa.ForeignKeyConstraint(
            ["parent_id"],
            ["sys_organization.id"],
            name="fk_sys_organization_parent_id_sys_organization",
            ondelete="RESTRICT",
        ),
        sa.UniqueConstraint("org_code", name="uq_sys_organization_org_code"),
    )
    op.create_index(
        "ix_sys_organization_parent_active_sort",
        "sys_organization",
        ["parent_id", "is_deleted", "sort_no"],
    )
    op.create_index(
        "ix_sys_organization_status_active",
        "sys_organization",
        ["status", "is_deleted"],
    )

    op.create_table(
        "sys_user",
        *_business_columns(),
        sa.Column("username", sa.String(length=64), nullable=False),
        sa.Column("password_hash", sa.String(length=255), nullable=False),
        sa.Column("real_name", sa.String(length=64), nullable=False),
        sa.Column("organization_id", sa.BigInteger(), nullable=True),
        sa.Column("organization_name", sa.String(length=128), nullable=True),
        sa.Column(
            "role_code",
            sa.String(length=32),
            server_default=sa.text("'USER'"),
            nullable=False,
        ),
        sa.Column(
            "security_level",
            sa.String(length=32),
            server_default=sa.text("'INTERNAL'"),
            nullable=False,
        ),
        sa.Column(
            "status",
            sa.String(length=20),
            server_default=sa.text("'ENABLED'"),
            nullable=False,
        ),
        sa.Column(
            "auth_version",
            sa.Integer(),
            server_default=sa.text("0"),
            nullable=False,
        ),
        sa.Column("last_login_at", mysql.DATETIME(fsp=3), nullable=True),
        sa.Column("last_login_ip", sa.String(length=64), nullable=True),
        sa.CheckConstraint(
            "role_code IN ('USER', 'ADMIN', 'AUDITOR')",
            name="user_role_code",
        ),
        sa.CheckConstraint(
            "security_level IN "
            "('PUBLIC', 'INTERNAL', 'SECRET', 'CONFIDENTIAL', 'TOP_SECRET')",
            name="user_security_level",
        ),
        sa.CheckConstraint(
            "status IN ('ENABLED', 'DISABLED')",
            name="user_status",
        ),
        sa.ForeignKeyConstraint(
            ["organization_id"],
            ["sys_organization.id"],
            name="fk_sys_user_organization_id_sys_organization",
            ondelete="RESTRICT",
        ),
        sa.UniqueConstraint("username", name="uq_sys_user_username"),
    )
    op.create_index(
        "ix_sys_user_organization_active_status",
        "sys_user",
        ["organization_id", "is_deleted", "status"],
    )
    op.create_index(
        "ix_sys_user_role_status_active",
        "sys_user",
        ["role_code", "status", "is_deleted"],
    )

    op.create_table(
        "doc_category",
        *_business_columns(),
        sa.Column("parent_id", sa.BigInteger(), nullable=True),
        sa.Column("category_code", sa.String(length=64), nullable=False),
        sa.Column("category_name", sa.String(length=128), nullable=False),
        sa.Column(
            "category_type",
            sa.String(length=32),
            server_default=sa.text("'OTHER'"),
            nullable=False,
        ),
        sa.Column("category_path", sa.String(length=1000), nullable=False),
        sa.Column(
            "sort_no",
            sa.Integer(),
            server_default=sa.text("0"),
            nullable=False,
        ),
        sa.Column(
            "document_count",
            sa.Integer(),
            server_default=sa.text("0"),
            nullable=False,
        ),
        sa.Column(
            "status",
            sa.String(length=20),
            server_default=sa.text("'ENABLED'"),
            nullable=False,
        ),
        sa.CheckConstraint(
            "category_type IN ('SCENE', 'STYLE', 'SITUATION', 'VERSION', 'OTHER')",
            name="category_type",
        ),
        sa.CheckConstraint(
            "status IN ('ENABLED', 'DISABLED')",
            name="category_status",
        ),
        sa.ForeignKeyConstraint(
            ["parent_id"],
            ["doc_category.id"],
            name="fk_doc_category_parent_id_doc_category",
            ondelete="RESTRICT",
        ),
        sa.UniqueConstraint("category_code", name="uq_doc_category_category_code"),
    )
    op.create_index(
        "ix_doc_category_parent_active_sort",
        "doc_category",
        ["parent_id", "is_deleted", "sort_no"],
    )
    op.create_index(
        "ix_doc_category_status_active",
        "doc_category",
        ["status", "is_deleted"],
    )
    op.create_index(
        "ix_doc_category_type_active",
        "doc_category",
        ["category_type", "is_deleted"],
    )

    op.create_table(
        "doc_document",
        *_business_columns(),
        sa.Column("document_name", sa.String(length=255), nullable=False),
        sa.Column("summary", sa.Text(), nullable=True),
        sa.Column("document_type", sa.String(length=32), nullable=False),
        sa.Column(
            "document_status",
            sa.String(length=32),
            server_default=sa.text("'DRAFT'"),
            nullable=False,
        ),
        sa.Column(
            "security_level",
            sa.String(length=32),
            server_default=sa.text("'INTERNAL'"),
            nullable=False,
        ),
        sa.Column(
            "visibility_type",
            sa.String(length=32),
            server_default=sa.text("'CUSTOM'"),
            nullable=False,
        ),
        sa.Column("attachment_type", sa.String(length=32), nullable=True),
        sa.Column("category_id", sa.BigInteger(), nullable=True),
        sa.Column("category_name", sa.String(length=128), nullable=True),
        sa.Column("category_path", sa.String(length=1000), nullable=True),
        sa.Column("parent_document_id", sa.BigInteger(), nullable=True),
        sa.Column("root_document_id", sa.BigInteger(), nullable=True),
        sa.Column("tags", mysql.JSON(), nullable=True),
        sa.Column("original_file_name", sa.String(length=255), nullable=False),
        sa.Column("file_relative_path", sa.String(length=1000), nullable=False),
        sa.Column("file_extension", sa.String(length=20), nullable=False),
        sa.Column("mime_type", sa.String(length=128), nullable=True),
        sa.Column(
            "file_size",
            sa.BigInteger(),
            server_default=sa.text("0"),
            nullable=False,
        ),
        sa.Column("file_hash", sa.String(length=64), nullable=False),
        sa.Column(
            "child_count",
            sa.Integer(),
            server_default=sa.text("0"),
            nullable=False,
        ),
        sa.Column(
            "attachment_count",
            sa.Integer(),
            server_default=sa.text("0"),
            nullable=False,
        ),
        sa.Column(
            "view_count",
            sa.BigInteger(),
            server_default=sa.text("0"),
            nullable=False,
        ),
        sa.Column(
            "download_count",
            sa.BigInteger(),
            server_default=sa.text("0"),
            nullable=False,
        ),
        sa.Column("search_text", sa.Text(), nullable=True),
        sa.Column("created_by_name", sa.String(length=64), nullable=True),
        sa.Column("updated_by_name", sa.String(length=64), nullable=True),
        sa.CheckConstraint(
            "document_type IN ('MAIN', 'SUB_PLAN', 'ATTACHMENT')",
            name="document_type",
        ),
        sa.CheckConstraint(
            "document_status IN ('DRAFT', 'PUBLISHED', 'ARCHIVED')",
            name="document_status",
        ),
        sa.CheckConstraint(
            "security_level IN "
            "('PUBLIC', 'INTERNAL', 'SECRET', 'CONFIDENTIAL', 'TOP_SECRET')",
            name="document_security_level",
        ),
        sa.CheckConstraint(
            "visibility_type IN ('ALL_AUTHENTICATED', 'ORGANIZATION', 'CUSTOM')",
            name="document_visibility_type",
        ),
        sa.CheckConstraint(
            "attachment_type IS NULL OR attachment_type IN "
            "('POLICY', 'WORK_STANDARD', 'TABLE', 'DIAGRAM', 'OTHER')",
            name="document_attachment_type",
        ),
        sa.CheckConstraint(
            "("
            "document_type = 'MAIN' "
            "AND parent_document_id IS NULL "
            "AND category_id IS NOT NULL "
            "AND attachment_type IS NULL"
            ") OR ("
            "document_type = 'SUB_PLAN' "
            "AND parent_document_id IS NOT NULL "
            "AND root_document_id IS NOT NULL "
            "AND category_id IS NOT NULL "
            "AND attachment_type IS NULL"
            ") OR ("
            "document_type = 'ATTACHMENT' "
            "AND parent_document_id IS NULL "
            "AND root_document_id IS NULL "
            "AND category_id IS NULL "
            "AND attachment_type IS NOT NULL "
            "AND security_level = 'PUBLIC' "
            "AND visibility_type = 'ALL_AUTHENTICATED'"
            ")",
            name="document_type_shape",
        ),
        sa.ForeignKeyConstraint(
            ["category_id"],
            ["doc_category.id"],
            name="fk_doc_document_category_id_doc_category",
            ondelete="RESTRICT",
        ),
        sa.ForeignKeyConstraint(
            ["parent_document_id"],
            ["doc_document.id"],
            name="fk_doc_document_parent_document_id_doc_document",
            ondelete="RESTRICT",
        ),
        sa.ForeignKeyConstraint(
            ["root_document_id"],
            ["doc_document.id"],
            name="fk_doc_document_root_document_id_doc_document",
            ondelete="RESTRICT",
        ),
    )
    op.create_index(
        "ix_doc_document_type_active_updated",
        "doc_document",
        ["document_type", "is_deleted", "updated_at"],
    )
    op.create_index(
        "ix_doc_document_parent_type_active",
        "doc_document",
        ["parent_document_id", "document_type", "is_deleted"],
    )
    op.create_index(
        "ix_doc_document_root_active",
        "doc_document",
        ["root_document_id", "is_deleted"],
    )
    op.create_index(
        "ix_doc_document_category_active",
        "doc_document",
        ["category_id", "is_deleted"],
    )
    op.create_index(
        "ix_doc_document_status_active",
        "doc_document",
        ["document_status", "is_deleted"],
    )
    op.create_index(
        "ix_doc_document_security_active",
        "doc_document",
        ["security_level", "is_deleted"],
    )
    op.create_index("ix_doc_document_file_hash", "doc_document", ["file_hash"])
    op.create_index(
        "ix_doc_document_creator_active",
        "doc_document",
        ["created_by", "is_deleted"],
    )
    op.create_index(
        "ix_doc_document_attachment_type_active_updated",
        "doc_document",
        ["attachment_type", "is_deleted", "updated_at"],
    )
    op.create_index(
        "ix_doc_document_file_extension_active_updated",
        "doc_document",
        ["file_extension", "is_deleted", "updated_at"],
    )

    op.create_table(
        "doc_attachment_binding",
        *_business_columns(),
        sa.Column("main_document_id", sa.BigInteger(), nullable=False),
        sa.Column("attachment_document_id", sa.BigInteger(), nullable=False),
        sa.Column(
            "sort_no",
            sa.Integer(),
            server_default=sa.text("0"),
            nullable=False,
        ),
        sa.Column(
            "active_marker",
            sa.Integer(),
            sa.Computed("IF(is_deleted = 0, 1, NULL)", persisted=False),
            nullable=True,
        ),
        sa.ForeignKeyConstraint(
            ["attachment_document_id"],
            ["doc_document.id"],
            name="fk_doc_attachment_binding_attachment_document_id_doc_document",
            ondelete="RESTRICT",
        ),
        sa.ForeignKeyConstraint(
            ["main_document_id"],
            ["doc_document.id"],
            name="fk_doc_attachment_binding_main_document_id_doc_document",
            ondelete="RESTRICT",
        ),
        sa.UniqueConstraint(
            "main_document_id",
            "attachment_document_id",
            "active_marker",
            name="uq_doc_attachment_binding_active",
        ),
    )
    op.create_index(
        "ix_doc_attachment_binding_main_active_sort",
        "doc_attachment_binding",
        ["main_document_id", "is_deleted", "sort_no"],
    )
    op.create_index(
        "ix_doc_attachment_binding_attachment_active",
        "doc_attachment_binding",
        ["attachment_document_id", "is_deleted"],
    )

    op.create_table(
        "doc_permission",
        *_business_columns(),
        sa.Column("document_id", sa.BigInteger(), nullable=False),
        sa.Column("subject_type", sa.String(length=20), nullable=False),
        sa.Column("subject_id", sa.BigInteger(), nullable=False),
        sa.Column("subject_name", sa.String(length=255), nullable=False),
        sa.Column(
            "can_view",
            sa.Boolean(),
            server_default=sa.text("1"),
            nullable=False,
        ),
        sa.Column(
            "can_download",
            sa.Boolean(),
            server_default=sa.text("0"),
            nullable=False,
        ),
        sa.Column(
            "can_edit",
            sa.Boolean(),
            server_default=sa.text("0"),
            nullable=False,
        ),
        sa.Column(
            "can_manage_permission",
            sa.Boolean(),
            server_default=sa.text("0"),
            nullable=False,
        ),
        sa.Column(
            "can_delete",
            sa.Boolean(),
            server_default=sa.text("0"),
            nullable=False,
        ),
        sa.Column(
            "active_marker",
            sa.Integer(),
            sa.Computed("IF(is_deleted = 0, 1, NULL)", persisted=False),
            nullable=True,
        ),
        sa.CheckConstraint(
            "subject_type IN ('ORG', 'USER')",
            name="permission_subject_type",
        ),
        sa.ForeignKeyConstraint(
            ["document_id"],
            ["doc_document.id"],
            name="fk_doc_permission_document_id_doc_document",
            ondelete="RESTRICT",
        ),
        sa.UniqueConstraint(
            "document_id",
            "subject_type",
            "subject_id",
            "active_marker",
            name="uq_doc_permission_active",
        ),
    )
    op.create_index(
        "ix_doc_permission_document_active",
        "doc_permission",
        ["document_id", "is_deleted"],
    )
    op.create_index(
        "ix_doc_permission_subject_active",
        "doc_permission",
        ["subject_type", "subject_id", "is_deleted"],
    )

    op.create_table(
        "sys_audit_log",
        sa.Column("id", sa.BigInteger(), autoincrement=True, nullable=False),
        sa.Column("user_id", sa.BigInteger(), nullable=True),
        sa.Column("username", sa.String(length=64), nullable=True),
        sa.Column("real_name", sa.String(length=64), nullable=True),
        sa.Column("organization_id", sa.BigInteger(), nullable=True),
        sa.Column("organization_name", sa.String(length=128), nullable=True),
        sa.Column("action_type", sa.String(length=32), nullable=False),
        sa.Column("target_type", sa.String(length=32), nullable=True),
        sa.Column("target_id", sa.BigInteger(), nullable=True),
        sa.Column("target_name", sa.String(length=255), nullable=True),
        sa.Column("operation_result", sa.String(length=20), nullable=False),
        sa.Column("failure_reason", sa.String(length=1000), nullable=True),
        sa.Column("client_ip", sa.String(length=64), nullable=True),
        sa.Column("user_agent", sa.String(length=500), nullable=True),
        sa.Column("request_id", sa.String(length=64), nullable=True),
        sa.Column("operation_detail", mysql.JSON(), nullable=True),
        sa.Column(
            "created_at",
            mysql.DATETIME(fsp=3),
            server_default=sa.text("CURRENT_TIMESTAMP(3)"),
            nullable=False,
        ),
        sa.CheckConstraint(
            "action_type IN ("
            "'LOGIN', 'LOGOUT', 'VIEW_DOCUMENT', 'DOWNLOAD_DOCUMENT', "
            "'UPLOAD_DOCUMENT', 'BATCH_IMPORT', 'EDIT_DOCUMENT', "
            "'DELETE_DOCUMENT', 'CREATE_CATEGORY', 'EDIT_CATEGORY', "
            "'DELETE_CATEGORY', 'CHANGE_PERMISSION', 'BIND_ATTACHMENT', "
            "'UNBIND_ATTACHMENT'"
            ")",
            name="audit_action_type",
        ),
        sa.CheckConstraint(
            "target_type IS NULL OR target_type IN ("
            "'AUTH', 'CATEGORY', 'DOCUMENT', 'ATTACHMENT', "
            "'PERMISSION', 'ATTACHMENT_BINDING'"
            ")",
            name="audit_target_type",
        ),
        sa.CheckConstraint(
            "operation_result IN ('SUCCESS', 'FAILURE')",
            name="audit_operation_result",
        ),
        sa.PrimaryKeyConstraint("id"),
    )
    op.create_index(
        "ix_sys_audit_log_created_at",
        "sys_audit_log",
        ["created_at"],
    )
    op.create_index(
        "ix_sys_audit_log_user_created",
        "sys_audit_log",
        ["user_id", "created_at"],
    )
    op.create_index(
        "ix_sys_audit_log_organization_created",
        "sys_audit_log",
        ["organization_id", "created_at"],
    )
    op.create_index(
        "ix_sys_audit_log_action_created",
        "sys_audit_log",
        ["action_type", "created_at"],
    )
    op.create_index(
        "ix_sys_audit_log_target_created",
        "sys_audit_log",
        ["target_type", "target_id", "created_at"],
    )
    op.create_index(
        "ix_sys_audit_log_result_created",
        "sys_audit_log",
        ["operation_result", "created_at"],
    )
    op.create_index(
        "ix_sys_audit_log_request_id",
        "sys_audit_log",
        ["request_id"],
    )


def downgrade() -> None:
    op.drop_table("sys_audit_log")
    op.drop_table("doc_permission")
    op.drop_table("doc_attachment_binding")
    op.drop_table("doc_document")
    op.drop_table("doc_category")
    op.drop_table("sys_user")
    op.drop_table("sys_organization")
