#!/usr/bin/env python3

import asyncio
import re
import sys
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"

PUBLISHER_ID = 1

# Telegram message IDs fetched per request batch.
BATCH_SIZE = 100


def fail(message):
    print("")
    print("❌ ERROR")
    print(message)
    sys.exit(1)


def read_php_database_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} "
                "from config/database.php"
            )

        result[key] = match.group(1)

    return result


def get_database():
    cfg = read_php_database_config()

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


def get_publisher(db):
    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 build_client(publisher):
    api_id = int(
        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 f"publisher_{PUBLISHER_ID}"
    ).strip()

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

    return TelegramClient(
        str(SESSION_DIR / session_name),
        api_id,
        api_hash
    )


def get_group(db, group_key):
    with db.cursor() as cursor:
        cursor.execute(
            """
            SELECT id, group_key
            FROM content_groups
            WHERE group_key = %s
            LIMIT 1
            """,
            (group_key,)
        )

        return cursor.fetchone()


def get_sources(db, group_id):
    with db.cursor() as cursor:
        cursor.execute(
            """
            SELECT DISTINCT
                s.id,
                s.username,
                s.name
            FROM source_group_sources sgs
            INNER JOIN sources s
                ON s.id = sgs.source_id
            WHERE sgs.group_id = %s
              AND sgs.source_role = 'approved'
              AND sgs.is_active = 1
              AND s.is_active = 1
              AND s.source_kind = 'telegram'
            ORDER BY s.id ASC
            """,
            (group_id,)
        )

        return cursor.fetchall()


def get_missing_posts(db, group_id, source_id):
    with db.cursor() as cursor:
        cursor.execute(
            """
            SELECT
                p.id,
                p.source_message_id
            FROM content_group_posts cgp
            INNER JOIN posts p
                ON p.id = cgp.post_id
            WHERE cgp.group_id = %s
              AND p.source_id = %s
              AND p.source_message_id IS NOT NULL
              AND p.telegram_grouped_id IS NULL
            ORDER BY p.source_message_id DESC
            """,
            (
                group_id,
                source_id
            )
        )

        return cursor.fetchall()


def chunks(items, size):
    for i in range(0, len(items), size):
        yield items[i:i + size]


async def process_source(
    client,
    db,
    group_id,
    source
):
    source_id = int(source["id"])

    username = str(
        source.get("username")
        or ""
    ).strip().lstrip("@")

    if not username:
        print(
            f"[SOURCE #{source_id}] "
            "SKIPPED - no username"
        )

        return {
            "checked": 0,
            "album_messages": 0,
            "albums": set(),
            "missing": 0,
        }

    rows = get_missing_posts(
        db,
        group_id,
        source_id
    )

    print("")
    print(
        f"[SOURCE #{source_id}] "
        f"@{username}"
    )
    print(
        f"  Missing Grouped ID: {len(rows)}"
    )

    if not rows:
        return {
            "checked": 0,
            "album_messages": 0,
            "albums": set(),
            "missing": 0,
        }

    entity = await client.get_entity(
        username
    )

    row_by_message_id = {
        int(row["source_message_id"]): row
        for row in rows
    }

    message_ids = list(
        row_by_message_id.keys()
    )

    checked = 0
    album_messages = 0
    missing = 0
    albums = set()

    for batch in chunks(
        message_ids,
        BATCH_SIZE
    ):
        try:
            messages = await client.get_messages(
                entity,
                ids=batch
            )

        except FloodWaitError as exc:
            wait_seconds = int(
                getattr(exc, "seconds", 0)
                or 0
            )

            print(
                f"  FloodWait:     "
                f"{wait_seconds}s"
            )

            await asyncio.sleep(
                wait_seconds + 2
            )

            messages = await client.get_messages(
                entity,
                ids=batch
            )

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

        for requested_id, message in zip(
            batch,
            messages
        ):
            checked += 1

            if not message:
                missing += 1
                continue

            grouped_id = getattr(
                message,
                "grouped_id",
                None
            )

            # NULL is intentionally left unchanged for
            # ordinary non-album messages.
            if not grouped_id:
                continue

            grouped_id = int(
                grouped_id
            )

            post_row = row_by_message_id.get(
                int(requested_id)
            )

            if not post_row:
                continue

            with db.cursor() as cursor:
                cursor.execute(
                    """
                    UPDATE posts
                    SET telegram_grouped_id = %s
                    WHERE id = %s
                    LIMIT 1
                    """,
                    (
                        grouped_id,
                        int(post_row["id"])
                    )
                )

            album_messages += 1
            albums.add(grouped_id)

        print(
            f"  Progress:      "
            f"{checked}/{len(message_ids)}"
        )

    print(
        f"  Album Messages: {album_messages}"
    )
    print(
        f"  Albums Found:   {len(albums)}"
    )

    if missing:
        print(
            f"  Missing Telegram: {missing}"
        )

    return {
        "checked": checked,
        "album_messages": album_messages,
        "albums": albums,
        "missing": missing,
    }


async def main():
    group_key = (
        sys.argv[1].strip().lower()
        if len(sys.argv) > 1
        else "deep"
    )

    db = get_database()
    client = None

    try:
        group = get_group(
            db,
            group_key
        )

        if not group:
            fail(
                f"Group not found: "
                f"{group_key}"
            )

        group_id = int(
            group["id"]
        )

        publisher = get_publisher(
            db
        )

        if not publisher:
            fail(
                "Active publisher not found."
            )

        sources = get_sources(
            db,
            group_id
        )

        print("")
        print(
            "ALBUM GROUPED ID BACKFILL V1.0"
        )
        print(
            "=============================================="
        )
        print(
            f"Group:          {group_key}"
        )
        print(
            f"Sources:        {len(sources)}"
        )
        print(
            f"Batch:          {BATCH_SIZE}"
        )
        print(
            "=============================================="
        )

        if not sources:
            print(
                "No approved sources."
            )
            return

        client = build_client(
            publisher
        )

        await client.connect()

        if not await client.is_user_authorized():
            fail(
                "Telegram session "
                "is not authorized."
            )

        total_checked = 0
        total_album_messages = 0
        total_missing = 0
        all_albums = set()

        for source in sources:
            try:
                result = await process_source(
                    client,
                    db,
                    group_id,
                    source
                )

                total_checked += (
                    result["checked"]
                )

                total_album_messages += (
                    result["album_messages"]
                )

                total_missing += (
                    result["missing"]
                )

                all_albums.update(
                    result["albums"]
                )

            except Exception as exc:
                print(
                    f"  ❌ ERROR: {exc}"
                )

        print("")
        print(
            "=============================================="
        )
        print(
            "BACKFILL COMPLETE ✅"
        )
        print(
            f"Checked:        {total_checked}"
        )
        print(
            f"Album Messages: {total_album_messages}"
        )
        print(
            f"Albums Found:   {len(all_albums)}"
        )
        print(
            f"Missing:        {total_missing}"
        )
        print(
            "=============================================="
        )

    finally:
        if client:
            await client.disconnect()

        db.close()


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