# ruff: noqa: E501

from __future__ import annotations

import html
from dataclasses import dataclass
from typing import Any


@dataclass(frozen=True)
class RiskRuleFlowDiagramField:
    key: str
    label: str


@dataclass(frozen=True)
class RiskRuleFlowDiagramSpec:
    title: str
    domain_label: str
    severity: str
    severity_label: str
    fields: tuple[RiskRuleFlowDiagramField, ...]
    start: str
    evidence: str
    decision: str
    basis: str
    pass_text: str
    fail_text: str
    fact_lines: tuple[str, ...] = ()
    condition_lines: tuple[str, ...] = ()
    hit_logic: str = ""


@dataclass(frozen=True)
class RiskRuleFlowDiagramPalette:
    accent: str
    accent_dark: str
    border: str
    surface: str


class RiskRuleFlowDiagramRenderer:
    """按 fireworks-tech-graph Style 7 OpenAI Official 生成只读流程 SVG。"""

    _FONT = (
        "-apple-system, BlinkMacSystemFont, 'Segoe UI', Roboto, Helvetica Neue, "
        "'PingFang SC', 'Microsoft YaHei', 'Microsoft JhengHei', 'SimHei', sans-serif"
    )
    _TEXT = "#0d0d0d"
    _MUTED = "#6e6e80"
    _NEUTRAL_LINE = "#cbd5e1"
    _NEUTRAL_BORDER = "#e2e8f0"
    _NEUTRAL_SURFACE = "#ffffff"
    _PALETTES = {
        "low": RiskRuleFlowDiagramPalette(
            accent="#2563eb",
            accent_dark="#1d4ed8",
            border="#bfdbfe",
            surface="#eff6ff",
        ),
        "medium": RiskRuleFlowDiagramPalette(
            accent="#f97316",
            accent_dark="#c2410c",
            border="#fed7aa",
            surface="#fff7ed",
        ),
        "high": RiskRuleFlowDiagramPalette(
            accent="#dc2626",
            accent_dark="#b91c1c",
            border="#fecaca",
            surface="#fef2f2",
        ),
        "critical": RiskRuleFlowDiagramPalette(
            accent="#991b1b",
            accent_dark="#7f1d1d",
            border="#fca5a5",
            surface="#fff1f2",
        ),
    }

    def render(self, spec: RiskRuleFlowDiagramSpec) -> str:
        title = self._truncate(spec.title, 26)
        palette = self._palette(spec.severity)
        fact_lines = spec.fact_lines or self._field_lines(spec.fields)
        condition_lines = spec.condition_lines or (spec.basis,)
        hit_logic = spec.hit_logic or spec.basis

        return f"""<svg xmlns="http://www.w3.org/2000/svg" width="860" height="360" viewBox="0 0 860 360" data-risk-flow-style="review-node-only" data-risk-flow-detail="logic-v2" role="img" aria-labelledby="risk-flow-title risk-flow-desc">
  <title id="risk-flow-title">{self._escape(title)}流程说明</title>
  <desc id="risk-flow-desc">风险规则只读流程图，展示字段事实、集合交集、日期范围、例外说明和命中路径。</desc>
  <defs>
    <marker id="arrow-neutral" markerWidth="10" markerHeight="7" refX="9" refY="3.5" orient="auto">
      <polygon points="0 0, 10 3.5, 0 7" fill="{self._NEUTRAL_LINE}"/>
    </marker>
    <marker id="arrow-risk" markerWidth="10" markerHeight="7" refX="9" refY="3.5" orient="auto">
      <polygon points="0 0, 10 3.5, 0 7" fill="{palette.accent}"/>
    </marker>
  </defs>
  <rect width="860" height="360" fill="#ffffff"/>
  <rect x="18" y="18" width="824" height="324" rx="8" ry="8" fill="none" stroke="{self._NEUTRAL_BORDER}" stroke-width="1" stroke-dasharray="4,3"/>
  <text x="34" y="43" fill="{self._MUTED}" font-family="{self._FONT}" font-size="11" font-weight="500">RULE FLOW</text>
  {self._node("业务输入", spec.start, 38, 142, 120, 62)}
  {self._panel("字段事实", fact_lines, 196, 64, 240, 128)}
  {self._panel("判断条件", condition_lines, 196, 216, 382, 104)}
  {self._diamond("命中逻辑", hit_logic, 494, 80, 122, 122)}
  {self._node("继续流转", spec.pass_text, 688, 76, 122, 60)}
  {self._node("进入复核", spec.fail_text, 688, 226, 122, 68, palette=palette)}
  <path d="M 158 173 H 176 V 128 H 196" fill="none" stroke="{self._NEUTRAL_LINE}" stroke-width="1.45" stroke-linecap="round" stroke-linejoin="round" marker-end="url(#arrow-neutral)"/>
  <line x1="316" y1="192" x2="316" y2="216" stroke="{self._NEUTRAL_LINE}" stroke-width="1.45" stroke-linecap="round" marker-end="url(#arrow-neutral)"/>
  <path d="M 436 128 H 466 V 141 H 494" fill="none" stroke="{self._NEUTRAL_LINE}" stroke-width="1.45" stroke-linecap="round" stroke-linejoin="round" marker-end="url(#arrow-neutral)"/>
  <line x1="555" y1="216" x2="555" y2="202" stroke="{self._NEUTRAL_LINE}" stroke-width="1.35" stroke-linecap="round" marker-end="url(#arrow-neutral)"/>
  <path d="M 616 125 H 648 V 106 H 688" fill="none" stroke="{self._NEUTRAL_LINE}" stroke-width="1.35" stroke-linecap="round" stroke-linejoin="round" marker-end="url(#arrow-neutral)"/>
  <text x="651" y="119" text-anchor="middle" fill="{self._MUTED}" font-family="{self._FONT}" font-size="10.5" font-weight="500">否</text>
  <path d="M 616 166 H 648 V 260 H 688" fill="none" stroke="{palette.accent}" stroke-width="1.8" stroke-linecap="round" stroke-linejoin="round" marker-end="url(#arrow-risk)"/>
  <text x="651" y="214" text-anchor="middle" fill="{palette.accent_dark}" font-family="{self._FONT}" font-size="10.5" font-weight="700">是</text>
</svg>"""

    def _node(
        self,
        title: str,
        body: str,
        x: int,
        y: int,
        width: int,
        height: int,
        palette: RiskRuleFlowDiagramPalette | None = None,
    ) -> str:
        body_lines = self._wrap(body, 10 if width <= 126 else 11, 1)
        border = palette.border if palette else self._NEUTRAL_BORDER
        stripe = palette.accent if palette else self._NEUTRAL_LINE
        surface = palette.surface if palette else self._NEUTRAL_SURFACE
        return f"""<g>
    <rect x="{x}" y="{y}" width="{width}" height="{height}" rx="7" ry="7" fill="{surface}" stroke="{border}" stroke-width="1.2"/>
    <rect x="{x}" y="{y}" width="3.5" height="{height}" rx="1.75" ry="1.75" fill="{stripe}"/>
    <text x="{x + width / 2:.0f}" y="{y + 24}" text-anchor="middle" fill="{self._TEXT}" font-family="{self._FONT}" font-size="13" font-weight="600">{self._escape(title)}</text>
    {self._text_lines(body_lines, x + width / 2, y + 43, "middle", self._MUTED, 11)}
  </g>"""

    def _diamond(
        self,
        title: str,
        body: str,
        x: int,
        y: int,
        width: int,
        height: int,
    ) -> str:
        cx = x + width / 2
        cy = y + height / 2
        points = f"{cx},{y} {x + width},{cy} {cx},{y + height} {x},{cy}"
        body_lines = self._wrap(body, 8, 2)
        return f"""<g>
    <polygon points="{points}" fill="#ffffff" stroke="{self._NEUTRAL_BORDER}" stroke-width="1.25"/>
    <text x="{cx:.0f}" y="{cy - 10:.0f}" text-anchor="middle" fill="{self._TEXT}" font-family="{self._FONT}" font-size="12.5" font-weight="600">{self._escape(title)}</text>
    {self._text_lines(body_lines, cx, cy + 11, "middle", self._MUTED, 10.2)}
  </g>"""

    def _panel(
        self,
        title: str,
        lines: tuple[str, ...],
        x: int,
        y: int,
        width: int,
        height: int,
    ) -> str:
        visible = [self._truncate(line, 36) for line in list(lines)[:4]]
        if not visible:
            visible = ["读取规则字段并归一化为判断事实"]
        rows = "\n    ".join(
            f'<text x="{x + 16}" y="{y + 48 + index * 18}" fill="{self._TEXT}" font-family="{self._FONT}" font-size="11" font-weight="400">{self._escape(line)}</text>'
            for index, line in enumerate(visible)
        )
        return f"""<g>
    <rect x="{x}" y="{y}" width="{width}" height="{height}" rx="7" ry="7" fill="#ffffff" stroke="{self._NEUTRAL_BORDER}" stroke-width="1.2"/>
    <text x="{x + 16}" y="{y + 26}" fill="{self._TEXT}" font-family="{self._FONT}" font-size="13" font-weight="650">{self._escape(title)}</text>
    {rows}
  </g>"""

    def _note(
        self,
        body: str,
        x: int,
        y: int,
        width: int,
        height: int,
    ) -> str:
        lines = self._wrap(body, 22, 1)
        return f"""<g>
    <rect x="{x}" y="{y}" width="{width}" height="{height}" rx="7" ry="7" fill="#ffffff" stroke="{self._NEUTRAL_BORDER}" stroke-width="1" stroke-dasharray="4,3"/>
    <text x="{x + 12}" y="{y + 22}" fill="{self._MUTED}" font-family="{self._FONT}" font-size="10" font-weight="500">BASIS</text>
    {self._text_lines(lines, x + 54, y + 22, "start", self._TEXT, 10.2)}
  </g>"""

    def _field_lines(self, fields: tuple[RiskRuleFlowDiagramField, ...]) -> tuple[str, ...]:
        rows = []
        for index, field in enumerate(fields[:4]):
            label = field.label or field.key
            rows.append(f"{chr(65 + index)}={label}[{field.key}]")
        return tuple(rows)

    def _text_lines(
        self,
        lines: list[str],
        x: float,
        y: float,
        anchor: str,
        color: str,
        font_size: float,
    ) -> str:
        return "\n    ".join(
            f'<text x="{x:.0f}" y="{y + index * (font_size + 5):.0f}" text-anchor="{anchor}" fill="{color}" font-family="{self._FONT}" font-size="{font_size}" font-weight="400">{self._escape(line)}</text>'
            for index, line in enumerate(lines)
        )

    @staticmethod
    def _wrap(value: str, width: int, max_lines: int) -> list[str]:
        text = str(value or "").strip()
        if not text:
            return [""]
        lines = [text[index : index + width] for index in range(0, len(text), width)]
        if len(lines) > max_lines:
            lines = lines[:max_lines]
            lines[-1] = f"{lines[-1][: max(0, width - 1)]}…"
        return lines

    @staticmethod
    def _truncate(value: str, length: int) -> str:
        text = str(value or "").strip()
        return text if len(text) <= length else f"{text[: length - 1]}…"

    @staticmethod
    def _escape(value: str) -> str:
        return html.escape(str(value or ""), quote=True)

    @classmethod
    def _palette(cls, severity: str) -> RiskRuleFlowDiagramPalette:
        return cls._PALETTES.get(str(severity or "").strip().lower(), cls._PALETTES["medium"])


def build_risk_rule_flow_diagram_details(
    payload: dict[str, Any],
    fields: list[RiskRuleFlowDiagramField],
) -> dict[str, tuple[str, ...] | str]:
    params = payload.get("params") if isinstance(payload.get("params"), dict) else {}
    rule_ir = params.get("rule_ir") if isinstance(params.get("rule_ir"), dict) else {}
    facts = rule_ir.get("facts") if isinstance(rule_ir.get("facts"), list) else []
    fact_lines = _build_fact_lines(facts, fields)
    condition_lines = _build_condition_lines(params, fields)
    hit_logic = _format_hit_logic(params.get("hit_logic")) or str(
        params.get("formula") or params.get("condition_summary") or ""
    ).strip()
    return {
        "fact_lines": tuple(fact_lines),
        "condition_lines": tuple(condition_lines),
        "hit_logic": hit_logic,
    }


def build_risk_rule_flow_diagram_spec(
    payload: dict[str, Any],
    *,
    fields: tuple[RiskRuleFlowDiagramField, ...],
    domain_label: str,
    severity: str,
    severity_label: str,
    flow_model: dict[str, Any] | None = None,
) -> RiskRuleFlowDiagramSpec:
    model_spec = _spec_from_flow_model(
        payload,
        fields=fields,
        domain_label=domain_label,
        severity=severity,
        severity_label=severity_label,
        flow_model=flow_model or {},
    )
    if model_spec:
        return model_spec
    metadata = payload.get("metadata") if isinstance(payload.get("metadata"), dict) else {}
    flow = metadata.get("flow") if isinstance(metadata.get("flow"), dict) else {}
    details = build_risk_rule_flow_diagram_details(payload, list(fields))
    summary = str(metadata.get("condition_summary") or "").strip()
    return RiskRuleFlowDiagramSpec(
        title=str(payload.get("name") or "").strip() or "风险规则判断流程",
        domain_label=domain_label,
        severity=severity,
        severity_label=severity_label,
        fields=fields,
        start=str(flow.get("start") or "").strip() or "业务单据提交",
        evidence=str(flow.get("evidence") or "").strip() or "读取规则字段",
        decision=str(flow.get("decision") or "").strip() or summary or "判断是否命中风险",
        basis=summary or str(flow.get("decision") or "").strip() or "根据规则字段判断",
        pass_text=str(flow.get("pass") or "").strip() or "未命中风险，继续流转",
        fail_text=str(flow.get("fail") or "").strip() or f"命中{severity_label}，进入人工复核",
        fact_lines=details["fact_lines"],
        condition_lines=details["condition_lines"],
        hit_logic=str(details["hit_logic"] or ""),
    )


def _spec_from_flow_model(
    payload: dict[str, Any],
    *,
    fields: tuple[RiskRuleFlowDiagramField, ...],
    domain_label: str,
    severity: str,
    severity_label: str,
    flow_model: dict[str, Any],
) -> RiskRuleFlowDiagramSpec | None:
    nodes = flow_model.get("nodes") if isinstance(flow_model, dict) else []
    if not isinstance(nodes, list) or not nodes:
        return None
    by_type: dict[str, list[dict[str, Any]]] = {}
    for node in nodes:
        if isinstance(node, dict):
            by_type.setdefault(str(node.get("type") or "").strip(), []).append(node)
    decisions = by_type.get("decision") or []
    if not decisions:
        return None
    start = _node_description(by_type.get("start"), "业务单据提交")
    evidence = _node_description(by_type.get("evidence"), "读取规则字段")
    pass_text = _node_description(by_type.get("pass"), "未命中风险，继续流转")
    fail_text = _node_description(by_type.get("risk"), f"命中{severity_label}，进入人工复核")
    condition_lines = _condition_lines_from_flow_nodes(decisions)
    basis = condition_lines[0] if condition_lines else _node_description(decisions, "判断是否命中风险")
    return RiskRuleFlowDiagramSpec(
        title=str(payload.get("name") or "").strip() or "风险规则判断流程",
        domain_label=domain_label,
        severity=severity,
        severity_label=severity_label,
        fields=fields,
        start=start,
        evidence=evidence,
        decision=_node_description(decisions, basis),
        basis=basis,
        pass_text=pass_text,
        fail_text=fail_text,
        fact_lines=tuple(_field_lines_from_flow_nodes(by_type.get("evidence"), fields)),
        condition_lines=tuple(condition_lines),
        hit_logic=_hit_logic_from_flow_model(flow_model, condition_lines),
    )


def _node_description(nodes: list[dict[str, Any]] | None, fallback: str) -> str:
    node = nodes[0] if nodes else {}
    return str(node.get("description") or node.get("title") or fallback).strip()


def _condition_lines_from_flow_nodes(nodes: list[dict[str, Any]]) -> list[str]:
    visible = [
        f"{str(node.get('title') or node.get('id') or '判断').strip()}: {str(node.get('description') or '').strip()}"
        for node in nodes[:4]
    ]
    if len(nodes) > 4:
        visible[-1] = f"{visible[-1]}；另有 {len(nodes) - 4} 个判断节点按命中逻辑汇总"
    return visible


def _field_lines_from_flow_nodes(
    nodes: list[dict[str, Any]] | None,
    fields: tuple[RiskRuleFlowDiagramField, ...],
) -> list[str]:
    field_keys = _read_string_list((nodes[0] if nodes else {}).get("fields"))
    if not field_keys:
        return [
            f"{chr(65 + index)}={field.label or field.key}[{field.key}]"
            for index, field in enumerate(fields[:4])
        ]
    label_by_key = {field.key: field.label or field.key for field in fields}
    return [
        f"{chr(65 + index)}={label_by_key.get(key, key)}[{key}]"
        for index, key in enumerate(field_keys[:4])
    ]


def _hit_logic_from_flow_model(flow_model: dict[str, Any], condition_lines: list[str]) -> str:
    metadata = flow_model.get("metadata") if isinstance(flow_model.get("metadata"), dict) else {}
    logic = str(metadata.get("hit_logic") or "").strip()
    if logic:
        return logic
    return " AND ".join(line.split(":", 1)[0] for line in condition_lines[:4] if line)


def _build_fact_lines(
    facts: list[Any],
    fields: list[RiskRuleFlowDiagramField],
) -> list[str]:
    label_by_key = {field.key: field.label or field.key for field in fields}
    rows: list[str] = []
    for fact in facts[:4]:
        if not isinstance(fact, dict):
            continue
        fact_id = str(fact.get("id") or "").strip()
        label = str(fact.get("label") or fact_id or "事实").strip()
        field_keys = _read_string_list(fact.get("fields"))
        field_text = "∪".join(label_by_key.get(key, key) for key in field_keys[:3])
        rows.append(f"{fact_id + '=' if fact_id else ''}{label}: {field_text or '规则字段'}")
    if rows:
        return rows
    return [
        f"{chr(65 + index)}={field.label or field.key}[{field.key}]"
        for index, field in enumerate(fields[:4])
    ]


def _build_condition_lines(
    params: dict[str, Any],
    fields: list[RiskRuleFlowDiagramField],
) -> list[str]:
    label_by_key = {field.key: field.label or field.key for field in fields}
    rows: list[str] = []
    conditions = params.get("conditions") if isinstance(params.get("conditions"), list) else []
    for index, condition in enumerate(conditions[:4], start=1):
        if not isinstance(condition, dict):
            continue
        rows.append(_format_condition(condition, label_by_key, index))
    if rows:
        return rows
    summary = str(params.get("condition_summary") or "").strip()
    return [summary] if summary else []


def _format_condition(condition: dict[str, Any], label_by_key: dict[str, str], index: int) -> str:
    operator = str(condition.get("operator") or "").strip()
    condition_id = str(condition.get("id") or f"C{index}").strip()
    prefix = f"{condition_id}: "
    if operator in {"not_in_scope", "not_in_set", "not_overlap"}:
        left = _field_group(condition.get("left_fields"), label_by_key)
        right = _field_group(condition.get("right_fields"), label_by_key)
        return f"{prefix}{left} ∩ {right} = ∅"
    if operator in {"in_scope", "overlap"}:
        left = _field_group(condition.get("left_fields"), label_by_key)
        right = _field_group(condition.get("right_fields"), label_by_key)
        return f"{prefix}{left} ∩ {right} ≠ ∅"
    if operator == "date_outside_range":
        dates = _field_group(condition.get("date_fields"), label_by_key)
        start = _field_group(condition.get("range_start_fields"), label_by_key)
        end = _field_group(condition.get("range_end_fields"), label_by_key)
        return f"{prefix}{dates} 不在 [{start}, {end}]"
    if operator == "numeric_compare":
        left = _field_group(condition.get("left_fields") or condition.get("fields"), label_by_key)
        right = _field_group(condition.get("right_fields"), label_by_key)
        compare = str(condition.get("compare") or "gt").strip().upper()
        target = right or str(condition.get("threshold") or condition.get("value") or "阈值").strip()
        return f"{prefix}{left} {compare} {target}"
    if operator == "duplicate_value":
        fields = _field_group(condition.get("fields"), label_by_key)
        return f"{prefix}{fields} 出现重复值"
    if operator in {"contains_any", "not_contains_any"}:
        fields = _field_group(condition.get("fields"), label_by_key)
        keywords = "、".join(_read_string_list(condition.get("keywords"))[:4])
        verb = "不含" if operator == "not_contains_any" else "包含"
        return f"{prefix}{fields} {verb} {keywords or '关键词'}"
    if operator in {"exists_any", "exists_all", "all_present"}:
        fields = _field_group(condition.get("fields"), label_by_key)
        verb = "任一有值" if operator == "exists_any" else "全部有值"
        return f"{prefix}{fields} {verb}"
    left = str(condition.get("left") or "").strip()
    right = str(condition.get("right") or "").strip()
    if left or right:
        return f"{prefix}{label_by_key.get(left, left)} {operator or 'compare'} {label_by_key.get(right, right)}"
    return f"{prefix}{operator or '规则条件'}"


def _field_group(value: Any, label_by_key: dict[str, str]) -> str:
    keys = _read_string_list(value)
    if not keys:
        return "字段集合"
    return "∪".join(label_by_key.get(key, key) for key in keys[:3])


def _format_hit_logic(value: Any) -> str:
    if isinstance(value, str):
        return value.strip()
    if isinstance(value, list):
        return " AND ".join(_format_hit_logic(item) for item in value if _format_hit_logic(item))
    if not isinstance(value, dict):
        return ""
    if isinstance(value.get("all"), list):
        return " AND ".join(_wrap_logic_part(item) for item in value["all"])
    if isinstance(value.get("any"), list):
        return " OR ".join(_wrap_logic_part(item) for item in value["any"])
    if "not" in value:
        return f"NOT {_wrap_logic_part(value.get('not'))}"
    return ""


def _wrap_logic_part(value: Any) -> str:
    text = _format_hit_logic(value)
    if isinstance(value, dict) and text:
        return f"({text})"
    return text


def _read_string_list(value: Any) -> list[str]:
    if not isinstance(value, list):
        return []
    return [str(item or "").strip() for item in value if str(item or "").strip()]
