"""Rule-group schema, bootstrap, and sync helpers."""

from __future__ import annotations

from typing import Any

from sqlalchemy import bindparam, text


async def ensure_rule_group_schema(session) -> None:
    """Create the new rule-group tables when they do not exist yet."""
    statements = [
        """
        CREATE TABLE IF NOT EXISTS leaudit_evaluation_point_groups (
            id BIGSERIAL PRIMARY KEY,
            pid BIGINT NOT NULL DEFAULT 0,
            code VARCHAR(120) NOT NULL,
            name VARCHAR(200) NOT NULL,
            description TEXT NULL,
            document_type_id BIGINT NULL REFERENCES leaudit_document_types(id),
            entry_module_id BIGINT NULL REFERENCES leaudit_entry_modules(id),
            sort_order INTEGER NOT NULL DEFAULT 0,
            is_enabled BOOLEAN NOT NULL DEFAULT TRUE,
            created_at TIMESTAMPTZ NOT NULL DEFAULT NOW(),
            updated_at TIMESTAMPTZ NOT NULL DEFAULT NOW(),
            deleted_at TIMESTAMPTZ NULL
        )
        """,
        """
        CREATE TABLE IF NOT EXISTS leaudit_rule_group_bindings (
            id BIGSERIAL PRIMARY KEY,
            group_id BIGINT NOT NULL REFERENCES leaudit_evaluation_point_groups(id),
            rule_set_id BIGINT NOT NULL REFERENCES leaudit_rule_sets(id),
            rule_type_binding_id BIGINT NULL REFERENCES leaudit_rule_type_bindings(id),
            priority INTEGER NOT NULL DEFAULT 0,
            is_active BOOLEAN NOT NULL DEFAULT TRUE,
            note TEXT NULL,
            created_at TIMESTAMPTZ NOT NULL DEFAULT NOW(),
            updated_at TIMESTAMPTZ NOT NULL DEFAULT NOW(),
            deleted_at TIMESTAMPTZ NULL
        )
        """,
        "CREATE INDEX IF NOT EXISTS idx_leaudit_ep_groups_pid ON leaudit_evaluation_point_groups(pid)",
        "CREATE INDEX IF NOT EXISTS idx_leaudit_ep_groups_doc_type ON leaudit_evaluation_point_groups(document_type_id)",
        "CREATE INDEX IF NOT EXISTS idx_leaudit_ep_groups_entry_module ON leaudit_evaluation_point_groups(entry_module_id)",
        "CREATE INDEX IF NOT EXISTS idx_leaudit_rule_group_bindings_group_id ON leaudit_rule_group_bindings(group_id)",
        "CREATE INDEX IF NOT EXISTS idx_leaudit_rule_group_bindings_rule_set_id ON leaudit_rule_group_bindings(rule_set_id)",
        "CREATE UNIQUE INDEX IF NOT EXISTS uq_leaudit_ep_groups_code_active ON leaudit_evaluation_point_groups (LOWER(code)) WHERE deleted_at IS NULL",
        "DROP INDEX IF EXISTS uq_leaudit_ep_groups_doc_type_active",
        "CREATE UNIQUE INDEX IF NOT EXISTS uq_leaudit_rule_group_bindings_active ON leaudit_rule_group_bindings (group_id, rule_set_id) WHERE deleted_at IS NULL",
    ]
    for statement in statements:
        await session.execute(text(statement))


async def bootstrap_rule_groups(session) -> None:
    """Seed doc-type roots and default child groups from current doc-type bindings."""
    await ensure_rule_group_schema(session)

    # Once the system has entered "business root" mode, stop recreating the
    # historical "doc type as top root" structure on every request.
    business_root_exists = bool(
        (
            await session.execute(
                text(
                    """
                    SELECT 1
                    FROM leaudit_evaluation_point_groups
                    WHERE deleted_at IS NULL
                      AND COALESCE(pid, 0) = 0
                      AND document_type_id IS NULL
                    LIMIT 1
                    """
                )
            )
        ).scalar_one_or_none()
    )
    if business_root_exists:
        return

    rows = (
        await session.execute(
            text(
                """
                SELECT
                    dt.id,
                    dt.code,
                    dt.name,
                    dt.description,
                    dt.entry_module_id,
                    dt.sort_order,
                    dt.is_enabled,
                    em.name AS entry_module_name,
                    COALESCE(
                        json_agg(
                            json_build_object(
                                'id', b.id,
                                'rule_set_id', b.rule_set_id,
                                'priority', b.priority,
                                'is_active', b.is_active,
                                'note', b.note
                            )
                            ORDER BY b.priority DESC, b.id ASC
                        ) FILTER (WHERE b.id IS NOT NULL AND b.deleted_at IS NULL),
                        '[]'::json
                    ) AS bindings
                FROM leaudit_document_types dt
                LEFT JOIN leaudit_entry_modules em ON em.id = dt.entry_module_id
                LEFT JOIN leaudit_rule_type_bindings b ON b.doc_type_id = dt.id AND b.deleted_at IS NULL
                WHERE dt.deleted_at IS NULL
                GROUP BY dt.id, dt.code, dt.name, dt.description, dt.entry_module_id, dt.sort_order, dt.is_enabled, em.name
                ORDER BY dt.sort_order ASC, dt.id ASC
                """
            )
        )
    ).mappings().all()

    for row in rows:
        top_group_id = await ensure_top_group(session, row)
        child_group_id = await ensure_default_child_group(session, row, top_group_id)
        existing_binding_count = int(
            (
                await session.execute(
                    text(
                        "SELECT COUNT(*) FROM leaudit_rule_group_bindings WHERE group_id = :group_id AND deleted_at IS NULL"
                    ),
                    {"group_id": child_group_id},
                )
            ).scalar_one()
        )
        bindings = list(row.get("bindings") or [])
        if existing_binding_count == 0 and bindings:
            await _replace_group_bindings(session, child_group_id, int(row["id"]), bindings)


async def ensure_group_for_doc_type(session, doc_type_id: int) -> dict[str, int]:
    """Ensure the tree nodes for one doc type exist and return their ids."""
    row = (
        await session.execute(
            text(
                """
                SELECT dt.id, dt.code, dt.name, dt.description, dt.entry_module_id, dt.sort_order, dt.is_enabled, em.name AS entry_module_name
                FROM leaudit_document_types dt
                LEFT JOIN leaudit_entry_modules em ON em.id = dt.entry_module_id
                WHERE dt.deleted_at IS NULL AND dt.id = :doc_type_id
                LIMIT 1
                """
            ),
            {"doc_type_id": doc_type_id},
        )
    ).mappings().first()
    if not row:
        raise ValueError("文档类型不存在")

    business_root_exists = bool(
        (
            await session.execute(
                text(
                    """
                    SELECT 1
                    FROM leaudit_evaluation_point_groups
                    WHERE deleted_at IS NULL
                      AND COALESCE(pid, 0) = 0
                      AND document_type_id IS NULL
                    LIMIT 1
                    """
                )
            )
        ).scalar_one_or_none()
    )
    if business_root_exists:
        target_root_code = "root.contract" if int(row.get("entry_module_id") or 0) == 1 else "root.casefile" if int(row.get("entry_module_id") or 0) == 2 else None
        if target_root_code:
            root_row = (
                await session.execute(
                    text(
                        """
                        SELECT id
                        FROM leaudit_evaluation_point_groups
                        WHERE deleted_at IS NULL
                          AND COALESCE(pid, 0) = 0
                          AND code = :code
                        LIMIT 1
                        """
                    ),
                    {"code": target_root_code},
                )
            ).mappings().first()
            if root_row:
                child_row = (
                    await session.execute(
                        text(
                            """
                            SELECT id
                            FROM leaudit_evaluation_point_groups
                            WHERE deleted_at IS NULL
                              AND pid = :pid
                              AND document_type_id = :doc_type_id
                            ORDER BY sort_order ASC, id ASC
                            LIMIT 1
                            """
                        ),
                        {"pid": int(root_row["id"]), "doc_type_id": doc_type_id},
                    )
                ).mappings().first()
                if child_row:
                    return {"top_group_id": int(root_row["id"]), "child_group_id": int(child_row["id"])}

    top_group_id = await ensure_top_group(session, row)
    child_group_id = await ensure_default_child_group(session, row, top_group_id)
    return {"top_group_id": top_group_id, "child_group_id": child_group_id}


async def sync_group_bindings_from_doc_type(session, doc_type_id: int, rule_set_ids: list[int]) -> int:
    """Mirror doc-type bindings into the child rule-group bindings."""
    await ensure_rule_group_schema(session)
    ids = [int(item) for item in rule_set_ids if item]
    ids = list(dict.fromkeys(ids))
    group_ids = await ensure_group_for_doc_type(session, doc_type_id)
    child_group_id = group_ids["child_group_id"]

    payload: list[dict[str, Any]] = []
    for index, rule_set_id in enumerate(ids):
        payload.append(
            {
                "rule_set_id": rule_set_id,
                "id": None,
                "priority": 100 - index,
                "is_active": True,
                "note": None,
            }
        )
    await _replace_group_bindings(session, child_group_id, doc_type_id, payload)
    return child_group_id


async def sync_doc_type_bindings_from_group(session, group_id: int) -> int | None:
    """兼容空实现：新链路已直接读取分组绑定，不再反向写旧文档类型绑定表。"""
    await ensure_rule_group_schema(session)
    group_row = (
        await session.execute(
            text(
                """
                SELECT g.id, g.document_type_id, dt.code AS document_type_code
                FROM leaudit_evaluation_point_groups g
                LEFT JOIN leaudit_document_types dt ON dt.id = g.document_type_id
                WHERE g.id = :group_id AND g.deleted_at IS NULL
                LIMIT 1
                """
            ),
            {"group_id": group_id},
        )
    ).mappings().first()
    if not group_row or group_row.get("document_type_id") is None:
        return None
    return int(group_row["document_type_id"])


async def ensure_top_group(session, doc_type_row) -> int:
    """Create or reuse the top-level root group for one doc type."""
    doc_type_id = int(doc_type_row["id"])
    top_code = str(doc_type_row["code"])
    top_name = str(doc_type_row["name"])
    top_sort = int(doc_type_row.get("sort_order") or 0)
    top = (
        await session.execute(
            text(
                """
                SELECT id
                FROM leaudit_evaluation_point_groups
                WHERE deleted_at IS NULL
                  AND COALESCE(pid, 0) = 0
                  AND document_type_id = :doc_type_id
                LIMIT 1
                """
            ),
            {"doc_type_id": doc_type_id},
        )
    ).mappings().first()
    if top:
        await session.execute(
            text(
                """
                UPDATE leaudit_evaluation_point_groups
                SET code = :code,
                    name = :name,
                    description = :description,
                    sort_order = :sort_order,
                    is_enabled = :is_enabled,
                    updated_at = NOW()
                WHERE id = :group_id
                """
            ),
            {
                "group_id": int(top["id"]),
                "code": top_code,
                "name": top_name,
                "description": doc_type_row.get("description"),
                "sort_order": top_sort,
                "is_enabled": bool(doc_type_row.get("is_enabled", True)),
            },
        )
        return int(top["id"])

    legacy = (
        await session.execute(
            text(
                """
                SELECT id
                FROM leaudit_evaluation_point_groups
                WHERE deleted_at IS NULL
                  AND document_type_id = :doc_type_id
                  AND LOWER(code) = LOWER(:code)
                ORDER BY CASE WHEN COALESCE(pid, 0) = 0 THEN 0 ELSE 1 END, id ASC
                LIMIT 1
                """
            ),
            {"doc_type_id": doc_type_id, "code": top_code},
        )
    ).mappings().first()
    if legacy:
        await session.execute(
            text(
                """
                UPDATE leaudit_evaluation_point_groups
                SET pid = 0,
                    code = :code,
                    name = :name,
                    description = :description,
                    document_type_id = :doc_type_id,
                    sort_order = :sort_order,
                    is_enabled = :is_enabled,
                    updated_at = NOW()
                WHERE id = :group_id
                """
            ),
            {
                "group_id": int(legacy["id"]),
                "doc_type_id": doc_type_id,
                "code": top_code,
                "name": top_name,
                "description": doc_type_row.get("description"),
                "sort_order": top_sort,
                "is_enabled": bool(doc_type_row.get("is_enabled", True)),
            },
        )
        return int(legacy["id"])

    inserted = (
        await session.execute(
            text(
                """
                INSERT INTO leaudit_evaluation_point_groups (
                    pid, code, name, description, document_type_id, sort_order, is_enabled, created_at, updated_at
                ) VALUES (
                    0, :code, :name, :description, :doc_type_id, :sort_order, :is_enabled, NOW(), NOW()
                )
                RETURNING id
                """
            ),
            {
                "doc_type_id": doc_type_id,
                "code": top_code,
                "name": top_name,
                "description": doc_type_row.get("description"),
                "sort_order": top_sort,
                "is_enabled": bool(doc_type_row.get("is_enabled", True)),
            },
        )
    ).mappings().one()
    return int(inserted["id"])


async def ensure_default_child_group(session, doc_type_row, top_group_id: int) -> int:
    """Ensure there is at least one default second-level group under a doc-type root."""
    doc_type_id = int(doc_type_row["id"])
    child = (
        await session.execute(
            text(
                """
                SELECT id, pid
                FROM leaudit_evaluation_point_groups
                WHERE deleted_at IS NULL
                  AND pid = :pid
                  AND document_type_id = :doc_type_id
                  AND LOWER(code) = LOWER(:code)
                LIMIT 1
                """
            ),
            {"pid": top_group_id, "doc_type_id": doc_type_id, "code": f"{doc_type_row['code']}.default"},
        )
    ).mappings().first()
    any_children = int(
        (
            await session.execute(
                text(
                    """
                    SELECT COUNT(*)
                    FROM leaudit_evaluation_point_groups
                    WHERE deleted_at IS NULL
                      AND pid = :pid
                    """
                ),
                {"pid": top_group_id},
            )
        ).scalar_one()
    )
    if child:
        return int(child["id"])
    if any_children > 0:
        adopted = (
            await session.execute(
                text(
                    """
                    SELECT id
                    FROM leaudit_evaluation_point_groups
                    WHERE deleted_at IS NULL
                      AND pid = :pid
                      AND document_type_id = :doc_type_id
                    ORDER BY sort_order ASC, id ASC
                    LIMIT 1
                    """
                ),
                {"pid": top_group_id, "doc_type_id": doc_type_id},
            )
        ).mappings().first()
        if adopted:
            return int(adopted["id"])

    payload = {
        "pid": top_group_id,
        "code": f"{doc_type_row['code']}.default",
        "name": "通用",
        "description": f"{doc_type_row['name']}默认子类型",
        "document_type_id": doc_type_id,
        "sort_order": int(doc_type_row.get("sort_order") or 0),
        "is_enabled": bool(doc_type_row.get("is_enabled", True)),
    }
    inserted = (
        await session.execute(
            text(
                """
                INSERT INTO leaudit_evaluation_point_groups (
                    pid, code, name, description, document_type_id, sort_order, is_enabled, created_at, updated_at
                ) VALUES (
                    :pid, :code, :name, :description, :document_type_id, :sort_order, :is_enabled, NOW(), NOW()
                )
                RETURNING id
                """
            ),
            payload,
        )
    ).mappings().one()
    default_child_id = int(inserted["id"])
    await _move_root_bindings_to_default_child(session, top_group_id, default_child_id)
    return int(inserted["id"])


async def _move_root_bindings_to_default_child(session, root_group_id: int, child_group_id: int) -> None:
    await session.execute(
        text(
            """
            UPDATE leaudit_rule_group_bindings
            SET group_id = :child_group_id,
                updated_at = NOW()
            WHERE group_id = :root_group_id
              AND deleted_at IS NULL
            """
        ),
        {"root_group_id": root_group_id, "child_group_id": child_group_id},
    )


async def _replace_group_bindings(session, child_group_id: int, doc_type_id: int, bindings: list[dict[str, Any]]) -> None:
    await session.execute(
        text(
            "UPDATE leaudit_rule_group_bindings SET deleted_at = NOW(), updated_at = NOW() WHERE group_id = :group_id AND deleted_at IS NULL"
        ),
        {"group_id": child_group_id},
    )
    for item in bindings:
        await session.execute(
            text(
                """
                INSERT INTO leaudit_rule_group_bindings (
                    group_id,
                    rule_set_id,
                    rule_type_binding_id,
                    priority,
                    is_active,
                    note,
                    created_at,
                    updated_at
                ) VALUES (
                    :group_id,
                    :rule_set_id,
                    :rule_type_binding_id,
                    :priority,
                    :is_active,
                    :note,
                    NOW(),
                    NOW()
                )
                """
            ),
            {
                "group_id": child_group_id,
                "rule_set_id": int(item["rule_set_id"]),
                "rule_type_binding_id": int(item["id"]) if item.get("id") else None,
                "priority": int(item.get("priority") or 0),
                "is_active": bool(item.get("is_active", True)),
                "note": item.get("note"),
            },
        )
    await sync_doc_type_bindings_from_group(session, child_group_id)
