#!/usr/bin/env python3

import re
import time
import signal
from datetime import datetime
from pathlib import Path

import pymysql


BASE_DIR = Path(__file__).resolve().parent.parent
DB_CONFIG = BASE_DIR / "config" / "database.php"

POLL_SECONDS = 5
BATCH_LIMIT = 100

running = True


def log(message):
    print(
        f"[{datetime.now().strftime('%Y-%m-%d %H:%M:%S')}] "
        f"{message}",
        flush=True
    )


def stop_handler(signum, frame):
    global running
    running = False
    log("STOP REQUESTED")


signal.signal(signal.SIGTERM, stop_handler)
signal.signal(signal.SIGINT, stop_handler)


def load_db_config():

    content = DB_CONFIG.read_text(
        encoding="utf-8",
        errors="ignore"
    )

    patterns = {
        "host": r"\$dbHost\s*=\s*['\"]([^'\"]+)['\"]",
        "database": r"\$dbName\s*=\s*['\"]([^'\"]+)['\"]",
        "user": r"\$dbUser\s*=\s*['\"]([^'\"]+)['\"]",
        "password": r"\$dbPass\s*=\s*['\"]([^'\"]*)['\"]",
    }

    result = {}

    for key, pattern in patterns.items():

        match = re.search(
            pattern,
            content
        )

        if not match:
            raise RuntimeError(
                f"Could not read DB config: {key}"
            )

        result[key] = match.group(1)

    result["charset"] = "utf8mb4"
    result["cursorclass"] = pymysql.cursors.DictCursor
    result["autocommit"] = False

    return result


def get_start_time(db):

    with db.cursor() as cursor:

        cursor.execute(
            """
            SELECT setting_value
            FROM news_settings
            WHERE setting_key = 'fast_publish_started_at'
            LIMIT 1
            """
        )

        row = cursor.fetchone()

    if not row:
        raise RuntimeError(
            "fast_publish_started_at setting is missing"
        )

    return row["setting_value"]


def load_channels(db):

    with db.cursor() as cursor:

        cursor.execute(
            """
            SELECT
                c.id AS channel_id,
                c.publisher_id,
                nc.style_id,
                nc.breaking_enabled,
                nc.normal_enabled,
                nc.updates_enabled
            FROM channels c
            JOIN news_channels nc
                ON nc.channel_id = c.id
            WHERE c.channel_type = 'news'
              AND c.is_active = 1
              AND c.auto_publish = 1
              AND nc.is_active = 1
              AND nc.auto_publish = 1
            ORDER BY c.id
            """
        )

        return cursor.fetchall()


def load_new_stories(db, start_time):

    with db.cursor() as cursor:

        cursor.execute(
            """
            SELECT
                id,
                story_type,
                first_seen_at,
                last_seen_at
            FROM news_stories
            WHERE first_seen_at >= %s
              AND status = 'ready'
            ORDER BY first_seen_at ASC
            LIMIT %s
            """,
            (
                start_time,
                BATCH_LIMIT
            )
        )

        return cursor.fetchall()


def type_allowed(channel, story_type):

    story_type = (
        story_type or "normal"
    ).lower()

    if story_type == "breaking":
        return bool(
            channel["breaking_enabled"]
        )

    if story_type == "update":
        return bool(
            channel["updates_enabled"]
        )

    return bool(
        channel["normal_enabled"]
    )


def dispatch_once(db):

    start_time = get_start_time(db)
    channels = load_channels(db)

    if not channels:
        return 0

    stories = load_new_stories(
        db,
        start_time
    )

    inserted = 0

    with db.cursor() as cursor:

        for story in stories:

            publish_type = (
                story["story_type"]
                or "normal"
            )

            for channel in channels:

                if not type_allowed(
                    channel,
                    publish_type
                ):
                    continue

                cursor.execute(
                    """
                    INSERT IGNORE INTO news_generated_content
                    (
                        story_id,
                        style_id,
                        generation_status
                    )
                    VALUES
                    (
                        %s,
                        %s,
                        'pending'
                    )
                    """,
                    (
                        story["id"],
                        channel["style_id"]
                    )
                )

                cursor.execute(
                    """
                    SELECT id
                    FROM news_generated_content
                    WHERE story_id = %s
                      AND style_id = %s
                    LIMIT 1
                    """,
                    (
                        story["id"],
                        channel["style_id"]
                    )
                )

                generated = cursor.fetchone()

                if not generated:
                    raise RuntimeError(
                        "Generated content row "
                        "could not be resolved"
                    )

                cursor.execute(
                    """
                    INSERT IGNORE INTO news_publish_queue
                    (
                        story_id,
                        channel_id,
                        style_id,
                        generated_content_id,
                        publish_type,
                        status,
                        scheduled_at
                    )
                    VALUES
                    (
                        %s,
                        %s,
                        %s,
                        %s,
                        %s,
                        'queued',
                        UTC_TIMESTAMP()
                    )
                    """,
                    (
                        story["id"],
                        channel["channel_id"],
                        channel["style_id"],
                        generated["id"],
                        publish_type
                    )
                )

                if cursor.rowcount == 1:

                    inserted += 1

                    log(
                        f"QUEUED story={story['id']} "
                        f"channel={channel['channel_id']} "
                        f"type={publish_type}"
                    )

    db.commit()

    return inserted


def main():

    log("NEWS FAST DISPATCHER STARTED ✅")
    log(
        f"Poll interval: {POLL_SECONDS}s"
    )

    db = None

    while running:

        try:

            if db is None:
                db = pymysql.connect(
                    **load_db_config()
                )

            db.ping(
                reconnect=True
            )

            inserted = dispatch_once(db)

            if inserted:
                log(
                    f"DISPATCHED: {inserted}"
                )

        except Exception as exc:

            log(
                f"ERROR: {type(exc).__name__}: {exc}"
            )

            if db is not None:
                try:
                    db.rollback()
                    db.close()
                except Exception:
                    pass

            db = None

        for _ in range(POLL_SECONDS):

            if not running:
                break

            time.sleep(1)

    if db is not None:
        try:
            db.close()
        except Exception:
            pass

    log("NEWS FAST DISPATCHER STOPPED")


if __name__ == "__main__":
    main()
