"""开版暂存（明道云）查询 API。

需求：
- 默认按 -ctime 排序
- 支持按“内部字段名”的 query param 过滤（对前端隐藏 JSONField 实现细节）
- 支持 LimitOffset 分页
- 返回时把 raw / related 两个 JSONField 做“可读化/碾平”
"""

from __future__ import annotations

from typing import Any

from django.core.exceptions import FieldError
from django.core.cache import cache
from django.utils.decorators import method_decorator
from django.views.decorators.cache import cache_page
from django.db.models import F
from django.db.models import Q
from rest_framework import serializers, viewsets
from rest_framework.permissions import DjangoModelPermissions
from rest_framework.exceptions import ValidationError

from flower.viewsets import LimitedReadOnlyModelViewSet
from api_v1.models import MDYPlateOrderStaging
from flower.utils.mingdaoyun.mappings import plate_order_field_definitions, plate_order_type_map
from flower.utils.mingdaoyun.relations import flatten_row_by_field_definitions, normalize_mdy_value


_PLATE_ORDER_DESIGN_NO_CONTROL_ID = "62d52f4b8d2972284492dd0e"

_PUBLIC_RAW_FIELDS: set[str] = set(plate_order_type_map.keys()) - {"created_at", "rowid"}

# 允许的 lookup（尽量收敛能力，降低风险）
_ALLOWED_LOOKUPS: set[str] = {"exact", "icontains", "isnull"}


def _parse_bool(value: str | None) -> bool:
    s = (value or "").strip().lower()
    return s in {"1", "true", "yes", "y", "t"}


def _coerce_value_for_lookup(lookup: str, raw_value: str) -> Any:
    lookup = (lookup or "exact").lower()
    if lookup == "isnull":
        return _parse_bool(raw_value)
    return raw_value


class MDYPlateOrderStagingSerializer(serializers.ModelSerializer):
    """把 raw/related 输出为扁平的可读结构。"""

    design_no = serializers.SerializerMethodField()
    raw = serializers.SerializerMethodField()
    related = serializers.SerializerMethodField()

    class Meta:
        model = MDYPlateOrderStaging
        fields = [
            "id",
            "mdy_rowid",
            "ctime",
            "utime",
            "created_at",
            "updated_at",
            "design_no",
            "raw",
            "related",
        ]

    def get_design_no(self, obj: MDYPlateOrderStaging) -> Any:
        if isinstance(obj.raw, dict):
            return obj.raw.get(_PLATE_ORDER_DESIGN_NO_CONTROL_ID)
        return None

    def get_raw(self, obj: MDYPlateOrderStaging) -> dict[str, Any]:
        if not isinstance(obj.raw, dict):
            return {}
        # 1) 已知字段：按映射转换为中文 key（更可读）
        out = flatten_row_by_field_definitions(obj.raw, plate_order_field_definitions, include_system_keys=True)

        # 2) 未映射字段：为了排查方便，不静默丢弃，保留 controlId 原样
        known_control_ids = set(plate_order_field_definitions.keys())
        for k, v in obj.raw.items():
            if k in {"rowid", "ctime", "utime"}:
                continue
            if k in known_control_ids:
                continue
            out[str(k)] = normalize_mdy_value(v)

        return out

    def get_related(self, obj: MDYPlateOrderStaging) -> list[dict[str, Any]]:
        if not obj.related:
            return []
        if not isinstance(obj.related, list):
            return []

        out: list[dict[str, Any]] = []
        for item in obj.related:
            if isinstance(item, dict):
                out.append({k: normalize_mdy_value(v) for k, v in item.items()})
            else:
                # 兜底：若数据被写成非 dict 元素，仍然返回原值，便于排查
                out.append({"value": item})
        return out


@method_decorator(cache_page(10), name='list')
class MDYPlateOrderStagingViewSet(LimitedReadOnlyModelViewSet):
    """开版暂存数据查询（只读）。"""

    serializer_class = MDYPlateOrderStagingSerializer
    # permission_classes = [DjangoModelPermissions]

    def get_queryset(self):
        # 默认 -ctime；ctime 为空的放最后，避免老数据（未回填）跑到前面
        return MDYPlateOrderStaging.objects.all().order_by(F("ctime").desc(nulls_last=True), F("id").desc())

    def filter_queryset(self, queryset):
        qs = super().filter_queryset(queryset)

        # 对前端暴露的过滤参数：只允许使用 “内部字段名”，像普通 query param 一样传递：
        # - <field>=...
        # - <field>__icontains=...
        # - <field>__isnull=true/false
        #
        # 额外支持：
        # - mdy_rowid=...（精确匹配模型字段）
        # - related_source=...（按关联表来源筛选，隐藏 related__contains 实现）
        #
        # 安全策略：
        # - 禁止直接使用 raw__/related__ 前缀（避免绕过白名单）
        # - 未在白名单内的过滤参数一律 400
        errors: dict[str, str] = {}

        for key, values in self.request.query_params.lists():
            if key in {"limit", "offset", "format"}:
                continue
            if not values:
                continue

            # 明确禁止内部实现细节参数（即使存在也不处理）
            if key.startswith("raw__") or key.startswith("related__"):
                errors[key] = "not allowed"
                continue

            # related_source：对外的“关联来源”过滤（等价于 related__contains=[{"source": "..."}]）
            if key == "related_source":
                q = Q()
                for raw_value in values:
                    if raw_value is None or str(raw_value).strip() == "":
                        continue
                    q |= Q(related__contains=[{"source": str(raw_value)}])
                if q:
                    qs = qs.filter(q)
                continue

            # mdy_rowid：模型字段过滤
            if key == "mdy_rowid":
                q = Q()
                for raw_value in values:
                    q |= Q(mdy_rowid=str(raw_value))
                qs = qs.filter(q)
                continue

            # raw 字段过滤：仅允许 internal_field（plate_order_type_map 的 key）
            parts = key.split("__")
            base = parts[0]
            if len(parts) == 1:
                lookup = "exact"
            elif len(parts) == 2:
                lookup = parts[1]
            else:
                errors[key] = "unsupported lookup"
                continue

            lookup = (lookup or "exact").lower()
            if lookup not in _ALLOWED_LOOKUPS:
                errors[key] = "unsupported lookup"
                continue

            if base not in _PUBLIC_RAW_FIELDS:
                errors[key] = "unsupported field"
                continue

            control_id = plate_order_type_map.get(base)
            if not control_id:
                errors[key] = "unsupported field"
                continue

            mapped_key = f"raw__{control_id}"
            if lookup != "exact":
                mapped_key += f"__{lookup}"

            q = Q()
            for raw_value in values:
                coerced = _coerce_value_for_lookup(lookup, raw_value)
                q |= Q(**{mapped_key: coerced})

            try:
                qs = qs.filter(q)
            except FieldError as exc:
                errors[key] = str(exc)

        if errors:
            raise ValidationError({"detail": "invalid filters", "filters": errors})

        return qs
