"""分析库同步与受控指标查询。

模型只能选择预定义指标、维度和时间窗口。SQLAlchemy 查询由本模块根据
白名单编译，外部输入无法传入表名、列名、聚合表达式或原始 SQL。
"""

from collections.abc import Callable
from datetime import date, datetime, timedelta
from typing import Any

from sqlalchemy import ColumnElement, func, select
from sqlalchemy.orm import Session, sessionmaker

from zbt.core.errors import AppError
from zbt.infrastructure.mysql.analytics_models import (
    AnalyticsSyncOffsetRecord,
    DimDateRecord,
    DimProductRecord,
    DimSalespersonRecord,
    FactOrderRecord,
    FactPolicyRecord,
    MetricDefinitionRecord,
)
from zbt.infrastructure.mysql.core_models import (
    EnrollmentOrderRecord,
    OrderAttributionRecord,
    PolicyRecord,
    ProductRecord,
    ProductVersionRecord,
    SalespersonRecord,
)

SessionFactory = sessionmaker[Session]

METRICS = {
    "ORDER_COUNT": {
        "name": "订单量",
        "unit": "笔",
        "fact": "fact_orders",
        "aggregation": "COUNT",
    },
    "ORDER_PREMIUM_SUM": {
        "name": "订单保费",
        "unit": "元",
        "fact": "fact_orders",
        "aggregation": "SUM",
    },
    "ACTIVE_POLICY_COUNT": {
        "name": "有效保单量",
        "unit": "张",
        "fact": "fact_policies",
        "aggregation": "COUNT",
    },
    "ACTIVE_POLICY_PREMIUM_SUM": {
        "name": "有效保单保费",
        "unit": "元",
        "fact": "fact_policies",
        "aggregation": "SUM",
    },
}


class SqlAlchemyOperationalAnalytics:
    """把 Core 事实同步至 Analytics，并执行白名单聚合查询。

    Agent 只提交指标代码、分组维度和时间窗口。本类负责把这三个受控参数编译为
    SQLAlchemy 查询，因此模型无法输入表名、字段名或任意 SQL。
    """

    def __init__(
        self,
        core_factory: SessionFactory,
        analytics_factory: SessionFactory,
        clock: Callable[[], datetime],
    ) -> None:
        self._core_factory = core_factory
        self._analytics_factory = analytics_factory
        self._clock = clock

    def refresh(self) -> dict[str, int]:
        """把当前 Core 业务快照同步成适合趋势分析的维度表和事实表。"""

        with self._core_factory() as core:
            product_rows = core.execute(
                select(ProductVersionRecord, ProductRecord).join(
                    ProductRecord,
                    ProductRecord.id == ProductVersionRecord.product_id,
                )
            ).all()
            salespeople = list(core.scalars(select(SalespersonRecord)).all())
            orders = list(core.scalars(select(EnrollmentOrderRecord)).all())
            policies = list(core.scalars(select(PolicyRecord)).all())
            attributions: dict[str, str] = {
                row.order_id: row.salesperson_id
                for row in core.execute(
                    select(
                        OrderAttributionRecord.order_id,
                        OrderAttributionRecord.salesperson_id,
                    )
                )
            }

        # 分析库使用独立事务更新；查询失败不会反向修改 Core 业务数据。
        with self._analytics_factory.begin() as analytics:
            product_dimensions = {
                item.product_version_id: item
                for item in analytics.scalars(select(DimProductRecord)).all()
            }
            for version, product in product_rows:
                if version.id in product_dimensions:
                    product_dimension = product_dimensions[version.id]
                    product_dimension.product_code = product.product_code
                    product_dimension.product_name = product.name
                    product_dimension.version_no = version.version_no
                    continue
                product_dimension = DimProductRecord(
                    product_id=product.id,
                    product_version_id=version.id,
                    product_code=product.product_code,
                    product_name=product.name,
                    version_no=version.version_no,
                )
                analytics.add(product_dimension)
                analytics.flush()
                product_dimensions[version.id] = product_dimension

            salesperson_dimensions = {
                item.salesperson_id: item
                for item in analytics.scalars(select(DimSalespersonRecord)).all()
            }
            for salesperson in salespeople:
                if salesperson.id in salesperson_dimensions:
                    salesperson_dimension = salesperson_dimensions[salesperson.id]
                    salesperson_dimension.salesperson_code = salesperson.salesperson_code
                    salesperson_dimension.display_name = salesperson.name
                    continue
                salesperson_dimension = DimSalespersonRecord(
                    salesperson_id=salesperson.id,
                    salesperson_code=salesperson.salesperson_code,
                    display_name=salesperson.name,
                    organization_code="ZBT",
                )
                analytics.add(salesperson_dimension)
                analytics.flush()
                salesperson_dimensions[salesperson.id] = salesperson_dimension

            all_dates = {
                order.created_at.date() for order in orders
            } | {policy.issued_at.date() for policy in policies}
            for business_date in all_dates:
                analytics.merge(
                    DimDateRecord(
                        date_key=_date_key(business_date),
                        full_date=business_date,
                        year=business_date.year,
                        month=business_date.month,
                        day=business_date.day,
                    )
                )

            for order in orders:
                product = product_dimensions.get(order.product_version_id)
                if product is None:
                    continue
                salesperson_id = attributions.get(order.id)
                order_salesperson = salesperson_dimensions.get(salesperson_id or "")
                analytics.merge(
                    FactOrderRecord(
                        order_id=order.id,
                        date_key=_date_key(order.created_at.date()),
                        product_key=product.id,
                        salesperson_key=(
                            order_salesperson.id
                            if order_salesperson is not None
                            else None
                        ),
                        status=order.status,
                        premium_cents=order.amount_cents,
                        created_at=order.created_at,
                    )
                )

            for policy in policies:
                product = product_dimensions.get(policy.product_version_id)
                if product is None:
                    continue
                analytics.merge(
                    FactPolicyRecord(
                        policy_id=policy.id,
                        order_id=policy.order_id,
                        date_key=_date_key(policy.issued_at.date()),
                        product_key=product.id,
                        status=policy.status,
                        premium_cents=policy.premium_cents,
                    )
                )

            for code, config in METRICS.items():
                analytics.merge(
                    MetricDefinitionRecord(
                        metric_code=code,
                        name=str(config["name"]),
                        description=f"受控指标：{config['name']}",
                        unit=str(config["unit"]),
                        expression=f"{config['aggregation']}({config['fact']})",
                    )
                )
            analytics.merge(
                AnalyticsSyncOffsetRecord(
                    source_name="core_snapshot",
                    last_event_id=None,
                    last_occurred_at=self._clock(),
                    updated_at=self._clock(),
                )
            )

        return {
            "products": len(product_rows),
            "salespeople": len(salespeople),
            "orders": len(orders),
            "policies": len(policies),
        }

    def query(
        self,
        *,
        metric: str,
        group_by: str,
        days: int,
        admin_user_id: str | None = None,
    ) -> dict[str, Any]:
        """根据白名单参数生成聚合查询，并应用后台用户的数据范围。"""

        # 第一层安全边界：任何未登记的指标、维度或天数都直接拒绝。
        config = METRICS.get(metric)
        if config is None or group_by not in {"DAY", "PRODUCT"} or days not in {7, 30, 90}:
            raise AppError(
                "ANALYTICS_QUERY_NOT_ALLOWED",
                "只允许查询已登记的指标、维度和时间窗口",
                422,
            )
        salesperson_key = self._salesperson_key(admin_user_id)
        if admin_user_id is not None and salesperson_key is None:
            return self._empty_result(metric, group_by, days, config)

        since = self._clock().date() - timedelta(days=days - 1)
        is_order_metric = metric.startswith("ORDER_")
        fact = FactOrderRecord if is_order_metric else FactPolicyRecord
        label_column: Any
        if group_by == "DAY":
            label_column = DimDateRecord.full_date
            dimension_name = "dim_date"
        else:
            label_column = DimProductRecord.product_name
            dimension_name = "dim_product"
        value_column: ColumnElement[Any]
        if metric.endswith("_COUNT"):
            value_column = func.count(
                FactOrderRecord.order_id
                if is_order_metric
                else FactPolicyRecord.policy_id
            )
        else:
            cents_column = (
                FactOrderRecord.premium_cents
                if is_order_metric
                else FactPolicyRecord.premium_cents
            )
            value_column = func.coalesce(func.sum(cents_column), 0) / 100

        statement = (
            select(label_column.label("label"), value_column.label("value"))
            .select_from(fact)
            .join(DimDateRecord, DimDateRecord.date_key == fact.date_key)
            .join(DimProductRecord, DimProductRecord.id == fact.product_key)
            .where(fact.date_key >= _date_key(since))
        )
        if not is_order_metric:
            statement = statement.where(FactPolicyRecord.status == "ACTIVE")
        if salesperson_key is not None:
            if is_order_metric:
                statement = statement.where(
                    FactOrderRecord.salesperson_key == salesperson_key
                )
            else:
                statement = statement.join(
                    FactOrderRecord,
                    FactOrderRecord.order_id == FactPolicyRecord.order_id,
                ).where(FactOrderRecord.salesperson_key == salesperson_key)
        statement = statement.group_by(label_column).order_by(label_column)

        with self._analytics_factory() as session:
            records = session.execute(statement).all()
        rows: list[dict[str, str | float]] = []
        total = 0.0
        for record in records:
            value = float(record.value or 0)
            total += value
            rows.append(
                {
                    "label": (
                        record.label.isoformat()
                        if isinstance(record.label, date)
                        else str(record.label)
                    ),
                    "value": value,
                }
            )
        return {
            "metric": metric,
            "metric_name": config["name"],
            "unit": config["unit"],
            "group_by": group_by,
            "days": days,
            "rows": rows,
            "total": total,
            "query_plan": {
                "source": "analytics",
                "fact": config["fact"],
                "aggregation": config["aggregation"],
                "dimension": dimension_name,
                "filters": {
                    "since": since.isoformat(),
                    "active_policy_only": not is_order_metric,
                    "data_scope": "SELF" if admin_user_id else "ALL",
                },
                "read_only": True,
            },
        }

    def _salesperson_key(self, admin_user_id: str | None) -> int | None:
        if admin_user_id is None:
            return None
        with self._core_factory() as core:
            salesperson_id = core.scalar(
                select(SalespersonRecord.id).where(
                    SalespersonRecord.admin_user_id == admin_user_id
                )
            )
        if salesperson_id is None:
            return None
        with self._analytics_factory() as analytics:
            return analytics.scalar(
                select(DimSalespersonRecord.id).where(
                    DimSalespersonRecord.salesperson_id == salesperson_id
                )
            )

    @staticmethod
    def _empty_result(
        metric: str,
        group_by: str,
        days: int,
        config: dict[str, str],
    ) -> dict[str, Any]:
        return {
            "metric": metric,
            "metric_name": config["name"],
            "unit": config["unit"],
            "group_by": group_by,
            "days": days,
            "rows": [],
            "total": 0,
            "query_plan": {
                "source": "analytics",
                "fact": config["fact"],
                "aggregation": config["aggregation"],
                "dimension": (
                    "dim_date" if group_by == "DAY" else "dim_product"
                ),
                "filters": {"data_scope": "SELF"},
                "read_only": True,
            },
        }


def _date_key(value: date) -> int:
    return int(value.strftime("%Y%m%d"))
