"""Read/update Objective sync config (single-row БД-конфигурация).

Используется:
  - tasks.scrape_objective.sync_all_groups: читает groups/use_ddu/use_dkp/
    period_months_back/inter_group_delay_s/rate_ms/retries при каждом запуске
  - workers.celery_app: читает cron_schedule при старте beat (один раз)
  - api.v1.admin_scrape: GET/PUT /admin/scrape/objective/config

Если таблица objective_sync_config недоступна (БД не поднята / таблицы нет /
ошибка) — все методы возвращают defaults на основе settings.objective_*.
Это обеспечивает безопасный fallback для cold-start и unit-тестов.
"""

from __future__ import annotations

import logging
from dataclasses import dataclass
from typing import Any

from sqlalchemy import text

from app.core.config import settings

logger = logging.getLogger(__name__)


@dataclass
class ObjectiveSyncConfig:
    cron_schedule: str
    groups_csv: str
    use_ddu: bool
    use_dkp: bool
    period_months_back: int
    inter_group_delay_s: int
    rate_ms: int
    retries: int
    updated_at: str | None = None
    updated_by: str | None = None

    @property
    def groups(self) -> list[str]:
        return [g.strip() for g in self.groups_csv.split(",") if g.strip()]

    def to_dict(self) -> dict[str, Any]:
        return {
            "cron_schedule": self.cron_schedule,
            "groups_csv": self.groups_csv,
            "groups": self.groups,
            "use_ddu": self.use_ddu,
            "use_dkp": self.use_dkp,
            "period_months_back": self.period_months_back,
            "inter_group_delay_s": self.inter_group_delay_s,
            "rate_ms": self.rate_ms,
            "retries": self.retries,
            "updated_at": self.updated_at,
            "updated_by": self.updated_by,
        }


def _defaults_from_settings() -> ObjectiveSyncConfig:
    """Fallback когда БД-таблицы нет / ошибка."""
    return ObjectiveSyncConfig(
        cron_schedule=settings.objective_sync_cron,
        groups_csv=settings.objective_sync_groups,
        use_ddu=True,
        use_dkp=True,
        period_months_back=1,
        inter_group_delay_s=30,
        rate_ms=3000,
        retries=2,
    )


def get_config(db) -> ObjectiveSyncConfig:
    """Прочитать config (single row id=1). Fallback на settings при ошибке."""
    try:
        r = (
            db.execute(
                text(
                    """
                SELECT cron_schedule, groups_csv, use_ddu, use_dkp,
                       period_months_back, inter_group_delay_s, rate_ms, retries,
                       updated_at, updated_by
                  FROM objective_sync_config WHERE id = 1
                """
                )
            )
            .mappings()
            .first()
        )
    except Exception as e:
        logger.warning("get_config: fallback на settings (БД недоступна?): %s", e)
        return _defaults_from_settings()

    if not r:
        # Таблица есть но пустая — посеем default и вернём
        try:
            db.execute(
                text("INSERT INTO objective_sync_config (id) VALUES (1) ON CONFLICT DO NOTHING")
            )
            db.commit()
        except Exception:
            logger.exception("objective_sync_config seed failed (id=1 ON CONFLICT DO NOTHING)")
        return _defaults_from_settings()

    return ObjectiveSyncConfig(
        cron_schedule=r["cron_schedule"],
        groups_csv=r["groups_csv"],
        use_ddu=bool(r["use_ddu"]),
        use_dkp=bool(r["use_dkp"]),
        period_months_back=int(r["period_months_back"]),
        inter_group_delay_s=int(r["inter_group_delay_s"]),
        rate_ms=int(r["rate_ms"]),
        retries=int(r["retries"]),
        updated_at=r["updated_at"].isoformat() if r["updated_at"] else None,
        updated_by=r["updated_by"],
    )


def update_config(
    db,
    *,
    cron_schedule: str | None = None,
    groups_csv: str | None = None,
    use_ddu: bool | None = None,
    use_dkp: bool | None = None,
    period_months_back: int | None = None,
    inter_group_delay_s: int | None = None,
    rate_ms: int | None = None,
    retries: int | None = None,
    updated_by: str | None = None,
) -> ObjectiveSyncConfig:
    """PATCH-style update — обновляет только переданные поля."""
    sets: list[str] = ["updated_at = NOW()"]
    params: dict[str, Any] = {}
    for field, value in [
        ("cron_schedule", cron_schedule),
        ("groups_csv", groups_csv),
        ("use_ddu", use_ddu),
        ("use_dkp", use_dkp),
        ("period_months_back", period_months_back),
        ("inter_group_delay_s", inter_group_delay_s),
        ("rate_ms", rate_ms),
        ("retries", retries),
        ("updated_by", updated_by),
    ]:
        if value is not None:
            sets.append(f"{field} = :{field}")
            params[field] = value
    if len(sets) == 1:
        # Только updated_at — нечего обновлять
        return get_config(db)

    # Гарантия что строка существует
    db.execute(text("INSERT INTO objective_sync_config (id) VALUES (1) ON CONFLICT DO NOTHING"))
    db.execute(
        text(f"UPDATE objective_sync_config SET {', '.join(sets)} WHERE id = 1"),
        params,
    )
    db.commit()
    return get_config(db)


def get_cron_schedule_safe(db_factory) -> str:
    """Безопасное чтение cron при старте Celery beat — открывает свою сессию.

    db_factory: callable () -> Session (обычно SessionLocal)
    """
    try:
        db = db_factory()
        try:
            cfg = get_config(db)
            return cfg.cron_schedule
        finally:
            db.close()
    except Exception as e:
        logger.warning("get_cron_schedule_safe: fallback на settings: %s", e)
        return settings.objective_sync_cron
