# -*- coding: utf-8 -*-
"""Reference attachments the user adds for narration context.

Each attachment is {type, value, summary}:
  image  -> described once via the user's AI CLI (needs ai_cmd configured)
  url    -> fetched and reduced to plain text (static pages only — anything
            the page loads with JavaScript after the fact is invisible here)
  file   -> .txt/.md/.csv read directly; .docx via python-docx

`summary` is the extracted text, cached on the attachment itself so the
extraction cost (AI call / network fetch) is paid once, not on every
analyze run.
"""
import re
import urllib.request
from pathlib import Path

from . import ai_cli

MAX_CHARS = 4000

IMAGE_PROMPT = (
    "請用繁體中文完整描述這張圖片的內容。如果是表格或排行榜，把每一列的"
    "文字與數字都列出來；如果是照片，描述場景與重點。這會作為撰寫影片旁白"
    "的參考資料。"
)


LOGIN_MARKERS = [
    "log in to see", "please log in", "sign in to continue",
    "登入後才能", "請先登入", "登入以檢視", "需要登入",
]


def _looks_like_login_wall(text: str) -> bool:
    low = text.lower()
    return any(m in low for m in LOGIN_MARKERS)


def extract(att: dict, log=print) -> str:
    kind = att.get("type")
    value = str(att.get("value", "")).strip()
    if not value:
        raise ValueError("附件路徑/網址是空的")

    if kind == "image":
        if not ai_cli.ai_available():
            raise RuntimeError("圖檔附件需要先設定 AI CLI 才能讀取內容")
        if not Path(value).is_file():
            raise FileNotFoundError(f"找不到圖檔: {value}")
        return ai_cli.ask(IMAGE_PROMPT, image=value, timeout=120)[:MAX_CHARS]

    if kind == "url":
        req = urllib.request.Request(value, headers={"User-Agent": "Mozilla/5.0"})
        raw = urllib.request.urlopen(req, timeout=30).read()
        html = raw.decode("utf-8", errors="replace")
        text = re.sub(r"<(script|style)[^>]*>.*?</\1>", " ", html,
                      flags=re.S | re.I)
        text = re.sub(r"<[^>]+>", " ", text)
        text = re.sub(r"\s+", " ", text).strip()
        if not text:
            raise ValueError("網頁抓不到任何文字內容")
        # A login wall answers with HTTP 200 and plenty of text, so "the
        # fetch worked" proves nothing — Strava activity pages return
        # 'Log in to see ...' to anonymous requests. Feeding that to the
        # narration prompt is worse than having no attachment at all.
        if _looks_like_login_wall(text):
            raise ValueError(
                "這個網址需要登入才看得到內容，抓回來的只是登入頁面。"
                "請改用截圖當附件（圖檔類型），或貼上不需登入的公開頁面。")
        return text[:MAX_CHARS]

    if kind == "file":
        p = Path(value)
        if not p.is_file():
            raise FileNotFoundError(f"找不到檔案: {value}")
        if p.suffix.lower() == ".docx":
            import docx
            doc = docx.Document(str(p))
            text = "\n".join(par.text for par in doc.paragraphs if par.text.strip())
        else:
            text = p.read_text(encoding="utf-8", errors="replace")
        if not text.strip():
            raise ValueError("檔案內容是空的")
        return text[:MAX_CHARS]

    raise ValueError(f"不認識的附件類型: {kind}")


def extract_all(attachments: list, log=print, on_each=None) -> list:
    """Fill in missing summaries in place; already-summarised entries are
    skipped (cache). on_each(attachments) persists after every completion."""
    for att in attachments:
        if att.get("summary"):
            continue
        label = f"{att.get('type')}:{str(att.get('value'))[:50]}"
        try:
            att["summary"] = extract(att, log)
            log(f"  附件擷取完成 [{label}] {len(att['summary'])} 字")
        except Exception as e:  # noqa: BLE001
            att["summary"] = None
            att["error"] = str(e)[:300]
            log(f"  附件擷取失敗 [{label}]: {e}")
        if on_each:
            on_each(attachments)
    return attachments


def as_prompt_text(attachments: list) -> str:
    parts = []
    for i, att in enumerate(attachments, 1):
        if att.get("summary"):
            parts.append(f"--- 附件{i} ({att.get('type')}: "
                         f"{str(att.get('value'))[:80]}) ---\n{att['summary']}")
    return "\n\n".join(parts)
