from __future__ import annotations

import asyncio
import os
import sys
from pathlib import Path

from loguru import logger

from ..config import settings
from ..constants import WORKER_SITE_POSTER
from ..db import fetch_bool_setting, fetch_int_setting, fetch_setting, get_pool
from ..heartbeat import HeartbeatReporter
from ..jobs import is_worker_enabled


class SitePoster:
    def __init__(self) -> None:
        self.pool = None
        self.heartbeat = HeartbeatReporter(WORKER_SITE_POSTER, 30)
        self.project_root = Path(__file__).resolve().parents[3]
        self.interval_sec = 300
        self.photo_batch_size = 10
        self.video_batch_size = 1
        self.ghost_url = "https://f-n8.ru"
        self.key_file = "/opt/vk-parser/.site-poster-key"

    async def init(self) -> None:
        self.pool = await get_pool()

    async def reload_settings(self) -> None:
        self.interval_sec = max(30, await fetch_int_setting("site_poster_interval_sec", 300))
        self.photo_batch_size = max(0, await fetch_int_setting("site_poster_photo_batch_size", 10))
        self.video_batch_size = max(0, await fetch_int_setting("site_poster_video_batch_size", 1))
        self.ghost_url = str(await fetch_setting("site_poster_ghost_url", "https://f-n8.ru") or "https://f-n8.ru").rstrip("/")
        self.key_file = str(await fetch_setting("site_poster_key_file", "/opt/vk-parser/.site-poster-key") or "").strip()
        if not self.key_file:
            raise RuntimeError("site_poster_key_file is empty")

    async def publish_mode(self, flag: str, limit: int) -> tuple[int, int]:
        if limit < 1:
            return 0, 0
        command = [
            sys.executable,
            str(self.project_root / "scripts" / "publish_site_batch.py"),
            flag,
            "--limit",
            str(limit),
            "--ghost-url",
            self.ghost_url,
            "--key-file",
            self.key_file,
        ]
        env = os.environ.copy()
        env["PYTHONPATH"] = str(self.project_root / "src")
        env["PYTHONUNBUFFERED"] = "1"
        process = await asyncio.create_subprocess_exec(
            *command,
            cwd=str(self.project_root),
            env=env,
            stdout=asyncio.subprocess.PIPE,
            stderr=asyncio.subprocess.STDOUT,
        )
        published = 0
        failed = 0
        assert process.stdout is not None
        while True:
            try:
                raw_line = await asyncio.wait_for(process.stdout.readline(), timeout=20)
            except TimeoutError:
                await self.heartbeat.beat(
                    self.pool,
                    status="publishing",
                    meta={"mode": flag, "published": published, "failed": failed},
                    force=True,
                )
                continue
            if not raw_line:
                break
            line = raw_line.decode("utf-8", errors="replace").strip()
            if not line:
                continue
            if line.startswith("PUBLISHED "):
                published += 1
                logger.info("Site poster: {}", line)
            elif line.startswith("FAILED "):
                failed += 1
                logger.error("Site poster: {}", line)
            elif line not in {"NO_READY_POSTS", "ALREADY_RUNNING"}:
                logger.debug("Site poster child: {}", line)
            await self.heartbeat.beat(
                self.pool,
                status="publishing",
                meta={"mode": flag, "published": published, "failed": failed},
            )
        return_code = await process.wait()
        if return_code != 0:
            raise RuntimeError(f"site publisher exited with code {return_code} for {flag}")
        return published, failed

    async def run_once(self) -> bool:
        await self.reload_settings()
        worker_enabled = await is_worker_enabled(self.pool, WORKER_SITE_POSTER)
        app_enabled = await fetch_bool_setting("site_poster_enabled", False)
        if not worker_enabled or not app_enabled:
            await self.heartbeat.beat(self.pool, status="disabled", force=True)
            return False
        await self.heartbeat.beat(self.pool, status="checking", force=True)
        photo_published, photo_failed = await self.publish_mode("--all-ready", self.photo_batch_size)
        video_published, video_failed = await self.publish_mode("--all-ready-videos", self.video_batch_size)
        published = photo_published + video_published
        failed = photo_failed + video_failed
        await self.heartbeat.beat(
            self.pool,
            status="idle" if not failed else "error",
            meta={"published": published, "failed": failed},
            force=True,
        )
        return bool(published)

    async def run_loop(self) -> None:
        await self.init()
        logger.info("{} started pid={}", WORKER_SITE_POSTER, os.getpid())
        while True:
            try:
                await self.run_once()
            except Exception as exc:
                logger.exception("Site poster loop error: {}", exc)
                await self.heartbeat.beat(self.pool, status="error", meta={"error": str(exc)[:500]}, force=True)
            await asyncio.sleep(self.interval_sec)


async def main() -> None:
    logger.remove()
    logger.add(sys.stdout, level=settings.log_level)
    worker = SitePoster()
    await worker.run_loop()


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