#!/usr/bin/env python3

import sys
import requests
from pathlib import Path

BASE_DIR = Path(__file__).resolve().parent.parent
sys.path.insert(0, str(BASE_DIR / "engine"))

import content_pool_publisher as base


def log(message):
    print(message, flush=True)


def get_job(db, queue_id=None):

    sql = """
        SELECT
            q.id AS queue_id,
            q.story_id,
            q.channel_id,
            q.generated_content_id,
            q.publish_type,
            q.status AS queue_status,
            q.attempts,

            gc.body,
            gc.generation_status,

            c.name AS channel_name,
            c.username,
            c.telegram_chat_id,
            c.publisher_id,
            c.is_active AS channel_active,
            c.auto_publish AS channel_auto_publish,

            nc.is_active AS news_active,
            nc.auto_publish AS news_auto_publish,

            p.name AS publisher_name,
            p.publisher_type,
            p.is_active AS publisher_active,
            p.telegram_bot_token,
            p.flood_status,
            p.flood_until

        FROM news_publish_queue q

        JOIN news_generated_content gc
            ON gc.id = q.generated_content_id

        JOIN channels c
            ON c.id = q.channel_id

        JOIN news_channels nc
            ON nc.channel_id = c.id

        JOIN publishers p
            ON p.id = c.publisher_id

        WHERE q.status = 'queued'
          AND gc.generation_status = 'generated'
          AND c.channel_type = 'news'
          AND c.is_active = 1
          AND c.auto_publish = 1
          AND nc.is_active = 1
          AND nc.auto_publish = 1
          AND p.is_active = 1
    """

    params = []

    if queue_id is not None:
        sql += " AND q.id = %s "
        params.append(int(queue_id))

    sql += """
        ORDER BY
            COALESCE(q.scheduled_at, q.created_at) ASC,
            q.id ASC
        LIMIT 1
    """

    with db.cursor() as cursor:
        cursor.execute(sql, tuple(params))
        return cursor.fetchone()


def telegram_target(job):

    chat_id = str(
        job.get("telegram_chat_id") or ""
    ).strip()

    username = str(
        job.get("username") or ""
    ).strip()

    if chat_id:
        return chat_id

    if username:
        if username.startswith("@"):
            return username
        return "@" + username

    raise RuntimeError(
        "Destination channel has no Telegram target."
    )


def reserve_job(db, queue_id):

    with db.cursor() as cursor:

        cursor.execute(
            """
            UPDATE news_publish_queue
            SET
                status = 'reserved',
                reserved_at = UTC_TIMESTAMP(),
                attempts = attempts + 1,
                last_error = NULL
            WHERE id = %s
              AND status = 'queued'
            LIMIT 1
            """,
            (queue_id,)
        )

        if cursor.rowcount != 1:
            db.rollback()
            return False

    db.commit()
    return True


def release_failed_job(db, queue_id, error):

    error = str(error)[:4000]

    with db.cursor() as cursor:

        cursor.execute(
            """
            UPDATE news_publish_queue
            SET
                status = CASE
                    WHEN attempts >= 5
                    THEN 'failed'
                    ELSE 'queued'
                END,
                reserved_at = NULL,
                last_error = %s
            WHERE id = %s
              AND status = 'reserved'
            LIMIT 1
            """,
            (
                error,
                queue_id
            )
        )

    db.commit()


def mark_published(
    db,
    queue_id,
    story_id,
    message_id
):

    with db.cursor() as cursor:

        cursor.execute(
            """
            UPDATE news_publish_queue
            SET
                status = 'published',
                published_at = UTC_TIMESTAMP(),
                telegram_message_id = %s,
                reserved_at = NULL,
                last_error = NULL
            WHERE id = %s
              AND status = 'reserved'
            LIMIT 1
            """,
            (
                message_id,
                queue_id
            )
        )

        if cursor.rowcount != 1:
            raise RuntimeError(
                "Could not mark queue row as published."
            )

        # A story is considered published once at least one
        # destination successfully publishes it.
        cursor.execute(
            """
            UPDATE news_stories
            SET
                status = 'published',
                updated_at = CURRENT_TIMESTAMP
            WHERE id = %s
            LIMIT 1
            """,
            (story_id,)
        )

    db.commit()


def update_publisher_success(db, publisher_id):

    with db.cursor() as cursor:
        cursor.execute(
            """
            UPDATE publishers
            SET
                health_status = 'healthy',
                flood_status = 'ok',
                flood_until = NULL,
                last_activity_at = UTC_TIMESTAMP(),
                last_error = NULL
            WHERE id = %s
            LIMIT 1
            """,
            (publisher_id,)
        )

    db.commit()


def update_publisher_error(
    db,
    publisher_id,
    error
):

    with db.cursor() as cursor:
        cursor.execute(
            """
            UPDATE publishers
            SET
                health_status = 'error',
                last_activity_at = UTC_TIMESTAMP(),
                last_error = %s
            WHERE id = %s
            LIMIT 1
            """,
            (
                str(error)[:4000],
                publisher_id
            )
        )

    db.commit()


def send_message(token, target, text):

    response = requests.post(
        f"https://api.telegram.org/bot{token}/sendMessage",
        data={
            "chat_id": target,
            "text": text,
            "disable_web_page_preview": True
        },
        timeout=30
    )

    try:
        payload = response.json()
    except Exception:
        raise RuntimeError(
            f"Telegram returned invalid JSON "
            f"(HTTP {response.status_code})"
        )

    if not payload.get("ok"):

        description = str(
            payload.get("description")
            or "Unknown Telegram error"
        )

        error_code = int(
            payload.get("error_code")
            or response.status_code
            or 0
        )

        raise RuntimeError(
            f"Telegram Bot API Error "
            f"{error_code}: {description}"
        )

    result = payload.get("result") or {}

    message_id = int(
        result.get("message_id") or 0
    )

    if message_id <= 0:
        raise RuntimeError(
            "Telegram returned no valid message_id."
        )

    return message_id


def publish_one(queue_id=None):

    db = base.db_connect()

    job = None

    try:

        job = get_job(
            db,
            queue_id
        )

        if not job:
            log("NO READY NEWS JOB")
            return 2

        queue_id = int(job["queue_id"])
        story_id = int(job["story_id"])
        channel_id = int(job["channel_id"])
        publisher_id = int(job["publisher_id"])

        publisher_type = str(
            job.get("publisher_type") or ""
        ).strip()

        if publisher_type != "telegram_bot":
            raise RuntimeError(
                f"Publisher #{publisher_id} "
                f"is not telegram_bot."
            )

        token = str(
            job.get("telegram_bot_token") or ""
        ).strip()

        if not token:
            raise RuntimeError(
                f"Publisher #{publisher_id} has no bot token."
            )

        body = str(
            job.get("body") or ""
        ).strip()

        if not body:
            raise RuntimeError(
                "Generated news body is empty."
            )

        target = telegram_target(job)

        log("")
        log("NEWS PUBLISHER")
        log("=" * 50)
        log(f"Queue ID:      {queue_id}")
        log(f"Story ID:      {story_id}")
        log(f"Channel ID:    {channel_id}")
        log(f"Target:        {target}")
        log(f"Publisher ID:  {publisher_id}")
        log(f"Publisher:     {job.get('publisher_name')}")
        log("-" * 50)
        log(body)
        log("-" * 50)

        if not reserve_job(
            db,
            queue_id
        ):
            raise RuntimeError(
                "Queue item could not be reserved."
            )

        try:

            message_id = send_message(
                token,
                target,
                body
            )

            mark_published(
                db,
                queue_id,
                story_id,
                message_id
            )

            update_publisher_success(
                db,
                publisher_id
            )

        except Exception as exc:

            release_failed_job(
                db,
                queue_id,
                exc
            )

            update_publisher_error(
                db,
                publisher_id,
                exc
            )

            raise

        log("NEWS PUBLISH SUCCESS ✅")
        log(f"Telegram Message ID: {message_id}")
        log("=" * 50)

        return 0

    except Exception as exc:

        log("")
        log("NEWS PUBLISH FAILED ❌")
        log(
            f"{type(exc).__name__}: {exc}"
        )

        return 1

    finally:
        db.close()


def main():

    queue_id = None

    if len(sys.argv) >= 2:
        queue_id = int(sys.argv[1])

    raise SystemExit(
        publish_one(queue_id)
    )


if __name__ == "__main__":
    main()
