#!/usr/bin/env python3

import asyncio
import json
import mimetypes
import os
import sys
import tempfile
from datetime import datetime
from pathlib import Path

import requests
from telethon.errors import FloodWaitError

import content_pool_publisher as base


DEFAULT_READER_PUBLISHER_ID = 1


def fail(message, code=1):
    print("")
    print("❌ ERROR")
    print(message)
    raise SystemExit(code)


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


def remaining_flood_seconds(publisher):
    flood_until = publisher.get("flood_until")

    if not flood_until:
        return 0

    try:
        if isinstance(flood_until, str):
            flood_until = datetime.strptime(
                flood_until,
                "%Y-%m-%d %H:%M:%S"
            )

        return max(
            0,
            int(
                (
                    flood_until
                    - datetime.utcnow()
                ).total_seconds()
            )
        )

    except Exception:
        return 0


def mark_reader_flood(
    db,
    publisher_id,
    seconds
):
    seconds = max(1, int(seconds))

    with db.cursor() as cursor:
        cursor.execute(
            """
            UPDATE publishers
            SET
                flood_status = 'blocked',
                flood_until =
                    DATE_ADD(
                        UTC_TIMESTAMP(),
                        INTERVAL %s SECOND
                    ),
                last_activity_at = UTC_TIMESTAMP(),
                last_error = %s
            WHERE id = %s
            LIMIT 1
            """,
            (
                seconds,
                "Telegram Reader FloodWait: "
                + str(seconds)
                + " seconds",
                publisher_id
            )
        )


def reader_blocked_fail(
    publisher_id,
    seconds
):
    fail(
        "MEDIA_READER_BLOCKED: "
        + str(int(seconds))
        + " seconds; Publisher ID: "
        + str(int(publisher_id)),
        75
    )


async def safe_reader_call(
    db,
    publisher_id,
    awaitable
):
    try:
        return await awaitable

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

        mark_reader_flood(
            db,
            publisher_id,
            seconds
        )

        reader_blocked_fail(
            publisher_id,
            seconds
        )


def bot_target(channel):
    chat_id = str(
        channel.get("telegram_chat_id")
        or ""
    ).strip()

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

    if chat_id:
        return chat_id

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

    fail("Destination channel has no Telegram target.")


def bot_request(token, method, data=None, files=None):
    url = f"https://api.telegram.org/bot{token}/{method}"

    try:
        response = requests.post(
            url,
            data=data or {},
            files=files,
            timeout=120
        )
    except Exception as exc:
        fail(f"Bot API request failed: {exc}")

    try:
        payload = response.json()
    except Exception:
        fail(
            f"Bot API returned invalid response. "
            f"HTTP {response.status_code}"
        )

    if not payload.get("ok"):

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

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

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

        retry_after = int(
            parameters.get("retry_after")
            or 0
        )

        if error_code == 429 and retry_after > 0:
            fail(
                f"Telegram FloodWait: {retry_after} seconds"
            )

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

    return payload["result"]


def detect_bot_method(path, media_type=None):
    media_type = str(media_type or "").lower()
    suffix = Path(path).suffix.lower()

    if (
        "photo" in media_type
        or "image" in media_type
        or suffix in {
            ".jpg",
            ".jpeg",
            ".png",
            ".webp"
        }
    ):
        return "sendPhoto", "photo"

    if (
        "video" in media_type
        or suffix in {
            ".mp4",
            ".mov",
            ".m4v"
        }
    ):
        return "sendVideo", "video"

    if (
        "animation" in media_type
        or suffix in {
            ".gif"
        }
    ):
        return "sendAnimation", "animation"

    return "sendDocument", "document"


def send_text(token, target, text):
    return bot_request(
        token,
        "sendMessage",
        data={
            "chat_id": target,
            "text": text
        }
    )


def send_single_file(
    token,
    target,
    file_path,
    caption,
    media_type=None
):
    method, field = detect_bot_method(
        file_path,
        media_type
    )

    mime = (
        mimetypes.guess_type(file_path)[0]
        or "application/octet-stream"
    )

    with open(file_path, "rb") as fh:
        return bot_request(
            token,
            method,
            data={
                "chat_id": target,
                "caption": caption or ""
            },
            files={
                field: (
                    Path(file_path).name,
                    fh,
                    mime
                )
            }
        )


def send_media_group(
    token,
    target,
    items,
    caption
):
    """
    Send a real Telegram media group.

    items:
        [
            {
                "path": "/tmp/file.jpg",
                "media_type": "photo"
            },
            {
                "path": "/tmp/file.mp4",
                "media_type": "video"
            }
        ]

    Telegram Bot API media groups support photo/video.
    """

    if len(items) < 2:
        fail(
            "send_media_group requires at least 2 media items."
        )

    media = []
    files = {}
    handles = []

    try:

        for index, item in enumerate(items):

            path = str(item["path"])

            method, _ = detect_bot_method(
                path,
                item.get("media_type")
            )

            if method == "sendPhoto":
                media_kind = "photo"

            elif method == "sendVideo":
                media_kind = "video"

            else:
                fail(
                    "Album contains unsupported media type "
                    "for sendMediaGroup: "
                    + str(item.get("media_type"))
                )

            attach_name = f"media_{index}"

            mime = (
                mimetypes.guess_type(path)[0]
                or "application/octet-stream"
            )

            fh = open(path, "rb")
            handles.append(fh)

            files[attach_name] = (
                Path(path).name,
                fh,
                mime
            )

            media_item = {
                "type": media_kind,
                "media": f"attach://{attach_name}",
            }

            # Telegram album caption belongs on first item.
            if index == 0 and caption:
                media_item["caption"] = caption

            media.append(media_item)

        result = bot_request(
            token,
            "sendMediaGroup",
            data={
                "chat_id": target,
                "media": json.dumps(
                    media,
                    ensure_ascii=False
                ),
            },
            files=files
        )

        if not isinstance(result, list):
            fail(
                "Telegram sendMediaGroup returned "
                "an invalid result."
            )

        message_ids = []

        for message in result:

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

            if message_id <= 0:
                fail(
                    "Telegram sendMediaGroup returned "
                    "an invalid message_id."
                )

            message_ids.append(message_id)

        if len(message_ids) != len(items):
            fail(
                "Telegram sendMediaGroup result count "
                "does not match media count."
            )

        return message_ids

    finally:

        for fh in handles:
            try:
                fh.close()
            except Exception:
                pass


async def download_message_media(
    reader,
    message,
    temp_dir
):
    if (
        message is None
        or getattr(message, "media", None) is None
    ):
        fail("Original Telegram media not found.")

    path = await reader.download_media(
        message,
        file=temp_dir
    )

    if not path:
        fail("Telegram media download failed.")

    return str(path)


async def main():
    if len(sys.argv) != 4:
        print(
            "Usage:\n"
            "  python3 engine/content_pool_bot_publisher.py "
            "<post_id> <channel_id> <bot_publisher_id>"
        )
        raise SystemExit(1)

    post_id = int(sys.argv[1])
    channel_id = int(sys.argv[2])
    bot_publisher_id = int(sys.argv[3])

    db = base.get_db()
    reader = None

    try:
        post = base.get_post(
            db,
            post_id
        )

        if not post:
            fail(f"Post #{post_id} not found.")

        channel = base.get_channel(
            db,
            channel_id
        )

        if not channel:
            fail(
                f"Active Channel #{channel_id} not found."
            )

        bot_publisher = get_publisher_by_id(
            db,
            bot_publisher_id
        )

        if not bot_publisher:
            fail(
                f"Bot Publisher #{bot_publisher_id} "
                f"not found or inactive."
            )

        if (
            str(
                bot_publisher.get("publisher_type")
                or ""
            )
            != "telegram_bot"
        ):
            fail(
                f"Publisher #{bot_publisher_id} "
                f"is not telegram_bot."
            )

        bot_token = str(
            bot_publisher.get(
                "telegram_bot_token"
            )
            or ""
        ).strip()

        if not bot_token:
            fail("Bot token is empty.")

        target = bot_target(channel)

        caption = base.build_destination_caption(
            post,
            channel
        )

        print("")
        print("CONTENT BOT PUBLISHER")
        print("==============================================")
        print(f"Post ID:          {post_id}")
        print(f"Channel ID:       {channel_id}")
        print(f"Target:           {target}")
        print(f"Bot Publisher ID: {bot_publisher_id}")
        print(
            f"Media Type:       "
            f"{post.get('media_type') or 'TEXT'}"
        )
        print("----------------------------------------------")

        # --------------------------------------------------
        # TEXT ONLY
        # --------------------------------------------------

        if not post.get("media_type"):

            if not caption:
                fail(
                    "Post has neither text nor media."
                )

            result = send_text(
                bot_token,
                target,
                caption
            )

            message_id = int(
                result["message_id"]
            )

            print("CONTENT BOT PUBLISH SUCCESS ✅")
            print(f"Message ID:       {message_id}")
            print("==============================================")
            return

        # --------------------------------------------------
        # MEDIA REQUIRES READER ACCOUNT
        # --------------------------------------------------

        reader_publisher_id = int(
            bot_publisher.get(
                "reader_publisher_id"
            )
            or DEFAULT_READER_PUBLISHER_ID
        )

        reader_publisher = get_publisher_by_id(
            db,
            reader_publisher_id
        )

        if not reader_publisher:
            fail(
                "Media Reader Publisher #"
                + str(reader_publisher_id)
                + " not found or inactive."
            )

        reader_wait = remaining_flood_seconds(
            reader_publisher
        )

        if (
            str(
                reader_publisher.get(
                    "flood_status"
                )
                or ""
            ) == "blocked"
            and reader_wait > 0
        ):
            reader_blocked_fail(
                reader_publisher_id,
                reader_wait
            )

        reader = base.build_client(
            reader_publisher
        )

        await safe_reader_call(
            db,
            reader_publisher_id,
            reader.connect()
        )

        authorized = await safe_reader_call(
            db,
            reader_publisher_id,
            reader.is_user_authorized()
        )

        if not authorized:
            fail(
                "Reader Telegram session "
                "is not authorized."
            )

        with tempfile.TemporaryDirectory(
            prefix="social_bot_publish_"
        ) as temp_dir:

            # ----------------------------------------------
            # ALBUM
            # ----------------------------------------------

            if post.get("telegram_grouped_id"):

                album_posts = base.get_album_posts(
                    db,
                    post
                )

                if not album_posts:
                    fail(
                        "Album grouped_id exists but "
                        "no album rows were found."
                    )

                source_entity = await safe_reader_call(
                    db,
                    reader_publisher_id,
                    base.resolve_source_entity(
                        reader,
                        post
                    )
                )

                message_ids = [
                    int(row["source_message_id"])
                    for row in album_posts
                    if row.get("source_message_id")
                ][:10]

                originals = await safe_reader_call(
                    db,
                    reader_publisher_id,
                    reader.get_messages(
                        source_entity,
                        ids=message_ids
                    )
                )

                if not isinstance(
                    originals,
                    (list, tuple)
                ):
                    originals = [originals]

                # ------------------------------------------
                # REAL TELEGRAM MEDIA GROUP
                # ------------------------------------------

                album_items = []

                for index, original in enumerate(
                    originals
                ):
                    if (
                        original is None
                        or getattr(
                            original,
                            "media",
                            None
                        ) is None
                    ):
                        continue

                    path = await safe_reader_call(
                        db,
                        reader_publisher_id,
                        download_message_media(
                            reader,
                            original,
                            temp_dir
                        )
                    )

                    row_media_type = None

                    if index < len(album_posts):
                        row_media_type = (
                            album_posts[index].get(
                                "media_type"
                            )
                        )

                    method, _ = detect_bot_method(
                        path,
                        row_media_type
                    )

                    # Telegram sendMediaGroup supports
                    # photo/video together for this use case.
                    if method not in (
                        "sendPhoto",
                        "sendVideo"
                    ):
                        continue

                    album_items.append(
                        {
                            "path": path,
                            "media_type": row_media_type,
                        }
                    )

                if not album_items:
                    fail(
                        "No supported album media could be sent."
                    )

                # If only one valid item survived, send it normally.
                if len(album_items) == 1:

                    item = album_items[0]

                    result = send_single_file(
                        bot_token,
                        target,
                        item["path"],
                        caption,
                        item.get("media_type")
                    )

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

                    if message_id <= 0:
                        fail(
                            "Telegram returned invalid "
                            "message_id for album fallback."
                        )

                    print(
                        "CONTENT BOT PUBLISH SUCCESS ✅"
                    )
                    print(
                        f"Message ID:       {message_id}"
                    )
                    print(
                        "=============================================="
                    )
                    return

                sent_ids = send_media_group(
                    bot_token,
                    target,
                    album_items[:10],
                    caption
                )

                print(
                    "CONTENT BOT PUBLISH SUCCESS ✅"
                )
                print(
                    "Message IDs:      "
                    + ",".join(
                        str(x)
                        for x in sent_ids
                    )
                )
                print("==============================================")
                return

            # ----------------------------------------------
            # SINGLE MEDIA
            # ----------------------------------------------

            original = await safe_reader_call(
                db,
                reader_publisher_id,
                base.load_original_message(
                    reader,
                    post
                )
            )

            path = await safe_reader_call(
                db,
                reader_publisher_id,
                download_message_media(
                    reader,
                    original,
                    temp_dir
                )
            )

            result = send_single_file(
                bot_token,
                target,
                path,
                caption,
                post.get("media_type")
            )

            message_id = int(
                result["message_id"]
            )

            print("CONTENT BOT PUBLISH SUCCESS ✅")
            print(f"Message ID:       {message_id}")
            print("==============================================")

    finally:

        if reader is not None:
            try:
                await reader.disconnect()
            except Exception:
                pass

        db.close()


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