"""规则租户物化服务。"""

from __future__ import annotations

from typing import Any

from sqlalchemy import text

from fastapi_common.fastapi_common_sqlalchemy.database import GetAsyncSession


class RuleTenantMaterializer:
    """将平台模板规则物化为租户私有规则资产。"""

    _PLATFORM_TEMPLATE_TENANTS = {"PUBLIC", "PROVINCIAL"}

    def _template_source_order(self) -> list[str]:
        return ["PUBLIC", "PROVINCIAL"]

    def _filter_materializable_tenants(self, tenants: list[dict[str, Any]]) -> list[str]:
        result: list[str] = []
        for tenant in tenants:
            tenant_code = str(tenant.get("tenant_code") or "").strip().upper()
            if not tenant_code or tenant_code in self._PLATFORM_TEMPLATE_TENANTS:
                continue
            if not bool(tenant.get("is_enabled", True)):
                continue
            result.append(tenant_code)
        return result

    def _legacy_region_for_tenant(self, tenant_code: str) -> str:
        return str(tenant_code or "").strip().upper()

    async def MaterializeAllEnabledTenants(self) -> dict[str, int]:
        async with GetAsyncSession() as session:
            await self._ensure_rule_tenant_schema(session)
            await self._promote_legacy_provincial_templates_to_public(session)
            tenants = await self._load_enabled_tenants(session)
            tenant_codes = self._filter_materializable_tenants(tenants)
            stats = await self._materialize_tenants(session, tenant_codes)
            await session.commit()
            return stats

    async def MaterializeTenant(self, TenantCode: str) -> dict[str, int]:
        tenant_code = str(TenantCode or "").strip().upper()
        if not tenant_code or tenant_code in self._PLATFORM_TEMPLATE_TENANTS:
            return {"tenants": 0, "rule_sets": 0, "versions": 0, "bindings": 0}
        async with GetAsyncSession() as session:
            await self._ensure_rule_tenant_schema(session)
            await self._promote_legacy_provincial_templates_to_public(session)
            stats = await self._materialize_tenants(session, [tenant_code])
            await session.commit()
            return stats

    async def _ensure_rule_tenant_schema(self, session) -> None:
        required = {
            "leaudit_rule_sets": {"tenant_code", "scope_type", "source_rule_set_id", "tenant_name_snapshot"},
            "leaudit_rule_versions": {"tenant_code_snapshot", "scope_type_snapshot", "source_version_id"},
            "leaudit_rule_group_bindings": {"tenant_code", "scope_type", "tenant_name_snapshot"},
        }
        rows = (
            await session.execute(
                text(
                    """
                    SELECT table_name, column_name
                    FROM information_schema.columns
                    WHERE table_schema = current_schema()
                      AND table_name = ANY(:table_names)
                    """
                ),
                {"table_names": list(required)},
            )
        ).mappings().all()
        existing: dict[str, set[str]] = {}
        for row in rows:
            existing.setdefault(str(row["table_name"]), set()).add(str(row["column_name"]))
        missing = [
            f"{table}.{column}"
            for table, columns in required.items()
            for column in sorted(columns - existing.get(table, set()))
        ]
        if missing:
            raise RuntimeError("规则域租户字段未就绪: " + ", ".join(missing))

    async def _load_enabled_tenants(self, session) -> list[dict[str, Any]]:
        rows = (
            await session.execute(
                text(
                    """
                    SELECT tenant_code, is_enabled
                    FROM sys_tenants
                    WHERE deleted_at IS NULL
                      AND is_enabled = TRUE
                    ORDER BY display_order ASC, id ASC
                    """
                )
            )
        ).mappings().all()
        return [dict(row) for row in rows]

    async def _promote_legacy_provincial_templates_to_public(self, session) -> None:
        """把历史 PROVINCIAL 模板归并成 PUBLIC 模板源。"""
        await session.execute(
            text(
                """
                UPDATE leaudit_rule_sets
                SET tenant_code = 'PUBLIC',
                    scope_type = 'PUBLIC',
                    region = 'PUBLIC',
                    updated_at = NOW()
                WHERE deleted_at IS NULL
                  AND COALESCE(NULLIF(BTRIM(tenant_code), ''), 'PROVINCIAL') = 'PROVINCIAL'
                  AND NOT EXISTS (
                      SELECT 1
                      FROM leaudit_rule_sets existing
                      WHERE existing.rule_type = leaudit_rule_sets.rule_type
                        AND existing.tenant_code = 'PUBLIC'
                        AND existing.deleted_at IS NULL
                  )
                """
            )
        )
        await session.execute(
            text(
                """
                UPDATE leaudit_rule_versions rv
                SET tenant_code_snapshot = 'PUBLIC',
                    scope_type_snapshot = 'PUBLIC',
                    updated_at = NOW()
                FROM leaudit_rule_sets rs
                WHERE rv.rule_set_id = rs.id
                  AND rs.tenant_code = 'PUBLIC'
                  AND (rv.tenant_code_snapshot IS NULL OR rv.tenant_code_snapshot = 'PROVINCIAL')
                """
            )
        )
        await session.execute(
            text(
                """
                UPDATE leaudit_rule_group_bindings rgb
                SET tenant_code = 'PUBLIC',
                    scope_type = 'PUBLIC',
                    updated_at = NOW()
                FROM leaudit_rule_sets rs
                WHERE rgb.rule_set_id = rs.id
                  AND rs.tenant_code = 'PUBLIC'
                  AND rgb.deleted_at IS NULL
                  AND COALESCE(NULLIF(BTRIM(rgb.tenant_code), ''), 'PROVINCIAL') = 'PROVINCIAL'
                """
            )
        )

    async def _materialize_tenants(self, session, tenant_codes: list[str]) -> dict[str, int]:
        stats = {"tenants": len(tenant_codes), "rule_sets": 0, "versions": 0, "bindings": 0}
        for tenant_code in tenant_codes:
            stats["rule_sets"] += await self._materialize_rule_sets(session, tenant_code)
            stats["versions"] += await self._materialize_versions(session, tenant_code)
            stats["bindings"] += await self._materialize_bindings(session, tenant_code)
        return stats

    async def _materialize_rule_sets(self, session, tenant_code: str) -> int:
        result = await session.execute(
            text(
                """
                INSERT INTO leaudit_rule_sets (
                    rule_type, rule_name, domain_type, description, entry_module,
                    current_version_id, status, is_builtin, owner_user_id,
                    tenant_code, scope_type, source_rule_set_id, tenant_name_snapshot,
                    created_at, updated_at, deleted_at, region
                )
                SELECT
                    src.rule_type, src.rule_name, src.domain_type, src.description, src.entry_module,
                    NULL, 'draft', FALSE, src.owner_user_id,
                    CAST(:tenant_code AS varchar), 'TENANT', src.id, t.tenant_name,
                    NOW(), NOW(), NULL, CAST(:tenant_code AS varchar)
                FROM leaudit_rule_sets src
                JOIN sys_tenants t ON t.tenant_code = CAST(:tenant_code AS varchar)
                WHERE src.deleted_at IS NULL
                  AND src.tenant_code = 'PUBLIC'
                  AND NOT EXISTS (
                      SELECT 1
                      FROM leaudit_rule_sets existing
                      WHERE existing.rule_type = src.rule_type
                        AND existing.tenant_code = CAST(:tenant_code AS varchar)
                        AND existing.deleted_at IS NULL
                  )
                """
            ),
            {"tenant_code": tenant_code},
        )
        return int(result.rowcount or 0)

    async def _materialize_versions(self, session, tenant_code: str) -> int:
        result = await session.execute(
            text(
                """
                INSERT INTO leaudit_rule_versions (
                    rule_set_id, version_no, version_seq, status, source_type, dsl_format,
                    oss_url, file_sha256, file_size, local_cache_path,
                    metadata_type_id, metadata_name, metadata_version, change_note,
                    editor_user_id, publisher_user_id, published_at,
                    created_at, updated_at, deleted_at,
                    tenant_code_snapshot, scope_type_snapshot, source_version_id
                )
                SELECT
                    tenant_rs.id, src_v.version_no, src_v.version_seq, src_v.status,
                    src_v.source_type, src_v.dsl_format, src_v.oss_url, src_v.file_sha256,
                    src_v.file_size, src_v.local_cache_path, src_v.metadata_type_id,
                    src_v.metadata_name, src_v.metadata_version, src_v.change_note,
                    src_v.editor_user_id, src_v.publisher_user_id, src_v.published_at,
                    NOW(), NOW(), NULL,
                    CAST(:tenant_code AS varchar), 'TENANT', src_v.id
                FROM leaudit_rule_sets tenant_rs
                JOIN leaudit_rule_sets src_rs ON src_rs.id = tenant_rs.source_rule_set_id
                JOIN leaudit_rule_versions src_v ON src_v.rule_set_id = src_rs.id
                WHERE tenant_rs.deleted_at IS NULL
                  AND tenant_rs.tenant_code = CAST(:tenant_code AS varchar)
                  AND src_rs.tenant_code = 'PUBLIC'
                  AND src_v.deleted_at IS NULL
                  AND NOT EXISTS (
                      SELECT 1
                      FROM leaudit_rule_versions tenant_existing
                      WHERE tenant_existing.rule_set_id = tenant_rs.id
                        AND tenant_existing.deleted_at IS NULL
                  )
                  AND NOT EXISTS (
                      SELECT 1
                      FROM leaudit_rule_versions existing
                      WHERE existing.rule_set_id = tenant_rs.id
                        AND existing.deleted_at IS NULL
                        AND (
                            existing.source_version_id = src_v.id
                            OR existing.version_no = src_v.version_no
                            OR existing.version_seq = src_v.version_seq
                        )
                  )
                """
            ),
            {"tenant_code": tenant_code},
        )
        await session.execute(
            text(
                """
                UPDATE leaudit_rule_sets tenant_rs
                SET current_version_id = tenant_v.id,
                    status = 'active',
                    updated_at = NOW()
                FROM leaudit_rule_sets src_rs
                JOIN leaudit_rule_versions src_current ON src_current.id = src_rs.current_version_id
                JOIN leaudit_rule_versions tenant_v ON tenant_v.source_version_id = src_current.id
                WHERE tenant_rs.source_rule_set_id = src_rs.id
                  AND tenant_rs.tenant_code = CAST(:tenant_code AS varchar)
                  AND tenant_v.rule_set_id = tenant_rs.id
                  AND tenant_rs.current_version_id IS NULL
                """
            ),
            {"tenant_code": tenant_code},
        )
        return int(result.rowcount or 0)

    async def _materialize_bindings(self, session, tenant_code: str) -> int:
        result = await session.execute(
            text(
                """
                INSERT INTO leaudit_rule_group_bindings (
                    group_id, rule_set_id, rule_type_binding_id, priority, is_active, note,
                    tenant_code, scope_type, tenant_name_snapshot,
                    created_at, updated_at, deleted_at
                )
                SELECT
                    src_b.group_id, tenant_rs.id, src_b.rule_type_binding_id, src_b.priority,
                    src_b.is_active, '由公共规则模板自动物化',
                    CAST(:tenant_code AS varchar), 'TENANT', t.tenant_name,
                    NOW(), NOW(), NULL
                FROM leaudit_rule_group_bindings src_b
                JOIN leaudit_rule_sets src_rs ON src_rs.id = src_b.rule_set_id
                JOIN leaudit_rule_sets tenant_rs
                  ON tenant_rs.source_rule_set_id = src_rs.id
                 AND tenant_rs.tenant_code = CAST(:tenant_code AS varchar)
                 AND tenant_rs.deleted_at IS NULL
                JOIN sys_tenants t ON t.tenant_code = CAST(:tenant_code AS varchar)
                WHERE src_b.deleted_at IS NULL
                  AND src_b.is_active = TRUE
                  AND src_rs.tenant_code = 'PUBLIC'
                  AND NOT EXISTS (
                      SELECT 1
                      FROM leaudit_rule_group_bindings existing
                      WHERE existing.group_id = src_b.group_id
                        AND existing.tenant_code = CAST(:tenant_code AS varchar)
                        AND existing.rule_set_id = tenant_rs.id
                        AND existing.deleted_at IS NULL
                  )
                """
            ),
            {"tenant_code": tenant_code},
        )
        return int(result.rowcount or 0)


_RULE_TENANT_MATERIALIZER_SINGLETON: RuleTenantMaterializer | None = None


def GetRuleTenantMaterializerSingleton() -> RuleTenantMaterializer:
    global _RULE_TENANT_MATERIALIZER_SINGLETON
    if _RULE_TENANT_MATERIALIZER_SINGLETON is None:
        _RULE_TENANT_MATERIALIZER_SINGLETON = RuleTenantMaterializer()
    return _RULE_TENANT_MATERIALIZER_SINGLETON
