#!/usr/bin/env python3
"""История PR-завода по дням: с чем сравнивать сегодняшние цифры.

Зачем. В эталоне PR Factory на обзоре стоит сравнение периодов: «99
опубликовано, +7 к 2026-09-16 (было 92)». У нас кнопки «Неделя» и «Месяц»
висели выключенными с подписью «сравнивать не с чем» - истории не было.
Она накопилась: реестр текстов помнит даты публикаций, журнал замеров -
просмотры по дням, база площадок - когда заведён аккаунт. Значит состояние
завода на любой прошедший день можно восстановить, а не хранить снимками.

Что считаем на каждую дату:
  опубликовано  - материалов вышло к этому дню включительно
  площадок      - на скольких площадках они вышли
  просмотры     - сумма последних замеров, сделанных не позже этой даты
  аккаунтов     - заведено аккаунтов к этому дню

Чего НЕ считаем, и это важно:
  «ждут ответа редакции» и «внешних ссылок» - у них в данных только текущее
  состояние. Задним числом восстановить нельзя: материал, который сегодня
  ждёт ответа, неделю назад мог ещё не быть отправлен, а отметки об этом
  не осталось. Пустое поле панель покажет честно, выдуманное - соврёт.

Правило журнала замеров: просмотры у площадок считаются нарастающим итогом,
поэтому «на дату» - это последний замер не позже неё, а не сумма за период.
Если публикация к этой дате ещё не мерилась, в сумму она не идёт вовсе:
ноль читался бы как «никто не смотрел».

Запуск: python3 панель/данные/обновить-историю-pr.py
"""

import json
import re
import sys
from datetime import date, timedelta
from pathlib import Path

КОРЕНЬ = Path(__file__).resolve().parent.parent.parent
ДАННЫЕ = Path(__file__).resolve().parent
РЕЕСТР = КОРЕНЬ / "завод" / "реестр-текстов.json"
ПЛОЩАДКИ = КОРЕНЬ / "завод" / "площадки.json"
ЖУРНАЛ = КОРЕНЬ / "завод" / "просмотры-журнал.csv"
ОТЧЁТ = ДАННЫЕ / "report.json"

# Портал подняли 02.09.2026, первые публикации - тогда же. Раньше этого дня
# сравнивать не с чем, и пустой хвост в истории только мешает.
НАЧАЛО = date(2026, 9, 2)


def дата(строка):
    """Первая дата в строке, в любом из двух наших форматов."""
    м = re.search(r"(\d{2})\.(\d{2})\.(\d{4})", str(строка or ""))
    if м:
        return date(int(м.group(3)), int(м.group(2)), int(м.group(1)))
    м = re.search(r"(\d{4})-(\d{2})-(\d{2})", str(строка or ""))
    return date(*map(int, м.groups())) if м else None


def общий_модуль():
    """завод/таблица-площадок.py - общий модуль признаков. Имя с дефисом,
    обычным import не возьмёшь."""
    import importlib.util
    путь = КОРЕНЬ / "завод" / "таблица-площадок.py"
    с = importlib.util.spec_from_file_location("таблица_площадок", путь)
    м = importlib.util.module_from_spec(с)
    с.loader.exec_module(м)
    return м


таб = общий_модуль()


def имя_площадки(п):
    """Одно написание на площадку: в журнале живут и «myJane», и «myJane
    (сеть Медиафорт)», а сверять их надо как одну."""
    return таб.имя_площадки(п)


def ключ_заголовка(з):
    return re.sub(r"[^а-яёa-z0-9]", "", str(з).lower())


def публикации():
    """Вышедшие материалы: (дата выхода, площадка, заголовок).

    «Вышло, но не видно» публикацией не считается - у нас так помечен пост
    на Sostav, который из кабинета не виден. Он есть, но читателя у него нет.
    """
    р = json.loads(РЕЕСТР.read_text(encoding="utf-8"))["тексты"]
    итог = []
    for т in р:
        if not таб.вышло_читателю(т) or not т.get("ссылка"):
            continue
        д = дата(т.get("статус"))
        if д:
            итог.append((д, имя_площадки(т.get("площадка", "")), т["заголовок"]))
    return итог


def замеры():
    """{(площадка, заголовок): [(дата, номер строки, число)]} - вся история.

    Номер строки нужен, чтобы при двух записях за один день взять последнюю:
    16.09.2026 у Smart-Lab так лежали ошибочные 2221 и исправление на 227,
    и сортировка по значению брала большее.
    """
    итог = {}
    if not ЖУРНАЛ.exists():
        return итог
    for i, строка in enumerate(ЖУРНАЛ.read_text(encoding="utf-8").splitlines()):
        ч = строка.split(";")
        if len(ч) < 5 or not ч[0].startswith("20") or not ч[4].strip().isdigit():
            continue
        к = (имя_площадки(ч[2]), ключ_заголовка(ч[3]))
        итог.setdefault(к, []).append((дата(ч[0]), i, int(ч[4])))
    for сп in итог.values():
        сп.sort(key=lambda т: (т[0], т[1]))
    return итог


def аккаунты():
    """Даты заведения аккаунтов - по тем же правилам, что и счётчик панели.

    Не считаем: общий вход с соседней площадкой (у VK-сообщества тот же вход,
    что у VK Статей) и снятые площадки. Pressfeed сняли 21.09.2026, а аккаунт
    там когда-то заводили - если его не отнять, счётчик растёт после отказа.
    """
    б = json.loads(ПЛОЩАДКИ.read_text(encoding="utf-8"))
    карточки = б["площадки"] if isinstance(б, dict) else б
    итог = []
    for к in карточки:
        if к.get("аккаунт") == "общий" or к.get("аккаунт_общий_с") or таб.снята(к):
            continue
        д = дата(к.get("аккаунт_заведён"))
        if д:
            итог.append(д)
    return sorted(итог)


def собрать():
    пуб = публикации()
    зам = замеры()
    акк = аккаунты()
    сегодня = date.today()

    ряд = []
    д = НАЧАЛО
    while д <= сегодня:
        вышли = [п for п in пуб if п[0] <= д]
        просмотров = 0
        for площадка, заголовок in {(п[1], ключ_заголовка(п[2])) for п in вышли}:
            точки = [н for дт, _, н in зам.get((площадка, заголовок), []) if дт <= д]
            if точки:
                просмотров += точки[-1]
        # Разбивка по площадкам нужна рейтингу: в эталоне у каждой строки
        # стоит прирост с даты сравнения, а не только текущее число.
        по_площадкам = {}
        for площадка in {п[1] for п in вышли}:
            свои = [п for п in вышли if п[1] == площадка]
            в, мерили = 0, False
            for _, _, заголовок in свои:
                точки = [н for дт, _, н in зам.get((площадка, ключ_заголовка(заголовок)), [])
                         if дт <= д]
                if точки:
                    в += точки[-1]
                    мерили = True
            # Ноль и «не мерили» - разное. Если к этому дню площадку ни разу
            # не замеряли, ставим null: панель покажет «нет базы» вместо
            # прироста от нуля, который был бы выдумкой.
            по_площадкам[площадка] = {"опубликовано": len(свои),
                                      "просмотры": в if мерили else None}

        ряд.append({
            "дата": д.isoformat(),
            "опубликовано": len(вышли),
            "площадок": len({п[1] for п in вышли}),
            "просмотры": просмотров,
            "аккаунтов": sum(1 for а in акк if а <= д),
            "по_площадкам": по_площадкам,
        })
        д += timedelta(days=1)
    return ряд


def главная():
    ряд = собрать()
    отчёт = json.loads(ОТЧЁТ.read_text(encoding="utf-8"))
    отчёт["pr"]["история"] = {
        "с": ряд[0]["дата"] if ряд else None,
        "по": ряд[-1]["дата"] if ряд else None,
        "ряд": ряд,
        "что_есть": ["опубликовано", "площадок", "просмотры", "аккаунтов"],
        "чего_нет": ("«ждут ответа редакции» и «внешних ссылок» на прошлую дату "
                     "восстановить нельзя: в данных лежит только сегодняшнее "
                     "состояние, отметок о прошлом не осталось"),
        "как_считано": ("опубликовано и площадки - по датам выхода из реестра "
                        "текстов; просмотры - последний замер не позже даты из "
                        "завод/просмотры-журнал.csv, нарастающим итогом; "
                        "аккаунты - по полю «аккаунт_заведён» базы площадок"),
    }
    ОТЧЁТ.write_text(json.dumps(отчёт, ensure_ascii=False, indent=1) + "\n",
                     encoding="utf-8")
    if ряд:
        п = ряд[-1]
        print(f"история PR: {len(ряд)} дней, {ряд[0]['дата']} - {ряд[-1]['дата']}")
        print(f"   на сегодня: опубликовано {п['опубликовано']}, "
              f"площадок {п['площадок']}, просмотров {п['просмотры']}, "
              f"аккаунтов {п['аккаунтов']}")
    else:
        print("история PR пуста: публикаций с датами не нашлось")
    return 0


if __name__ == "__main__":
    raise SystemExit(главная())
