"""Parse bot.log for 2026-07-16 SMS successes, report duplicates, rebuild stats."""
from __future__ import annotations

import asyncio
import logging
import re
import sys
from collections import Counter
from datetime import datetime
from pathlib import Path

sys.path.insert(0, "/app")

from sqlalchemy import select, text

from app.database.engine import async_session
from app.database.models import AutoInfo, MessageLog, MessageTotals, SentPhones

logging.basicConfig(level=logging.INFO)
log = logging.getLogger("recover_from_logs")

TODAY = "2026-07-16"
LOG_PATHS = [
    "/home/cdn_revany/public_html/autoria/logs/bot.log",
    "/app/logs/bot.log",
    "/tmp/sms_today_full.log",
]

RE_DATE = re.compile(rf"^{TODAY}\s")
RE_PHONE_SEND = re.compile(
    r"Новые номера для отправки SMS:\s*\[([^\]]+)\]"
)
RE_PHONE_USE = re.compile(
    r"Используем первый номер из списка:\s*\[([^\]]+)\]"
)
RE_SUCCESS = re.compile(r"SMS успешно отправлены на\s+(\d+)")
RE_FAIL = re.compile(r"(Ошибка отправки SMS|Исключение при отправке SMS|TurboSMS отказ)")
RE_PHONE_NUM = re.compile(r"(\d{10,15})")


def _phones_from(raw: str) -> list[str]:
    # "380..., '380...'" or "'380...'"
    return RE_PHONE_NUM.findall(raw)


def parse_log(path: str) -> list[dict]:
    """Return list of successful sends: {ts, phone, count}."""
    events: list[dict] = []
    pending_phone: str | None = None
    pending_ts: str | None = None

    with open(path, "r", encoding="utf-8", errors="replace") as f:
        for line in f:
            if not RE_DATE.match(line):
                continue
            ts = line[:23]

            m_send = RE_PHONE_SEND.search(line)
            if m_send:
                phones = _phones_from(m_send.group(1))
                if phones:
                    pending_phone = phones[0]
                    pending_ts = ts
                continue

            m_use = RE_PHONE_USE.search(line)
            if m_use and not pending_phone:
                phones = _phones_from(m_use.group(1))
                if phones:
                    pending_phone = phones[0]
                    pending_ts = ts
                continue

            if RE_SUCCESS.search(line):
                if pending_phone:
                    events.append(
                        {
                            "ts": pending_ts or ts,
                            "phone": pending_phone,
                            "log_ts": ts,
                        }
                    )
                pending_phone = None
                pending_ts = None
                continue

            # real SMS API failure before success — clear pending
            if "Ошибка отправки SMS" in line or "TurboSMS отказ" in line:
                pending_phone = None
                pending_ts = None
                continue

    return events


def find_log() -> str:
    for p in LOG_PATHS:
        if Path(p).is_file():
            return p
    raise FileNotFoundError("bot.log not found")


async def rebuild(events: list[dict]) -> None:
    phones = [e["phone"] for e in events]
    cnt = Counter(phones)
    unique = len(cnt)
    total = len(events)
    dupes = {p: n for p, n in cnt.items() if n > 1}

    print(f"TOTAL_SUCCESS={total}")
    print(f"UNIQUE_PHONES={unique}")
    print(f"DUPLICATE_PHONES={len(dupes)}")
    print(f"EXTRA_SENDS_DUE_TO_DUPES={total - unique}")
    print("--- top duplicates ---")
    for p, n in sorted(dupes.items(), key=lambda x: -x[1])[:30]:
        print(f"  {p}: {n} times")

    # Load ad info for phones from sent_phones / autoinfo
    async with async_session() as session:
        sp_rows = (
            await session.execute(select(SentPhones))
        ).scalars().all()
        phone_meta = {
            sp.phone_number: {
                "ad_id": sp.ad_id,
                "ad_name": sp.ad_name,
                "sent_at": sp.sent_at,
            }
            for sp in sp_rows
        }

        ad_ids = [m["ad_id"] for m in phone_meta.values() if m.get("ad_id")]
        links: dict[str, str] = {}
        if ad_ids:
            rows = (
                await session.execute(
                    select(AutoInfo.ad_id, AutoInfo.link).where(AutoInfo.ad_id.in_(ad_ids))
                )
            ).all()
            links = {str(a): ln for a, ln in rows}

        # Delete today's recovered/incomplete message_log SMS rows for these phones,
        # then insert one row per successful send from logs.
        # Keep non-SMS / other-day rows.
        existing_today = (
            await session.execute(
                text(
                    "SELECT id, name, created_at FROM message_log "
                    "WHERE date(created_at) = :d OR "
                    "(created_at >= :start_utc AND created_at < :end_utc)"
                ),
                {
                    "d": TODAY,
                    # Kyiv day 2026-07-16 = UTC 2026-07-15 21:00 .. 2026-07-16 21:00
                    "start_utc": "2026-07-15 21:00:00",
                    "end_utc": "2026-07-16 21:00:00",
                },
            )
        ).mappings().all()

        # Count how many we'll remove that look like SMS logs (contain Телефоны:)
        to_delete_ids = [
            r["id"] for r in existing_today if "Телефоны:" in (r["name"] or "")
        ]
        # Also keep manual test? User asked restore ALL from logs — tests not in send events.
        # Preserve rows without matching our rebuild if they're test SMS
        test_keep = [
            r["id"]
            for r in existing_today
            if "Тест SMS" in (r["name"] or "")
        ]
        delete_ids = [i for i in to_delete_ids if i not in test_keep]

        old_deleted = len(delete_ids)
        if delete_ids:
            await session.execute(
                text(
                    f"DELETE FROM message_log WHERE id IN ({','.join(str(i) for i in delete_ids)})"
                )
            )

        # Mark all phones that appear in success events as successful
        for phone in set(phones):
            sp = (
                await session.execute(
                    select(SentPhones).where(SentPhones.phone_number == phone)
                )
            ).scalar_one_or_none()
            # last event ts for this phone
            last = max(
                (e for e in events if e["phone"] == phone),
                key=lambda e: e["ts"],
            )
            try:
                sent_at = datetime.strptime(last["ts"], "%Y-%m-%d %H:%M:%S,%f")
            except ValueError:
                try:
                    sent_at = datetime.strptime(last["ts"][:19], "%Y-%m-%d %H:%M:%S")
                except ValueError:
                    sent_at = datetime.utcnow()

            meta = phone_meta.get(phone, {})
            if sp:
                sp.is_successful = True
                sp.sent_at = sent_at
            else:
                session.add(
                    SentPhones(
                        phone_number=phone,
                        ad_id=meta.get("ad_id"),
                        ad_name=meta.get("ad_name"),
                        is_successful=True,
                        sent_at=sent_at,
                    )
                )

        # Insert one MessageLog per successful send
        for e in events:
            phone = e["phone"]
            meta = phone_meta.get(phone, {})
            ad_name = meta.get("ad_name") or "Неизвестно"
            ad_id = meta.get("ad_id")
            link = links.get(str(ad_id or ""), "") or (f"ad:{ad_id}" if ad_id else "from_log")
            name = f"{ad_name} | Телефоны: {phone}"
            try:
                created = datetime.strptime(e["ts"], "%Y-%m-%d %H:%M:%S,%f")
            except ValueError:
                created = datetime.strptime(e["ts"][:19], "%Y-%m-%d %H:%M:%S")
            session.add(
                MessageLog(name=name, link=link, is_sent=True, created_at=created)
            )

        await session.flush()

        total_sent = (
            await session.execute(
                text("SELECT COUNT(*) FROM message_log WHERE is_sent = 1")
            )
        ).scalar()
        total_failed = (
            await session.execute(
                text("SELECT COUNT(*) FROM message_log WHERE is_sent = 0")
            )
        ).scalar()

        totals = (
            await session.execute(select(MessageTotals).limit(1))
        ).scalars().first()
        if totals:
            totals.sent_total = int(total_sent or 0)
            totals.failed_total = int(total_failed or 0)
        else:
            session.add(
                MessageTotals(
                    id=1,
                    sent_total=int(total_sent or 0),
                    failed_total=int(total_failed or 0),
                )
            )

        await session.commit()
        print(f"DELETED_OLD_TODAY_LOGS={old_deleted}")
        print(f"INSERTED_FROM_LOG={total}")
        print(f"MESSAGE_TOTALS_SENT={total_sent}")

    from app.database.crud_static import get_sms_statistics

    stats = await get_sms_statistics()
    print(f"STATS={stats}")


async def main() -> None:
    # Prefer host log copied into container
    path = None
    for p in (
        "/tmp/bot.log",
        "/app/logs/bot.log",
        "/home/cdn_revany/public_html/autoria/logs/bot.log",
    ):
        if Path(p).is_file():
            path = p
            break
    if not path:
        raise SystemExit("no bot.log")
    print(f"LOG={path}")
    events = parse_log(path)
    await rebuild(events)


if __name__ == "__main__":
    asyncio.run(main())
