"""AI 日复盘 / 周复盘:附图收集与 journal 文本格式化(三所共用)."""
from __future__ import annotations

import os
import uuid
from typing import Any, Callable, List, Mapping, Optional, Sequence

from lib.instance.journal_chart_lib import (
    JOURNAL_CHART_ANCHOR_CLOSE,
    JOURNAL_CHART_DEFAULT_LIMIT,
    JOURNAL_CHART_DEFAULT_TF1,
    JOURNAL_CHART_DEFAULT_TF2,
    normalize_chart_timeframe,
)
from lib.instance.journal_images_lib import journal_image_paths


def _journal_nz(v: Any, default: str = "无") -> str:
    if v is None:
        return default
    s = str(v).strip()
    return s if s else default


def _row_get(row: Any, key: str, default: Any = None) -> Any:
    """兼容 dict 与 sqlite3.Row(Row 无 .get 方法)."""
    if row is None:
        return default
    getter = getattr(row, "get", None)
    if callable(getter):
        return getter(key, default)
    try:
        keys = row.keys() if hasattr(row, "keys") else ()
        if key in keys:
            return row[key]
    except Exception:
        pass
    try:
        return row[key]
    except (KeyError, TypeError, IndexError):
        return default


def journal_row_lines_for_ai(
    idx: int,
    row: Any,
    *,
    include_hold_duration: bool = True,
) -> str:
    """把 journal 字段拼成给 AI 的文本;三所日复盘/周复盘共用."""
    lines = [
        (
            f"{idx}. {_journal_nz(_row_get(row, 'coin'))} {_journal_nz(_row_get(row, 'tf'))} "
            f"| 盈亏:{_journal_nz(_row_get(row, 'pnl'))}U "
            f"| 实际RR:{_journal_nz(_row_get(row, 'real_rr'))} "
            f"| 预期RR:{_journal_nz(_row_get(row, 'expect_rr'))}"
        ),
        f"   开仓逻辑:{_journal_nz(_row_get(row, 'entry_reason'))}",
        f"   平仓/离场(交易员自述):{_journal_nz(_row_get(row, 'exit_reason'))}",
    ]
    if include_hold_duration:
        lines.append(f"   持仓时长:{_journal_nz(_row_get(row, 'hold_duration'))}")
    ee_bits = [
        _journal_nz(_row_get(row, "early_exit")),
        _journal_nz(_row_get(row, "early_exit_reason")),
        _journal_nz(_row_get(row, "early_exit_trigger")),
        _journal_nz(_row_get(row, "early_exit_note")),
    ]
    if any(x != "无" for x in ee_bits):
        lines.append(
            "   提前离场记录:"
            f"{ee_bits[0]} | 原因:{ee_bits[1]} | 触发:{ee_bits[2]} | 备注:{ee_bits[3]}"
        )
    mood_bits = f"心态标签:{_journal_nz(_row_get(row, 'mood_issues'))}"
    mood_score = _row_get(row, "mood_score")
    if mood_score is not None:
        mood_bits += f" | 自评心态分:{mood_score}"
    lines.append(f"   {mood_bits}")
    if _journal_nz(_row_get(row, "post_breakeven_stare")) != "无":
        lines.append(f"   保本后盯盘:{_journal_nz(_row_get(row, 'post_breakeven_stare'))}")
    if _journal_nz(_row_get(row, "note")) != "无":
        lines.append(f"   备注:{_journal_nz(_row_get(row, 'note'))}")
    return "\n".join(lines) + "\n"


def collect_images_for_ai_review(
    rows: Sequence,
    upload_folder: str,
    *,
    build_chart_if_missing: Optional[Callable] = None,
) -> List[str]:
    """
    收集传给视觉模型的本地图片路径.
    - 优先 journal_entries.images_json / image 已存附图(含多周期手动上传);
    - 若无附图且提供 build_chart_if_missing,则临时生成 K 线图.
    """
    paths: List[str] = []
    seen = set()
    upload_folder = os.path.abspath(upload_folder or "")
    for row in rows or []:
        row_paths = journal_image_paths(row, upload_folder)
        if row_paths:
            for candidate in row_paths:
                if candidate not in seen:
                    seen.add(candidate)
                    paths.append(candidate)
            continue
        if build_chart_if_missing:
            try:
                candidate = build_chart_if_missing(row)
            except Exception:
                candidate = None
            if not candidate:
                continue
            candidate = os.path.abspath(candidate)
            if os.path.isfile(candidate) and candidate not in seen:
                seen.add(candidate)
                paths.append(candidate)
    return paths


def build_journal_ai_chart_path(
    row,
    upload_folder: str,
    *,
    order_chart_enabled: bool,
    normalize_exchange_symbol_fn: Callable[[str], str],
    generate_chart_fn: Callable,
    local_datetime_to_ms_fn: Callable[[str], Optional[int]],
    now_ts_ms_fn: Callable[[], int],
) -> Optional[str]:
    """无已存附图时,按复盘记录开平仓时间临时生成 K 线图路径."""
    if not order_chart_enabled:
        return None
    try:
        keys = row.keys() if hasattr(row, "keys") else []
    except Exception:
        return None
    coin = (row["coin"] if "coin" in keys else "") or ""
    coin = str(coin).strip()
    if not coin:
        return None
    try:
        symbol = normalize_exchange_symbol_fn(coin)
    except Exception:
        return None
    open_dt = row["open_datetime"] if "open_datetime" in keys else ""
    close_dt = row["close_datetime"] if "close_datetime" in keys else ""
    entry_ms = local_datetime_to_ms_fn(open_dt)
    exit_ms = local_datetime_to_ms_fn(close_dt)
    if not entry_ms:
        return None
    row_tf = row["tf"] if "tf" in keys else ""
    tf1 = normalize_chart_timeframe(row_tf) or JOURNAL_CHART_DEFAULT_TF1
    tf2 = JOURNAL_CHART_DEFAULT_TF2 if tf1 != JOURNAL_CHART_DEFAULT_TF2 else "1h"
    row_id = str(row["id"] if "id" in keys else "")[:8] or uuid.uuid4().hex[:8]
    marker = {
        "entry_ts_ms": entry_ms,
        "exit_ts_ms": exit_ms,
        "chart_anchor": JOURNAL_CHART_ANCHOR_CLOSE,
        "now_ts_ms": int(now_ts_ms_fn()),
    }
    fname = f"ai_rev_{row_id}_{uuid.uuid4().hex[:6]}.png"
    saved = generate_chart_fn(
        symbol,
        f"AI复盘 {coin}",
        timeframes=[tf1, tf2],
        limit=JOURNAL_CHART_DEFAULT_LIMIT,
        out_dir=upload_folder,
        filename=fname,
        marker_payload=marker,
        marker_timeframes={tf1, tf2},
        layout="vertical",
    )
    if not saved:
        return None
    path = os.path.join(upload_folder, saved)
    return path if os.path.isfile(path) else None
