"""Оркестратор прогона: тянет посты и метрики по всем платформам в SQLite."""
from __future__ import annotations

from datetime import datetime, timedelta

import db

# Первый прогон забирает год истории: старые посты приходят с уже устоявшимися
# метриками, и медиана работает с первого дня. Дальше хватает месяца — именно
# в этом окне метрики ещё заметно растут.
BACKFILL_DAYS = 365
INCREMENT_DAYS = 30


def _parse(moment: str) -> datetime:
    return datetime.fromisoformat(moment.replace("Z", "+00:00"))


def _shift(moment: str, days: int) -> str:
    """Граница окна в том же виде, в каком Meta отдаёт timestamp постов."""
    return (_parse(moment) - timedelta(days=days)).strftime("%Y-%m-%dT%H:%M:%S%z")


def window_start(db_path, platform: str, now: str) -> str:
    with db.connect(db_path) as conn:
        known = conn.execute(
            "SELECT 1 FROM {} LIMIT 1".format(db.TABLES[platform])).fetchone()
    return _shift(now, BACKFILL_DAYS if known is None else INCREMENT_DAYS)


def run_platform(db_path, platform: str, client, now: str) -> dict:
    """Прогон одной платформы. Возвращает счётчики для лога."""
    since = window_start(db_path, platform, now)
    known_ids = {r["post_id"] for r in db.posts_in_window(db_path, platform, since)}

    seen, new_count = set(), 0
    for post in client.posts(since):
        if db.upsert_post(db_path, platform, post):
            new_count += 1
        seen.add(post["post_id"])
        metrics = client.insights(
            post["post_id"], product_type=post.get("media_product_type"))
        db.update_stats(db_path, platform, post["post_id"], metrics, now=now)

    # Удалённым помечаем только то, что лежит внутри просмотренного окна:
    # посты старше него мы в этот раз не запрашивали, и их отсутствие в
    # ответе ничего не означает.
    gone = known_ids - seen
    for post_id in gone:
        db.mark_deleted(db_path, platform, post_id)

    account = client.account()
    db.save_account_snapshot(
        db_path, platform, date=now[:10],
        followers=account.get("followers"), media_count=account.get("media_count"),
        username=account.get("username"))

    return {"seen": len(seen), "new": new_count, "deleted": len(gone),
            "followers": account.get("followers")}


def run(db_path, clients: dict, now: str):
    """Прогоняет все платформы. Падение одной не мешает остальным."""
    stats, errors = {}, {}
    for platform, client in clients.items():
        try:
            stats[platform] = run_platform(db_path, platform, client, now)
        except Exception as exc:
            errors[platform] = str(exc)

    if not errors:
        status = "ok"
    elif len(errors) == len(clients):
        status = "failed"
    else:
        status = "partial"

    db.record_run(
        db_path, started_at=now,
        finished_at=datetime.now().strftime("%Y-%m-%dT%H:%M:%SZ"),
        status=status,
        error_summary="; ".join("{}: {}".format(p, e) for p, e in errors.items()))
    return status, errors
