"""Собирает PPTX из трёх слайдов по маршруты/план-презентации.md.

Библиотека: python-pptx==1.0.2 (см. сборка/requirements-презентация.txt).
Показатели читаются из маршруты/данные/показатели.json; данные вымышленные.
Текст размещается в собственных текстовых блоках, а не в плейсхолдерах темы,
чтобы вёрстка не зависела от унаследованных настроек макета.
"""

from __future__ import annotations

import json
from pathlib import Path

from lxml import etree
from pptx import Presentation
from pptx import __version__ as pptx_version
from pptx.chart.data import CategoryChartData
from pptx.dml.color import RGBColor
from pptx.enum.chart import XL_CHART_TYPE, XL_LEGEND_POSITION
from pptx.oxml.ns import qn
from pptx.util import Inches, Pt

BASE = Path(__file__).resolve().parents[1]
METRICS_PATH = BASE / "маршруты" / "данные" / "показатели.json"
OUT_PATH = BASE / "результаты" / "еженедельный-отчёт-презентация.pptx"

ACCENT = RGBColor(0x0F, 0x6B, 0x78)
SECONDARY = RGBColor(0xD2, 0x9E, 0x42)
TEXT = RGBColor(0x1D, 0x25, 0x2B)
MUTED = RGBColor(0x64, 0x70, 0x7D)

TITLE_SIZE = Pt(28)
BODY_SIZE = Pt(16)
NOTE_SIZE = Pt(10)

SOURCE_TITLE_SLIDE = (
    "Источники: маршруты/план-презентации.md; маршруты/план-отчёта.md; "
    "маршруты/требования-к-отчёту.md. Данные вымышленные."
)
SOURCE_WEEK_SLIDE = (
    "Источник показателей: маршруты/данные/показатели.json "
    "(поля requests, completed). Данные вымышленные."
)
SOURCE_FINAL_SLIDE = (
    "Источники: маршруты/данные/показатели.json; маршруты/требования-к-отчёту.md; "
    "маршруты/план-отчёта.md. Данные вымышленные."
)
MISSING_LINE = (
    "Недостающие сведения: дата встречи и ответственный не заданы; "
    "фактических причин изменений в материалах нет."
)


def load_metrics() -> list[dict]:
    """Читает недельные показатели из учебного JSON."""
    with METRICS_PATH.open(encoding="utf-8") as handle:
        payload = json.load(handle)
    rows = payload["rows"]
    if len(rows) != 2:
        raise ValueError(f"ожидалось две недели, получено {len(rows)}")
    return rows


def add_title(slide, text: str) -> None:
    box = slide.shapes.add_textbox(Inches(0.62), Inches(0.35), Inches(12.1), Inches(1.0))
    frame = box.text_frame
    frame.word_wrap = True
    frame.text = text
    run = frame.paragraphs[0].runs[0]
    run.font.size = TITLE_SIZE
    run.font.bold = True
    run.font.color.rgb = TEXT


def add_bullet(paragraph) -> None:
    """Включает маркер-точку для абзаца вне плейсхолдера темы."""
    p_pr = paragraph._p.get_or_add_pPr()
    p_pr.set("marL", "228600")
    p_pr.set("indent", "-228600")
    font = etree.SubElement(p_pr, qn("a:buFont"))
    font.set("typeface", "Arial")
    char = etree.SubElement(p_pr, qn("a:buChar"))
    char.set("char", "•")


def add_text_block(slide, left, top, width, height, lines) -> None:
    """Добавляет текстовый блок.

    lines: последовательность словарей с ключами text, size, bold, color, bullet.
    """
    box = slide.shapes.add_textbox(left, top, width, height)
    frame = box.text_frame
    frame.word_wrap = True
    for index, line in enumerate(lines):
        paragraph = frame.paragraphs[0] if index == 0 else frame.add_paragraph()
        run = paragraph.add_run()
        run.text = line["text"]
        run.font.size = line.get("size", BODY_SIZE)
        run.font.bold = line.get("bold", False)
        run.font.color.rgb = line.get("color", TEXT)
        paragraph.space_after = Pt(line.get("space_after", 6))
        if line.get("bullet", False):
            add_bullet(paragraph)
    return box


def add_source_line(slide, text: str) -> None:
    add_text_block(
        slide,
        Inches(0.72),
        Inches(6.62),
        Inches(11.9),
        Inches(0.45),
        [{"text": text, "size": NOTE_SIZE, "color": MUTED}],
    )


def add_title_slide(prs: Presentation) -> None:
    slide = prs.slides.add_slide(prs.slide_layouts[6])
    add_title(slide, "Еженедельный отчёт: цель и состав")
    add_text_block(
        slide,
        Inches(0.72),
        Inches(1.65),
        Inches(11.9),
        Inches(4.6),
        [
            {
                "text": "Цель: показать динамику обращений за две недели и договориться о следующем шаге.",
                "bold": True,
                "bullet": True,
            },
            {
                "text": "С 15 октября команда готовит еженедельный отчёт; материалы хранятся в рабочей области.",
                "bullet": True,
            },
            {
                "text": "В отчёте — три главных изменения и ссылка на источник каждого числа.",
                "bullet": True,
            },
            {
                "text": "Показатели первой и второй недели берутся из учебного файла показателей.",
                "bullet": True,
            },
        ],
    )
    add_source_line(slide, SOURCE_TITLE_SLIDE)

    slide.notes_slide.notes_text_frame.text = (
        "Цель встречи — сверить динамику обращений за две недели и зафиксировать следующий шаг.\n"
        "Подготовка началась 15 октября, материалы лежат в рабочей области.\n"
        "Подчёркиваем: все числа учебные и вымышленные, ссылки на источники обязательны."
    )


def add_week_slide(prs: Presentation, rows: list[dict]) -> None:
    slide = prs.slides.add_slide(prs.slide_layouts[6])
    add_title(slide, "Сравнение недель: обращения и завершённые обращения")

    first, second = rows
    diff = second["requests"] - first["requests"]
    completed_growth = second["completed"] - first["completed"]
    share = round(first["completed"] / first["requests"] * 100)

    add_text_block(
        slide,
        Inches(0.72),
        Inches(1.7),
        Inches(4.9),
        Inches(4.3),
        [
            {"text": "Итоги двух недель", "bold": True, "color": ACCENT, "size": Pt(15)},
            {
                "text": f"{first['week']} неделя: обращения {first['requests']}, "
                f"завершено {first['completed']}.",
                "space_after": 4,
            },
            {
                "text": f"{second['week']} неделя: обращения {second['requests']}, "
                f"завершено {second['completed']}.",
                "space_after": 4,
            },
            {"text": f"Разность числа обращений между неделями: +{diff}.", "space_after": 4},
            {
                "text": f"Доля завершённых обращений: {share} % (расчёт по тем же данным).",
                "space_after": 4,
            },
        ],
    )

    chart_data = CategoryChartData()
    chart_data.categories = [f"{first['week']} неделя", f"{second['week']} неделя"]
    chart_data.add_series("Обращения", (first["requests"], second["requests"]))
    chart_data.add_series("Завершено", (first["completed"], second["completed"]))

    frame = slide.shapes.add_chart(
        XL_CHART_TYPE.COLUMN_CLUSTERED,
        Inches(5.9),
        Inches(1.65),
        Inches(6.7),
        Inches(4.4),
        chart_data,
    )
    chart = frame.chart
    chart.has_title = True
    chart.chart_title.text_frame.text = "Обращения и завершённые обращения по неделям"
    chart.chart_title.text_frame.paragraphs[0].runs[0].font.size = Pt(12)
    chart.chart_title.text_frame.paragraphs[0].runs[0].font.color.rgb = TEXT
    chart.has_legend = True
    chart.legend.position = XL_LEGEND_POSITION.BOTTOM
    chart.legend.include_in_layout = False
    chart.font.size = Pt(11)
    chart.font.color.rgb = TEXT

    plot = chart.plots[0]
    plot.gap_width = 80
    plot.has_data_labels = True
    plot.data_labels.font.size = Pt(10)
    plot.data_labels.font.color.rgb = TEXT
    plot.series[0].format.fill.solid()
    plot.series[0].format.fill.fore_color.rgb = ACCENT
    plot.series[1].format.fill.solid()
    plot.series[1].format.fill.fore_color.rgb = SECONDARY

    add_source_line(slide, SOURCE_WEEK_SLIDE)

    slide.notes_slide.notes_text_frame.text = (
        f"Первая неделя: {first['requests']} обращений, завершено {first['completed']}.\n"
        f"Вторая неделя: {second['requests']} обращений, завершено {second['completed']}.\n"
        f"Обращения выросли на {diff}; завершённых стало больше на {completed_growth}.\n"
        "Источник: маршруты/данные/показатели.json (requests, completed); "
        f"доля {share} % — расчёт по этим же данным. Данные вымышленные."
    )


def add_conclusion_slide(prs: Presentation) -> None:
    slide = prs.slides.add_slide(prs.slide_layouts[6])
    add_title(slide, "Выводы и следующий шаг")

    add_text_block(
        slide,
        Inches(0.72),
        Inches(1.65),
        Inches(11.9),
        Inches(3.6),
        [
            {"text": "Выводы:", "bold": True, "space_after": 6},
            {"text": "Обращения выросли с 12 до 16, завершённые — с 9 до 12.", "bullet": True},
            {"text": "Разность числа обращений между неделями равна четырём.", "bullet": True},
            {
                "text": "Структура отчёта согласована; справочные данные требуют уточнения.",
                "bullet": True,
            },
            {
                "text": "Следующий шаг: обсудить причины изменений на встрече команды.",
                "bullet": True,
            },
        ],
    )
    add_text_block(
        slide,
        Inches(0.72),
        Inches(5.45),
        Inches(11.9),
        Inches(0.9),
        [{"text": MISSING_LINE, "size": Pt(13), "bold": True, "color": SECONDARY}],
    )
    add_source_line(slide, SOURCE_FINAL_SLIDE)

    slide.notes_slide.notes_text_frame.text = (
        "Три вывода: рост обращений, рост завершённых, разность четыре.\n"
        "Следующий шаг — разобрать причины изменений на встрече.\n"
        "Чего не хватает: дата встречи и ответственный не заданы, "
        "подтверждённых фактических причин изменений в материалах нет — их не выдумываем."
    )


def main() -> None:
    rows = load_metrics()
    prs = Presentation()
    prs.slide_width = Inches(13.333)
    prs.slide_height = Inches(7.5)

    add_title_slide(prs)
    add_week_slide(prs, rows)
    add_conclusion_slide(prs)

    OUT_PATH.parent.mkdir(parents=True, exist_ok=True)
    prs.save(OUT_PATH)
    print(f"python-pptx {pptx_version}: сохранено слайдов — {len(prs.slides._sldIdLst)} -> {OUT_PATH}")


if __name__ == "__main__":
    main()
