#!/usr/bin/env python3
"""Generate the daily Yifangbao automation status package.

This script is intentionally dependency-free. It writes a small HTML report
and a valid OOXML workbook for the automation review surface.
"""

from __future__ import annotations

import html
import shutil
import zipfile
from datetime import datetime
from pathlib import Path
from typing import Any


WORKSPACE = Path(__file__).resolve().parents[1]
RUN_DATE = datetime.now().strftime("%Y-%m-%d")
RUN_DATE_COMPACT = RUN_DATE.replace("-", "")
RESULT_SLUG = f"{RUN_DATE_COMPACT}-yifangbao-daily-award-update"
RESULT_DIR = WORKSPACE / f"docs/final-results/{RESULT_SLUG}"
WORKBOOK_NAME = f"{RESULT_SLUG}.xlsx"
RUN_NOTE = WORKSPACE / "docs/runs/20260612-yifangbao-daily-award-automation.html"


RUN_TIME = datetime.now().strftime("%Y-%m-%d %H:%M CST")
BLOCK_REASON = "当前会话没有可调用的 in-app Browser 控制通道，无法复用用户已有乙方宝登录状态或取得乙方宝结果页。"


def sanitize_visible(value: Any) -> str:
    return str(value if value is not None else "").replace("—", "-").replace("–", "-")


def xml_escape(value: Any) -> str:
    text = sanitize_visible(value)
    return (
        text.replace("&", "&amp;")
        .replace("<", "&lt;")
        .replace(">", "&gt;")
        .replace('"', "&quot;")
    )


def col_letter(n: int) -> str:
    result = ""
    while n > 0:
        n, rem = divmod(n - 1, 26)
        result = chr(65 + rem) + result
    return result


def cell_xml(ref: str, value: Any, style: int = 0) -> str:
    if value is None or value == "":
        return f'<c r="{ref}" s="{style}"/>'
    return f'<c r="{ref}" t="inlineStr" s="{style}"><is><t>{xml_escape(value)}</t></is></c>'


def row_xml(row_num: int, values: list[Any], styles: list[int] | None = None) -> str:
    styles = styles or [0] * len(values)
    cells = []
    for idx, value in enumerate(values, start=1):
        cells.append(cell_xml(f"{col_letter(idx)}{row_num}", value, styles[idx - 1] if idx - 1 < len(styles) else 0))
    return f'<row r="{row_num}">{"".join(cells)}</row>'


def sheet_xml(headers: list[str], rows: list[list[Any]], widths: list[int] | None = None) -> str:
    widths = widths or [22] * len(headers)
    col_xml = "".join(
        f'<col min="{idx}" max="{idx}" width="{width}" customWidth="1"/>'
        for idx, width in enumerate(widths, start=1)
    )
    data_rows = [row_xml(1, headers, [4] * len(headers))]
    for row_idx, row in enumerate(rows, start=2):
        data_rows.append(row_xml(row_idx, row, [0] * len(headers)))
    end_col = col_letter(len(headers))
    end_row = max(len(rows) + 1, 1)
    return f"""<?xml version="1.0" encoding="UTF-8" standalone="yes"?>
<worksheet xmlns="http://schemas.openxmlformats.org/spreadsheetml/2006/main"
  xmlns:r="http://schemas.openxmlformats.org/officeDocument/2006/relationships">
  <dimension ref="A1:{end_col}{end_row}"/>
  <sheetViews><sheetView workbookViewId="0"><pane ySplit="1" topLeftCell="A2" activePane="bottomLeft" state="frozen"/></sheetView></sheetViews>
  <sheetFormatPr defaultRowHeight="20"/>
  <cols>{col_xml}</cols>
  <sheetData>{''.join(data_rows)}</sheetData>
  <autoFilter ref="A1:{end_col}{end_row}"/>
  <pageMargins left="0.7" right="0.7" top="0.75" bottom="0.75" header="0.3" footer="0.3"/>
</worksheet>
"""


def styles_xml() -> str:
    return """<?xml version="1.0" encoding="UTF-8" standalone="yes"?>
<styleSheet xmlns="http://schemas.openxmlformats.org/spreadsheetml/2006/main">
  <fonts count="2">
    <font><sz val="11"/><name val="Calibri"/></font>
    <font><b/><sz val="11"/><name val="Calibri"/><color rgb="00FFFFFF"/></font>
  </fonts>
  <fills count="3">
    <fill><patternFill patternType="none"/></fill>
    <fill><patternFill patternType="gray125"/></fill>
    <fill><patternFill patternType="solid"><fgColor rgb="002F5F53"/><bgColor indexed="64"/></patternFill></fill>
  </fills>
  <borders count="1"><border><left/><right/><top/><bottom/><diagonal/></border></borders>
  <cellStyleXfs count="1"><xf numFmtId="0" fontId="0" fillId="0" borderId="0"/></cellStyleXfs>
  <cellXfs count="5">
    <xf numFmtId="0" fontId="0" fillId="0" borderId="0" xfId="0"/>
    <xf numFmtId="0" fontId="0" fillId="0" borderId="0" xfId="0"/>
    <xf numFmtId="0" fontId="0" fillId="0" borderId="0" xfId="0"/>
    <xf numFmtId="0" fontId="0" fillId="0" borderId="0" xfId="0"/>
    <xf numFmtId="0" fontId="1" fillId="2" borderId="0" xfId="0" applyFont="1" applyFill="1"/>
  </cellXfs>
  <cellStyles count="1"><cellStyle name="Normal" xfId="0" builtinId="0"/></cellStyles>
</styleSheet>
"""


def write_workbook(output: Path) -> None:
    sheets = [
        (
            "运行摘要",
            ["项目", "内容"],
            [
                ["运行时间", RUN_TIME],
                ["自动化", "乙方宝中标项目每日更新"],
                ["状态", "阻塞 - 未能取得乙方宝页面数据"],
                ["原因", BLOCK_REASON],
                ["结果", "本次未整理新增或变化的中标项目。"],
            ],
            [24, 72],
        ),
        (
            "检索口径",
            ["条件", "本次要求"],
            [
                ["关键词", "智慧食堂"],
                ["栏目", "中标"],
                ["更多筛选", "中标通知"],
                ["地区", "全国"],
                ["发布时间", f"{RUN_DATE} 当天或适合增量更新的日期范围"],
                ["登录规则", "如需重新登录、验证码、密码或人工确认，停止并提示用户处理。"],
            ],
            [22, 72],
        ),
        (
            "项目变更清单",
            ["状态", "项目名称", "地区", "发布时间", "中标单位", "金额", "链接", "备注"],
            [["未检索", "", "", "", "", "", "", "访问阻塞，未取得乙方宝结果页。"]],
            [14, 34, 16, 16, 28, 16, 42, 42],
        ),
        (
            "下一步",
            ["优先级", "动作", "负责人", "说明"],
            [
                ["高", "在可访问外部站点的浏览器中保持乙方宝登录状态", "用户", "自动化需要复用已有登录态，不能处理验证码或密码。"],
                ["高", "重新运行自动化", "Codex", "登录和浏览器访问通道可用后，按同一检索口径生成新增项目清单。"],
                ["中", "保留本次阻塞包", "项目", "作为当天自动化未产出项目数据的审计记录。"],
            ],
            [12, 40, 18, 70],
        ),
    ]

    work_dir = Path("/tmp/yifangbao-daily-status-xlsx")
    if work_dir.exists():
        shutil.rmtree(work_dir)
    (work_dir / "_rels").mkdir(parents=True)
    (work_dir / "xl/_rels").mkdir(parents=True)
    (work_dir / "xl/worksheets").mkdir(parents=True)

    sheet_entries = []
    rel_entries = []
    overrides = [
        '<Default Extension="rels" ContentType="application/vnd.openxmlformats-package.relationships+xml"/>',
        '<Default Extension="xml" ContentType="application/xml"/>',
        '<Override PartName="/xl/workbook.xml" ContentType="application/vnd.openxmlformats-officedocument.spreadsheetml.sheet.main+xml"/>',
        '<Override PartName="/xl/styles.xml" ContentType="application/vnd.openxmlformats-officedocument.spreadsheetml.styles+xml"/>',
    ]
    for idx, (name, headers, rows, widths) in enumerate(sheets, start=1):
        rid = f"rId{idx}"
        sheet_entries.append(f'<sheet name="{xml_escape(name)}" sheetId="{idx}" r:id="{rid}"/>')
        rel_entries.append(
            f'<Relationship Id="{rid}" Type="http://schemas.openxmlformats.org/officeDocument/2006/relationships/worksheet" Target="worksheets/sheet{idx}.xml"/>'
        )
        overrides.append(
            f'<Override PartName="/xl/worksheets/sheet{idx}.xml" ContentType="application/vnd.openxmlformats-officedocument.spreadsheetml.worksheet+xml"/>'
        )
        (work_dir / f"xl/worksheets/sheet{idx}.xml").write_text(sheet_xml(headers, rows, widths), encoding="utf-8")

    rel_entries.append(
        '<Relationship Id="rIdStyles" Type="http://schemas.openxmlformats.org/officeDocument/2006/relationships/styles" Target="styles.xml"/>'
    )
    (work_dir / "_rels/.rels").write_text(
        """<?xml version="1.0" encoding="UTF-8" standalone="yes"?>
<Relationships xmlns="http://schemas.openxmlformats.org/package/2006/relationships">
  <Relationship Id="rId1" Type="http://schemas.openxmlformats.org/officeDocument/2006/relationships/officeDocument" Target="xl/workbook.xml"/>
</Relationships>
""",
        encoding="utf-8",
    )
    (work_dir / "xl/workbook.xml").write_text(
        f"""<?xml version="1.0" encoding="UTF-8" standalone="yes"?>
<workbook xmlns="http://schemas.openxmlformats.org/spreadsheetml/2006/main"
  xmlns:r="http://schemas.openxmlformats.org/officeDocument/2006/relationships">
  <bookViews><workbookView xWindow="0" yWindow="0" windowWidth="20140" windowHeight="10960"/></bookViews>
  <sheets>{''.join(sheet_entries)}</sheets>
</workbook>
""",
        encoding="utf-8",
    )
    (work_dir / "xl/_rels/workbook.xml.rels").write_text(
        f"""<?xml version="1.0" encoding="UTF-8" standalone="yes"?>
<Relationships xmlns="http://schemas.openxmlformats.org/package/2006/relationships">
  {''.join(rel_entries)}
</Relationships>
""",
        encoding="utf-8",
    )
    (work_dir / "xl/styles.xml").write_text(styles_xml(), encoding="utf-8")
    (work_dir / "[Content_Types].xml").write_text(
        f"""<?xml version="1.0" encoding="UTF-8" standalone="yes"?>
<Types xmlns="http://schemas.openxmlformats.org/package/2006/content-types">
  {''.join(overrides)}
</Types>
""",
        encoding="utf-8",
    )

    output.parent.mkdir(parents=True, exist_ok=True)
    if output.exists():
        output.unlink()
    with zipfile.ZipFile(output, "w", zipfile.ZIP_DEFLATED) as zf:
        for path in sorted(work_dir.rglob("*")):
            if path.is_file():
                zf.write(path, path.relative_to(work_dir).as_posix())


def write_html(output: Path) -> None:
    css = """
:root{--bg:#f6f8fb;--panel:#ffffff;--ink:#172033;--muted:#667085;--line:#d8dee8;--accent:#0f766e;--warn:#b54708;--warn-soft:#fff7ed;--soft:#e8f3f1}
*{box-sizing:border-box}body{margin:0;background:var(--bg);color:var(--ink);font:15px/1.64 -apple-system,BlinkMacSystemFont,"Segoe UI","PingFang SC","Microsoft YaHei",sans-serif}.shell{width:min(1120px,calc(100% - 32px));margin:0 auto;padding:44px 0 58px}header{display:grid;grid-template-columns:minmax(0,1fr) auto;gap:28px;align-items:end;padding-bottom:26px;border-bottom:2px solid var(--ink)}h1{margin:0 0 10px;font-size:clamp(30px,4.8vw,52px);line-height:1.12;letter-spacing:0}.lead{margin:0;color:var(--muted);max-width:760px}.badge{display:inline-block;margin-bottom:14px;padding:5px 10px;border:1px solid var(--warn);background:var(--warn-soft);color:var(--warn);font-weight:800}.meta{color:var(--muted);font-size:13px;text-align:right}.kpis{display:grid;grid-template-columns:repeat(3,minmax(0,1fr));gap:14px;margin:22px 0}.kpi{background:var(--panel);border:1px solid var(--line);border-radius:8px;padding:18px}.kpi span{display:block;color:var(--muted);font-size:13px}.kpi strong{display:block;margin-top:4px;font-size:22px}.panel{background:var(--panel);border:1px solid var(--line);border-radius:8px;padding:22px;margin-top:18px}h2{margin:0 0 12px;font-size:22px}table{width:100%;border-collapse:collapse;border:1px solid var(--line);background:var(--panel)}th,td{padding:12px 14px;border-bottom:1px solid var(--line);text-align:left;vertical-align:top}th{background:#eef2f7;color:#344054;font-size:13px}tr:last-child td{border-bottom:0}.status{color:var(--warn);font-weight:800}.actions{display:flex;gap:12px;flex-wrap:wrap;margin-top:18px}.btn{display:inline-flex;align-items:center;justify-content:center;padding:10px 14px;border-radius:8px;border:1px solid var(--accent);background:var(--accent);color:#fff;text-decoration:none;font-weight:800}.btn.secondary{background:#fff;color:var(--accent)}code{padding:2px 6px;border-radius:6px;background:var(--soft);color:#0b524b}.note{color:var(--muted);font-size:13px}@media(max-width:760px){.shell{width:min(100% - 24px,720px);padding-top:26px}header,.kpis{grid-template-columns:1fr}.meta{text-align:left}table{min-width:760px}.panel{overflow:auto}}
"""
    doc = f"""<!doctype html>
<html lang="zh-CN">
<head>
  <meta charset="utf-8">
  <meta name="viewport" content="width=device-width, initial-scale=1">
  <title>乙方宝中标项目每日更新 - 访问阻塞记录</title>
  <style>{css}</style>
</head>
<body>
  <main class="shell">
    <header>
      <div>
        <span class="badge">本次运行阻塞</span>
        <h1>乙方宝中标项目每日更新</h1>
        <p class="lead">本次应检索“智慧食堂 / 中标 / 中标通知 / 全国 / {html.escape(RUN_DATE)} 当天或增量范围”，但未取得乙方宝页面数据，因此没有新增或变化项目清单。</p>
      </div>
      <p class="meta">运行时间<br>{RUN_TIME}</p>
    </header>

    <section class="kpis" aria-label="运行指标">
      <div class="kpi"><span>新增项目</span><strong>未检索</strong></div>
      <div class="kpi"><span>变化项目</span><strong>未检索</strong></div>
      <div class="kpi"><span>运行状态</span><strong class="status">阻塞</strong></div>
    </section>

    <section class="panel">
      <h2>阻塞原因</h2>
      <p>{html.escape(BLOCK_REASON)}按自动化规则，遇到需要登录态、验证码、密码、人工确认或不可访问站点时，应停止并提示用户处理，不能尝试绕过。</p>
      <div class="actions">
        <a class="btn" href="{html.escape(WORKBOOK_NAME)}">下载 Excel 记录</a>
        <a class="btn secondary" href="../">返回结果列表</a>
      </div>
    </section>

    <section class="panel">
      <h2>检索口径</h2>
      <table>
        <thead><tr><th>条件</th><th>本次要求</th></tr></thead>
        <tbody>
          <tr><td>关键词</td><td><code>智慧食堂</code></td></tr>
          <tr><td>栏目</td><td><code>中标</code></td></tr>
          <tr><td>更多筛选</td><td><code>中标通知</code></td></tr>
          <tr><td>地区</td><td>全国</td></tr>
          <tr><td>发布时间</td><td>当天或适合本次增量更新的日期范围</td></tr>
        </tbody>
      </table>
    </section>

    <section class="panel">
      <h2>项目变更清单</h2>
      <table>
        <thead><tr><th>状态</th><th>项目名称</th><th>备注</th></tr></thead>
        <tbody><tr><td class="status">未检索</td><td>未取得乙方宝结果页</td><td>需要用户先处理登录状态或可用访问通道后重新运行。</td></tr></tbody>
      </table>
      <p class="note">本页是当天自动化审计记录，不代表乙方宝当天没有新增项目。</p>
    </section>

    <section class="panel">
      <h2>下一步</h2>
      <p>请在可访问外部站点的浏览器中确认乙方宝已登录，并确保自动化能复用该登录状态；随后重新运行“乙方宝中标项目每日更新”。</p>
    </section>
  </main>
</body>
</html>
"""
    output.parent.mkdir(parents=True, exist_ok=True)
    output.write_text(doc, encoding="utf-8")


def append_run_note() -> None:
    entry = f"""

    <section>
      <h2>{html.escape(RUN_DATE)} 自动运行结果</h2>
      <p>运行时间：<code>{html.escape(RUN_TIME)}</code>。本次尝试执行每日更新，但 {html.escape(BLOCK_REASON)}</p>
      <p>最终处理：已生成阻塞状态结果包，未整理新增或变化项目。该状态不代表乙方宝当天无新增项目，只代表本次没有取得乙方宝结果页。</p>
      <ul>
        <li>结果包：<code>docs/final-results/{RESULT_SLUG}/index.html</code></li>
        <li>Excel：<code>docs/final-results/{RESULT_SLUG}/{WORKBOOK_NAME}</code></li>
        <li>命令验证：<code>bash scripts/sync_workflow.sh pull</code> 成功；当前会话未提供可复用乙方宝登录态的 Browser 控制通道；结果包文件已生成。</li>
        <li>复用模式：账号类自动化应显式区分“无新增数据”和“未取得页面数据”，阻塞状态也要生成审计包。</li>
        <li>下一步：用户在可访问乙方宝的浏览器中保持登录后重新运行自动化。</li>
      </ul>
    </section>
"""
    if RUN_NOTE.exists():
        text = RUN_NOTE.read_text(encoding="utf-8")
        if f"{RUN_DATE} 自动运行结果" not in text:
            text = text.replace("  </main>", entry + "\n  </main>")
            RUN_NOTE.write_text(text, encoding="utf-8")


def main() -> None:
    write_html(RESULT_DIR / "index.html")
    write_workbook(RESULT_DIR / WORKBOOK_NAME)
    append_run_note()


if __name__ == "__main__":
    main()
