#!/usr/bin/env python3

import sys
import re
import asyncio
from pathlib import Path

import pymysql
from telethon import TelegramClient
from telethon.errors import FloodWaitError


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


def fail(message: str, code: int = 1):
    print("")
    print("❌ ERROR")
    print(message)
    sys.exit(code)


def read_php_database_config():
    if not DB_CONFIG.exists():
        fail(f"Database config not found: {DB_CONFIG}")

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

    patterns = {
        "host": r"\$dbHost\s*=\s*['\"]([^'\"]+)['\"]",
        "name": 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:
            fail(
                f"Could not read '{key}' "
                f"from config/database.php"
            )

        result[key] = match.group(1)

    return result


def get_database():
    cfg = read_php_database_config()

    try:
        return pymysql.connect(
            host=cfg["host"],
            user=cfg["user"],
            password=cfg["password"],
            database=cfg["name"],
            charset="utf8mb4",
            cursorclass=pymysql.cursors.DictCursor,
            autocommit=True,
        )

    except Exception as exc:
        fail(f"Database connection failed: {exc}")


def get_publisher(db, publisher_id: int):
    with db.cursor() as cursor:
        cursor.execute(
            """
            SELECT *
            FROM publishers
            WHERE id = %s
            LIMIT 1
            """,
            (publisher_id,)
        )
        return cursor.fetchone()


def get_channel(db, channel_id: int):
    with db.cursor() as cursor:
        cursor.execute(
            """
            SELECT *
            FROM channels
            WHERE id = %s
            LIMIT 1
            """,
            (channel_id,)
        )
        return cursor.fetchone()


def get_daily_post(db, daily_post_id: int):
    with db.cursor() as cursor:
        cursor.execute(
            """
            SELECT *
            FROM football_daily_posts
            WHERE id = %s
            LIMIT 1
            """,
            (daily_post_id,)
        )
        return cursor.fetchone()


def update_publisher_health(
    db,
    publisher_id: int,
    health_status: str,
    last_error=None
):
    with db.cursor() as cursor:
        cursor.execute(
            """
            UPDATE publishers
            SET
                health_status = %s,
                last_error = %s,
                last_activity_at = NOW()
            WHERE id = %s
            """,
            (
                health_status,
                last_error,
                publisher_id
            )
        )


def update_flood(
    db,
    publisher_id: int,
    seconds: int
):
    with db.cursor() as cursor:
        cursor.execute(
            """
            UPDATE publishers
            SET
                flood_status = 'flood',
                flood_until = DATE_ADD(NOW(), INTERVAL %s SECOND),
                health_status = 'limited',
                last_error = %s,
                last_activity_at = NOW()
            WHERE id = %s
            """,
            (
                seconds,
                f"Telegram FloodWait: {seconds} seconds",
                publisher_id
            )
        )


def clear_flood(db, publisher_id: int):
    with db.cursor() as cursor:
        cursor.execute(
            """
            UPDATE publishers
            SET
                flood_status = 'ok',
                flood_until = NULL
            WHERE id = %s
            """,
            (publisher_id,)
        )


def write_log(
    db,
    publisher_id: int,
    channel_id: int,
    action: str,
    status: str,
    telegram_message_id=None,
    error_message=None
):
    with db.cursor() as cursor:
        cursor.execute(
            """
            INSERT INTO publisher_logs
            (
                publisher_id,
                channel_id,
                action,
                status,
                telegram_message_id,
                error_message,
                created_at
            )
            VALUES
            (
                %s,
                %s,
                %s,
                %s,
                %s,
                %s,
                NOW()
            )
            """,
            (
                publisher_id,
                channel_id,
                action,
                status,
                telegram_message_id,
                error_message
            )
        )


def resolve_target(channel):
    username = str(
        channel.get("username") or ""
    ).strip()

    telegram_chat_id = str(
        channel.get("telegram_chat_id") or ""
    ).strip()

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

    if telegram_chat_id:
        try:
            return int(telegram_chat_id)
        except ValueError:
            return telegram_chat_id

    fail(
        "Channel has neither username "
        "nor telegram_chat_id."
    )


def build_client(publisher_id: int, publisher):
    api_id_raw = str(
        publisher.get("telegram_api_id") or ""
    ).strip()

    api_hash = str(
        publisher.get("telegram_api_hash") or ""
    ).strip()

    session_name = str(
        publisher.get("session_name") or ""
    ).strip()

    if not api_id_raw:
        fail("Telegram API ID is missing.")

    try:
        api_id = int(api_id_raw)
    except ValueError:
        fail("Telegram API ID must be numeric.")

    if not api_hash:
        fail("Telegram API Hash is missing.")

    if not session_name:
        session_name = f"publisher_{publisher_id}"

    session_name = re.sub(
        r"[^a-zA-Z0-9_-]",
        "_",
        session_name
    )

    session_path = SESSION_DIR / session_name

    return TelegramClient(
        str(session_path),
        api_id,
        api_hash
    )


def validate_assignment(
    publisher_id: int,
    publisher,
    channel
):
    if not publisher:
        fail(
            f"Publisher #{publisher_id} not found."
        )

    if int(publisher["is_active"]) != 1:
        fail("Publisher is inactive.")

    if publisher["publisher_type"] != "telegram_user":
        fail(
            "This engine currently expects "
            "a Telegram User publisher."
        )

    if not channel:
        fail("Channel not found.")

    if int(channel["is_active"]) != 1:
        fail("Channel is inactive.")

    assigned_publisher = (
        int(channel["publisher_id"])
        if channel.get("publisher_id")
        else 0
    )

    if assigned_publisher != publisher_id:
        fail(
            f"Channel #{channel['id']} is not assigned "
            f"to Publisher #{publisher_id}."
        )


async def ensure_authorized(
    db,
    publisher_id: int,
    client
):
    await client.connect()

    if not await client.is_user_authorized():
        update_publisher_health(
            db,
            publisher_id,
            "error",
            "Telegram session is not authorized."
        )

        fail(
            "Telegram session is not authorized. "
            "Run publisher_login.py again."
        )


async def test_send(
    publisher_id: int,
    channel_id: int
):
    db = get_database()
    client = None

    try:
        publisher = get_publisher(
            db,
            publisher_id
        )

        channel = get_channel(
            db,
            channel_id
        )

        validate_assignment(
            publisher_id,
            publisher,
            channel
        )

        target = resolve_target(channel)

        client = build_client(
            publisher_id,
            publisher
        )

        await ensure_authorized(
            db,
            publisher_id,
            client
        )

        try:
            message = await client.send_message(
                target,
                "🧪 تست سیستم انتشار\n\n"
                "اتصال Publisher به کانال با موفقیت انجام شد. ✅"
            )

        except FloodWaitError as exc:
            seconds = int(exc.seconds)

            update_flood(
                db,
                publisher_id,
                seconds
            )

            write_log(
                db,
                publisher_id,
                channel_id,
                "test_send",
                "failed",
                None,
                f"FloodWait: {seconds} seconds"
            )

            fail(
                f"Telegram FloodWait: "
                f"{seconds} seconds"
            )

        message_id = str(message.id)

        clear_flood(
            db,
            publisher_id
        )

        update_publisher_health(
            db,
            publisher_id,
            "healthy",
            None
        )

        write_log(
            db,
            publisher_id,
            channel_id,
            "test_send",
            "success",
            message_id,
            None
        )

        print(
            f"TEST_SEND_SUCCESS "
            f"message_id={message_id}"
        )

    finally:
        if client is not None:
            try:
                if client.is_connected():
                    await client.disconnect()
            except Exception:
                pass

        db.close()


async def publish_daily_post(
    publisher_id: int,
    channel_id: int,
    daily_post_id: int,
    message_file: str
):
    db = get_database()
    client = None

    try:
        publisher = get_publisher(
            db,
            publisher_id
        )

        channel = get_channel(
            db,
            channel_id
        )

        validate_assignment(
            publisher_id,
            publisher,
            channel
        )

        daily_post = get_daily_post(
            db,
            daily_post_id
        )

        if not daily_post:
            fail(
                f"Daily Post #{daily_post_id} not found."
            )

        if int(daily_post["channel_id"]) != channel_id:
            fail(
                "Daily Post does not belong "
                "to the requested channel."
            )

        message_path = Path(message_file)

        if not message_path.is_file():
            fail(
                f"Message file not found: "
                f"{message_path}"
            )

        message_text = message_path.read_text(
            encoding="utf-8"
        ).strip()

        if not message_text:
            fail("Daily Post text is empty.")

        target = resolve_target(channel)

        client = build_client(
            publisher_id,
            publisher
        )

        await ensure_authorized(
            db,
            publisher_id,
            client
        )

        existing_message_id = (
            int(daily_post["telegram_message_id"])
            if daily_post.get("telegram_message_id")
            else 0
        )

        try:

            if existing_message_id > 0:

                edited = await client.edit_message(
                    target,
                    existing_message_id,
                    message_text
                )

                message_id = str(
                    getattr(
                        edited,
                        "id",
                        existing_message_id
                    )
                )

                with db.cursor() as cursor:
                    cursor.execute(
                        """
                        UPDATE football_daily_posts
                        SET
                            status =
                                CASE
                                    WHEN status = 'pinned'
                                    THEN 'pinned'
                                    ELSE 'published'
                                END,
                            last_edited_at = NOW()
                        WHERE id = %s
                        LIMIT 1
                        """,
                        (daily_post_id,)
                    )

                action = "daily_post_edit"
                result_label = "DAILY_POST_EDITED"

            else:

                sent = await client.send_message(
                    target,
                    message_text
                )

                message_id = str(sent.id)

                with db.cursor() as cursor:
                    cursor.execute(
                        """
                        UPDATE football_daily_posts
                        SET
                            telegram_message_id = %s,
                            status = 'published',
                            published_at = NOW(),
                            last_edited_at = NOW()
                        WHERE id = %s
                        LIMIT 1
                        """,
                        (
                            message_id,
                            daily_post_id
                        )
                    )

                action = "daily_post_send"
                result_label = "DAILY_POST_SENT"

        except FloodWaitError as exc:

            seconds = int(exc.seconds)

            update_flood(
                db,
                publisher_id,
                seconds
            )

            write_log(
                db,
                publisher_id,
                channel_id,
                "daily_post",
                "failed",
                (
                    str(existing_message_id)
                    if existing_message_id
                    else None
                ),
                f"FloodWait: {seconds} seconds"
            )

            fail(
                f"Telegram FloodWait: "
                f"{seconds} seconds"
            )

        clear_flood(
            db,
            publisher_id
        )

        update_publisher_health(
            db,
            publisher_id,
            "healthy",
            None
        )

        write_log(
            db,
            publisher_id,
            channel_id,
            action,
            "success",
            message_id,
            None
        )

        print(
            f"{result_label} "
            f"message_id={message_id}"
        )

    except SystemExit:
        raise

    except Exception as exc:
        error_text = str(exc)

        try:
            update_publisher_health(
                db,
                publisher_id,
                "error",
                error_text[:2000]
            )

            write_log(
                db,
                publisher_id,
                channel_id,
                "daily_post",
                "failed",
                None,
                error_text[:2000]
            )
        except Exception:
            pass

        print("")
        print("❌ DAILY POST PUBLISH FAILED")
        print(error_text)
        sys.exit(1)

    finally:
        if client is not None:
            try:
                if client.is_connected():
                    await client.disconnect()
            except Exception:
                pass

        db.close()


async def main():

    if len(sys.argv) < 2:
        fail(
            "Usage:\n"
            "python3 engine/telegram_publisher.py "
            "test <publisher_id> <channel_id>\n"
            "python3 engine/telegram_publisher.py "
            "daily_post <publisher_id> <channel_id> "
            "<daily_post_id> <message_file>"
        )

    command = sys.argv[1].lower()

    if command == "test":

        if len(sys.argv) < 4:
            fail(
                "Usage:\n"
                "python3 engine/telegram_publisher.py "
                "test <publisher_id> <channel_id>"
            )

        try:
            publisher_id = int(sys.argv[2])
            channel_id = int(sys.argv[3])

        except ValueError:
            fail(
                "Publisher ID and Channel ID "
                "must be numeric."
            )

        await test_send(
            publisher_id,
            channel_id
        )

        return

    if command == "daily_post":

        if len(sys.argv) < 6:
            fail(
                "Usage:\n"
                "python3 engine/telegram_publisher.py "
                "daily_post <publisher_id> <channel_id> "
                "<daily_post_id> <message_file>"
            )

        try:
            publisher_id = int(sys.argv[2])
            channel_id = int(sys.argv[3])
            daily_post_id = int(sys.argv[4])

        except ValueError:
            fail(
                "Publisher ID, Channel ID and "
                "Daily Post ID must be numeric."
            )

        await publish_daily_post(
            publisher_id,
            channel_id,
            daily_post_id,
            sys.argv[5]
        )

        return

    fail(
        f"Unknown command: {command}"
    )


if __name__ == "__main__":

    try:
        asyncio.run(main())

    except KeyboardInterrupt:
        print("\nCancelled.")
