"""Вырезка секции одного дня из недельного плана канала.

Читает файл, который пишет воскресная планёрка, и достаёт из него день
по дате. Формат заголовка дня жёстко зафиксирован в prompt.md — это
контракт, а не соглашение: по нему режет парсер.
"""
from __future__ import annotations

import os
import re
import sys
import tempfile
from dataclasses import dataclass
from pathlib import Path

# Промпт требует ровно одно тире — длинное «—». Парсер намеренно терпимее
# промпта: принимает ещё и en dash, и обычный дефис. Это дешёвая страховка от
# дрейфа модели — подмена тире не должна стоить Наташе целого дня без контента.
# Послаблять сам промпт нельзя: там формат остаётся однозначным.
DASH = r"[—–-]"
DAY_RE = re.compile(
    r"^##\s+(?P<wd>[А-Яа-я]{2})\s+" + DASH + r"\s+(?P<date>\d{4}-\d{2}-\d{2})"
    r"(?:\s+" + DASH + r"\s+(?P<rubric>.+?))?\s*$"
)
FIELD_RE = re.compile(r"^\*\*(?P<name>[^:*]+):\*\*\s*(?P<value>.*)$")

FIELD_MAP = {"ТГ": "tg", "Threads": "threads", "IG": "ig", "Хук": "hook"}


@dataclass
class DayPlan:
    weekday: str = ""
    date: str = ""
    rubric: str = ""
    tg: str = ""
    threads: str = ""
    ig: str = ""
    hook: str = ""


def parse_day(text: str, target: str):
    """Вернуть DayPlan для даты target или None, если такого дня в файле нет."""
    lines = text.splitlines()
    start = None
    for i, line in enumerate(lines):
        m = DAY_RE.match(line)
        if m and m.group("date") == target:
            start = i
            day = DayPlan(
                weekday=m.group("wd"),
                date=target,
                rubric=(m.group("rubric") or "").strip(),
            )
            break
    if start is None:
        return None

    # Побеждает ПЕРВОЕ вхождение поля. Промпт велит писать рассмотренные, но
    # отвергнутые варианты свободным текстом ниже полей дня; если модель всё же
    # напишет их той же жирной меткой, в телеграм должен уехать выбранный
    # вариант (он идёт первым), а не отвергнутый.
    seen = set()
    for line in lines[start + 1:]:
        if DAY_RE.match(line) or line.startswith("## "):
            break  # начался следующий день — дальше не наше
        fm = FIELD_RE.match(line)
        if not fm:
            continue
        attr = FIELD_MAP.get(fm.group("name").strip())
        if attr and attr not in seen:
            seen.add(attr)
            setattr(day, attr, fm.group("value").strip())
    return day


def render_block(day: DayPlan) -> str:
    """Собрать текст блока для утреннего сообщения.

    Тот же формат отдаёт фаза 2 с Claude: tasks-bot не должен различать,
    какая из фаз успела отработать.
    """
    head = "✍️ Сегодня, {}".format(day.weekday)
    if day.rubric:
        head += " — {}".format(day.rubric)

    lines = [head]
    for label, value in (("ТГ", day.tg), ("Threads", day.threads), ("IG", day.ig)):
        lines.append("{:<8} {}".format(label + ":", value or "—"))
    if day.hook:
        lines.append("{:<8} 🎬 хук: {}".format("", day.hook))
    return "\n".join(lines)


def write_block(out_dir, target: str, block: str) -> Path:
    """Записать блок атомарно: tasks-bot читает этот файл в 9:00."""
    out_dir = Path(out_dir)
    out_dir.mkdir(parents=True, exist_ok=True)
    path = out_dir / "morning-{}.md".format(target)
    payload = "date: {}\n\n{}".format(target, block)

    fd, tmp = tempfile.mkstemp(dir=str(out_dir), prefix=".morning-", suffix=".tmp")
    try:
        with os.fdopen(fd, "w", encoding="utf-8") as fh:
            fh.write(payload)
        os.replace(tmp, str(path))
    except Exception:
        Path(tmp).unlink(missing_ok=True)
        raise
    return path


def merge_days(tg_day, insta_day):
    """Склеить блок дня из двух недельных планов.

    Телеграмный план владеет полем ТГ и рубрикой дня, инста-тредсовый —
    полями Threads, IG и Хук. Любого из файлов может не быть: воскресный
    прогон мог упасть, субботний — ещё не отработать. Тогда блок собирается
    из того, что есть: половина дня полезнее, чем «темы на сегодня нет».
    """
    if tg_day is None and insta_day is None:
        return None

    base = tg_day if tg_day is not None else insta_day
    rubric = ""
    if tg_day is not None and tg_day.rubric:
        rubric = tg_day.rubric
    elif insta_day is not None:
        rubric = insta_day.rubric

    merged = DayPlan(
        weekday=base.weekday,
        date=base.date,
        rubric=rubric,
        tg=tg_day.tg if tg_day is not None else "",
    )
    if insta_day is not None:
        merged.threads = insta_day.threads
        merged.ig = insta_day.ig
        merged.hook = insta_day.hook
    elif tg_day is not None:
        # Переходный период и аварийный режим: пока субботний прогон не
        # начал писать свой файл, поля площадок ещё лежат в телеграмном.
        # Без этой ветки первое же утро без субботы осталось бы без Threads
        # и IG, хотя они прямо тут, в файле.
        merged.threads = tg_day.threads
        merged.ig = tg_day.ig
        merged.hook = tg_day.hook
    return merged


def _read_day(path, target):
    """DayPlan из файла или None: файла нет, он битый или дня в нём нет.

    Ошибки печатаются в stderr и не роняют вызов — решение о том, хватает ли
    собранного на блок дня, принимает main() по обоим файлам сразу.
    """
    if not path:
        return None
    try:
        text = Path(path).read_text(encoding="utf-8")
    except FileNotFoundError:
        print("файл недели {} не найден".format(path), file=sys.stderr)
        return None
    except UnicodeDecodeError:
        print("файл {} не может быть прочитан как UTF-8".format(path), file=sys.stderr)
        return None
    except Exception as e:
        print("ошибка чтения файла {}: {}".format(path, e), file=sys.stderr)
        return None
    day = parse_day(text, target)
    if day is None:
        print("дня {} нет в {}".format(target, path), file=sys.stderr)
    return day


def main(argv) -> int:
    if len(argv) not in (4, 5):
        print(
            "usage: dayblock.py <weekly_file> <YYYY-MM-DD> <out_dir> [insta_file]",
            file=sys.stderr,
        )
        return 2
    weekly, target, out_dir = argv[1], argv[2], argv[3]
    insta = argv[4] if len(argv) == 5 else None

    day = merge_days(_read_day(weekly, target), _read_day(insta, target))
    if day is None:
        print("дня {} нет ни в одном из планов".format(target), file=sys.stderr)
        return 2
    path = write_block(out_dir, target, render_block(day))
    print(path)
    return 0


if __name__ == "__main__":
    sys.exit(main(sys.argv))
