import asyncio
import os
import re
import time

from aiogram import Bot, Dispatcher, F
from aiogram.client.default import DefaultBotProperties
from aiogram.filters import Command
from aiogram.fsm.context import FSMContext
from aiogram.fsm.state import State, StatesGroup
from aiogram.fsm.storage.memory import MemoryStorage
from aiogram.types import (
    CallbackQuery,
    InlineKeyboardButton,
    InlineKeyboardMarkup,
    Message,
)
from pyrogram.errors import (
    PasswordHashInvalid,
    PhoneCodeExpired,
    PhoneCodeInvalid,
    PhoneNumberInvalid,
    SessionPasswordNeeded,
)

import client as uc
import config
import database as db

import logging

logging.basicConfig(
    level=logging.INFO,
    format="%(asctime)s %(levelname)s %(name)s %(message)s",
    handlers=[
        logging.FileHandler(config.BASE_DIR + "/bot.log", encoding="utf-8"),
        logging.StreamHandler(),
    ],
)
log = logging.getLogger("kodo")


async def delete_safe(msg):
    try:
        await msg.delete()
    except Exception:
        pass


bot = Bot(token=config.BOT_TOKEN, default=DefaultBotProperties())
dp = Dispatcher(storage=MemoryStorage())

PENDING = {}
PENDING_TASKS = {}
WATCHERS = {}
ALBUM_TASKS = {}

LOGIN_TIMEOUT = 300
WATCH_TIMEOUT = 86400

MAIN_TEXT = "⌯ هلا بالمطور، استخدم اجراء تحب تسوي اليوم من خلال لوحتك الخاصة:"


class AddAccount(StatesGroup):
    phone = State()
    code = State()
    password = State()


class AddSession(StatesGroup):
    file = State()


class AddDev(StatesGroup):
    dev_id = State()


class EditStart(StatesGroup):
    text = State()


class EditAccount(StatesGroup):
    name_bio = State()
    photo = State()


class EditAll(StatesGroup):
    lines = State()
    photos = State()


class SendMsg(StatesGroup):
    target = State()
    text = State()
    reply = State()


def btn(text, style=None, **kwargs):
    return InlineKeyboardButton(text=text, style=style, **kwargs)


def back_kb():
    return InlineKeyboardMarkup(
        inline_keyboard=[[btn("•رجوع•", "primary", callback_data="main")]]
    )


def main_kb():
    return InlineKeyboardMarkup(
        inline_keyboard=[
            [btn("‹ ارسال رسالة ›", "primary", callback_data="send_msg")],
            [
                btn("‹ اضافة حساب ›", "primary", callback_data="add_acc"),
                btn("‹ اضافة سيشن ›", "primary", callback_data="add_ses"),
            ],
            [
                btn("‹ ادارة الحسابات ›", "primary", callback_data="manage:0"),
                btn("‹ تعديل الحسابات ›", "primary", callback_data="edit_menu"),
            ],
            [btn("╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌", "danger", callback_data="noop")],
            [
                btn("‹ اضافة مطور ›", "success", callback_data="add_dev"),
                btn("‹ حذف مطور ›", "success", callback_data="dev_list"),
            ],
            [btn("‹ تعديل رسالة البدء ›", "success", callback_data="edit_start")],
        ]
    )


def guest_kb():
    return InlineKeyboardMarkup(
        inline_keyboard=[[btn("Source Kodo", "primary", url=config.SOURCE_URL)]]
    )


def user_link(row):
    if row.get("username"):
        return f"https://t.me/{row['username']}"
    return f"tg://user?id={row['user_id']}"


def user_label(row):
    return row.get("username") or row.get("name") or str(row["user_id"])


def paginate(rows, page):
    pages = max(1, (len(rows) + config.PER_PAGE - 1) // config.PER_PAGE)
    page = max(0, min(page, pages - 1))
    start = page * config.PER_PAGE
    return rows[start : start + config.PER_PAGE], page, pages


def nav_row(page, pages, prefix):
    row = [btn("•رجوع•", "primary", callback_data="main")]
    if page > 0:
        row.append(btn("السابق", "primary", callback_data=f"{prefix}:{page - 1}"))
    if page < pages - 1:
        row.append(btn("التالي", "primary", callback_data=f"{prefix}:{page + 1}"))
    return row


async def safe_edit(message, text, markup):
    try:
        await message.edit_text(text, reply_markup=markup)
    except Exception:
        await message.answer(text, reply_markup=markup)


async def manage_view(page):
    rows = await db.get_accounts()
    chunk, page, pages = paginate(rows, page)
    keyboard = []
    for row in chunk:
        keyboard.append(
            [
                btn(user_label(row), "primary", url=user_link(row)),
                btn("⌫", "danger", callback_data=f"del_acc:{row['user_id']}:{page}"),
            ]
        )
    keyboard.append(nav_row(page, pages, "manage"))
    text = f"⌯ عدد حساباتك: {len(rows)} حساب\n⌯ يمكنك ادارة اي حساب من الازرار:"
    return text, InlineKeyboardMarkup(inline_keyboard=keyboard)


async def edit_list_view(page):
    rows = await db.get_accounts()
    chunk, page, pages = paginate(rows, page)
    keyboard = []
    for row in chunk:
        keyboard.append(
            [
                btn(user_label(row), "primary", url=user_link(row)),
                btn("تعديل", "success", callback_data=f"edit_pick:{row['user_id']}"),
            ]
        )
    keyboard.append(nav_row(page, pages, "edit_one"))
    return "⌯ طيب، اختر الحساب المراد تعديله الان:", InlineKeyboardMarkup(inline_keyboard=keyboard)


async def sender_view(page):
    rows = await db.get_accounts()
    chunk, page, pages = paginate(rows, page)
    keyboard = []
    for row in chunk:
        keyboard.append(
            [btn(user_label(row), "primary", callback_data=f"sender:{row['user_id']}")]
        )
    keyboard.append(nav_row(page, pages, "pick_sender"))
    return "⌯ طيب، اختر الحساب اللي راح ترسل منه:", InlineKeyboardMarkup(inline_keyboard=keyboard)


async def dev_view():
    rows = await db.get_devs()
    keyboard = []
    for row in rows:
        label = row.get("username")
        if not label:
            try:
                chat = await bot.get_chat(row["user_id"])
                label = chat.username or chat.full_name
                if chat.username:
                    await db.add_dev(row["user_id"], chat.username)
            except Exception:
                label = str(row["user_id"])
        keyboard.append(
            [
                btn(label, "primary", url=f"tg://user?id={row['user_id']}"),
                btn("⌫", "danger", callback_data=f"del_dev:{row['user_id']}"),
            ]
        )
    keyboard.append([btn("•رجوع•", "primary", callback_data="main")])
    text = f"⌯ عدد المطورين: {len(rows)} مطور.\n⌯ يمكنك حذف مطور من خلال الزر ادناه:"
    return text, InlineKeyboardMarkup(inline_keyboard=keyboard)


def account_details(me):
    return (
        "⌯ تم اضافة حسابك للقائمة بنجاح ✓\n"
        "⌯ تفاصيل الحساب:\n"
        f"- Name: {me.first_name or ''}\n"
        f"- Username: {me.username or '-'}\n"
        f"- User ID: {me.id}"
    )


async def store_account(me, session_string, phone=None):
    await db.save_account(me.id, phone, me.first_name, me.username, session_string)


async def drop_login(dev_id):
    app = PENDING.pop(dev_id, None)
    task = PENDING_TASKS.pop(dev_id, None)
    if task:
        task.cancel()
    if app:
        await uc.cancel_login(app)


def arm_login_timeout(dev_id):
    task = PENDING_TASKS.pop(dev_id, None)
    if task:
        task.cancel()

    async def expire():
        await asyncio.sleep(LOGIN_TIMEOUT)
        app = PENDING.pop(dev_id, None)
        PENDING_TASKS.pop(dev_id, None)
        if app:
            await uc.cancel_login(app)
            try:
                await bot.send_message(
                    dev_id, "⌯ انتهت مهلة الاضافة، ابدأ من جديد.", reply_markup=main_kb()
                )
            except Exception:
                pass

    PENDING_TASKS[dev_id] = asyncio.create_task(expire())


@dp.message(Command("start"))
async def cmd_start(message: Message, state: FSMContext):
    await state.clear()
    if await db.is_dev(message.from_user.id):
        await message.answer(MAIN_TEXT, reply_markup=main_kb())
    else:
        text = await db.get_setting("start_message", db.DEFAULT_START)
        await message.answer(text, reply_markup=guest_kb())


@dp.callback_query(F.data == "noop")
async def cb_noop(call: CallbackQuery):
    await call.answer()


@dp.callback_query(F.data == "main")
async def cb_main(call: CallbackQuery, state: FSMContext):
    await state.clear()
    await drop_login(call.from_user.id)
    await safe_edit(call.message, MAIN_TEXT, main_kb())
    await call.answer()


@dp.callback_query(F.data == "add_acc")
async def cb_add_acc(call: CallbackQuery, state: FSMContext):
    await state.set_state(AddAccount.phone)
    await safe_edit(
        call.message, "⌯ طيب ارسل رقم هاتف حسابك مثل:\n+964xxxxxxxxxxx", back_kb()
    )
    await call.answer()


@dp.message(AddAccount.phone)
async def on_phone(message: Message, state: FSMContext):
    phone = (message.text or "").strip().replace(" ", "")
    wait = await message.answer("⌯ يتم الاتصال بالحساب الان لحضة…")
    try:
        app, code_hash = await asyncio.wait_for(uc.start_login(phone), 60)
    except asyncio.TimeoutError:
        log.error("send_code timeout for %s", phone)
        await delete_safe(wait)
        await message.answer("⌯ انتهت مهلة الاتصال بتليجرام.", reply_markup=back_kb())
        return
    except PhoneNumberInvalid:
        log.exception("invalid phone")
        await delete_safe(wait)
        await message.answer("⌯ رقم الهاتف غير صحيح، جربه مرة ثانية.", reply_markup=back_kb())
        return
    except Exception as e:
        log.exception("send_code failed")
        await delete_safe(wait)
        await message.answer(f"⌯ فشل الارسال: {type(e).__name__}: {e}", reply_markup=back_kb())
        return
    await delete_safe(wait)
    PENDING[message.from_user.id] = app
    arm_login_timeout(message.from_user.id)
    await state.update_data(phone=phone, code_hash=code_hash)
    await state.set_state(AddAccount.code)
    await message.answer("⌯ وصلك كود التحقق ع الحساب، دزه هنا:", reply_markup=back_kb())


@dp.message(AddAccount.code)
async def on_code(message: Message, state: FSMContext):
    data = await state.get_data()
    app = PENDING.get(message.from_user.id)
    if not app:
        await state.clear()
        await message.answer("⌯ انتهت الجلسة، ابدأ من جديد.", reply_markup=back_kb())
        return
    code = re.sub(r"\D", "", message.text or "")
    try:
        await uc.confirm_code(app, data["phone"], data["code_hash"], code)
    except SessionPasswordNeeded:
        arm_login_timeout(message.from_user.id)
        await state.set_state(AddAccount.password)
        await message.answer("⌯ حسابك محمي ارسل رمز التحقق بخطوتين:", reply_markup=back_kb())
        return
    except (PhoneCodeInvalid, PhoneCodeExpired):
        await message.answer("⌯ الكود غير صحيح او منتهي، ارسله من جديد:", reply_markup=back_kb())
        return
    except Exception as e:
        await message.answer(f"⌯ فشل التحقق: {e}", reply_markup=back_kb())
        return
    await complete_login(message, state, app, data.get("phone"))


@dp.message(AddAccount.password)
async def on_password(message: Message, state: FSMContext):
    data = await state.get_data()
    app = PENDING.get(message.from_user.id)
    if not app:
        await state.clear()
        await message.answer("⌯ انتهت الجلسة، ابدأ من جديد.", reply_markup=back_kb())
        return
    try:
        await uc.confirm_password(app, (message.text or "").strip())
    except PasswordHashInvalid:
        await message.answer("⌯ الرمز غير صحيح، ارسله من جديد:", reply_markup=back_kb())
        return
    except Exception as e:
        await message.answer(f"⌯ فشل التحقق: {e}", reply_markup=back_kb())
        return
    await complete_login(message, state, app, data.get("phone"))


async def complete_login(message, state, app, phone):
    session_string, me = await uc.finish_login(app)
    PENDING.pop(message.from_user.id, None)
    task = PENDING_TASKS.pop(message.from_user.id, None)
    if task:
        task.cancel()
    await store_account(me, session_string, phone)
    await state.clear()
    await message.answer(account_details(me), reply_markup=back_kb())


@dp.callback_query(F.data == "add_ses")
async def cb_add_ses(call: CallbackQuery, state: FSMContext):
    await state.set_state(AddSession.file)
    await safe_edit(
        call.message,
        "⌯ طيب: ارسل ملف الجلسة للحساب الان.\n⌯ تنبيه: ملف الجلسة لازم يكون pyrogram",
        back_kb(),
    )
    await call.answer()


@dp.message(AddSession.file, F.document)
async def on_session_file(message: Message, state: FSMContext):
    os.makedirs(config.TEMP_DIR, exist_ok=True)
    path = os.path.join(config.TEMP_DIR, f"{int(time.time())}_{message.document.file_name}")
    await bot.download(message.document, destination=path)
    try:
        session_string, me, lib = await uc.import_session_file(path)
    except Exception as e:
        await message.answer(f"⌯ فشل قراءة الملف: {e}", reply_markup=back_kb())
        return
    finally:
        try:
            os.remove(path)
        except OSError:
            pass
    if not session_string:
        await message.answer(
            f"⌯ ملف الجلسة هذا ({lib}) ليس pyrogram.\n"
            f"⌯ قم بتحويل ملف الجلسة من ({lib}) الى pyrogram من خلال البوت {config.CONVERT_BOT} "
            "ثم اعد ارسال الملف من جديد.",
            reply_markup=back_kb(),
        )
        return
    await store_account(me, session_string)
    await state.clear()
    await message.answer(account_details(me), reply_markup=back_kb())


@dp.message(AddSession.file, F.text)
async def on_session_text(message: Message, state: FSMContext):
    try:
        session_string, me = await uc.import_session_string((message.text or "").strip())
    except Exception as e:
        await message.answer(f"⌯ الجلسة غير صالحة: {e}", reply_markup=back_kb())
        return
    await store_account(me, session_string)
    await state.clear()
    await message.answer(account_details(me), reply_markup=back_kb())


@dp.callback_query(F.data.startswith("manage:"))
async def cb_manage(call: CallbackQuery):
    text, kb = await manage_view(int(call.data.split(":")[1]))
    await safe_edit(call.message, text, kb)
    await call.answer()


@dp.callback_query(F.data.startswith("del_acc:"))
async def cb_del_acc(call: CallbackQuery):
    _, user_id, page = call.data.split(":")
    await db.delete_account(int(user_id))
    text, kb = await manage_view(int(page))
    await safe_edit(call.message, text, kb)
    await call.answer("⌯ تم حذف الحساب بنجاح ✓", show_alert=True)


@dp.callback_query(F.data == "send_msg")
async def cb_send_msg(call: CallbackQuery, state: FSMContext):
    rows = await db.get_accounts()
    if not rows:
        await call.answer("⌯ ما عندك حسابات مضافة.", show_alert=True)
        return
    if len(rows) == 1:
        await state.update_data(sender=rows[0]["user_id"])
        await state.set_state(SendMsg.target)
        await safe_edit(call.message, "⌯ طيب: ارسل يوزر الشخص الان.", back_kb())
    else:
        text, kb = await sender_view(0)
        await safe_edit(call.message, text, kb)
    await call.answer()


@dp.callback_query(F.data.startswith("pick_sender:"))
async def cb_pick_sender(call: CallbackQuery):
    text, kb = await sender_view(int(call.data.split(":")[1]))
    await safe_edit(call.message, text, kb)
    await call.answer()


@dp.callback_query(F.data.startswith("sender:"))
async def cb_sender(call: CallbackQuery, state: FSMContext):
    await state.update_data(sender=int(call.data.split(":")[1]))
    await state.set_state(SendMsg.target)
    await safe_edit(call.message, "⌯ طيب: ارسل يوزر الشخص الان.", back_kb())
    await call.answer()


@dp.message(SendMsg.target)
async def on_target(message: Message, state: FSMContext):
    target = (message.text or "").strip().lstrip("@")
    if not target:
        await message.answer("⌯ ارسل يوزر صحيح.", reply_markup=back_kb())
        return
    await state.update_data(target=target)
    await state.set_state(SendMsg.text)
    await message.answer(f"⌯ طيب: ارسل رسالتك لارسالها لـ {target}.", reply_markup=back_kb())


@dp.message(SendMsg.text)
async def on_text(message: Message, state: FSMContext):
    data = await state.get_data()
    await state.update_data(body=message.text or "")
    kb = InlineKeyboardMarkup(
        inline_keyboard=[
            [btn("نعم، ارسل ✓", "success", callback_data="do_send")],
            [btn("رجوع", "primary", callback_data="main")],
        ]
    )
    await message.answer(
        "⌯ ممتاز، انا جاهز الان ما ادري عنك انت جاهز؟\n"
        f"⌯ الشخص: {data['target']}\n"
        "⌯ رسالتك:\n"
        f"{message.text}\n\n"
        "⌯ تريد ارسل هاذي الرسالـة له الان:",
        reply_markup=kb,
    )


@dp.callback_query(F.data == "do_send")
async def cb_do_send(call: CallbackQuery, state: FSMContext):
    data = await state.get_data()
    await call.answer()
    wait = await call.message.answer("⌯ يتم التجهيز لحضة شوي عزيزي.")
    session_string = await db.get_session(data["sender"])
    ok, elapsed, err = await uc.send_text(
        data["sender"], session_string, data["target"], data["body"]
    )
    try:
        await wait.delete()
    except Exception:
        pass
    reasons = {
        "peerflood": "الحساب مقيّد مؤقتاً من مراسلة الغرباء، راجع @SpamBot",
        "notfound": "اليوزر غير موجود",
        "blocked": "الشخص حاظر الحساب",
        "dead": "الجلسة منتهية، اعد اضافة الحساب",
    }
    status = "وصلت بنجاح ✓" if ok else f"فشل الارسال ✗ ({reasons.get(err, err)})"
    kb = InlineKeyboardMarkup(
        inline_keyboard=[
            [btn("نعم", "success", callback_data="watch_yes")],
            [btn("لا", "danger", callback_data="main")],
        ]
    )
    await call.message.answer(
        "⌯ تم حب، نفذت المطلوب راجع التفاصيل:\n"
        f"⌯ حالة الرسالة: {status}\n"
        f"⌯ الوقت المستغرق: {elapsed:.1f} ثانية\n"
        "⌯ هل تريد استلام أي رد يصل من الشخص؟",
        reply_markup=kb,
    )


def watch_kb(account_id, target_id):
    return InlineKeyboardMarkup(
        inline_keyboard=[
            [btn("رد", "success", callback_data=f"wreply:{account_id}:{target_id}")],
            [btn("انهاء", "danger", callback_data=f"wstop:{account_id}:{target_id}")],
        ]
    )


async def make_callback(dev_id, account_id, account_label, target_username, target_id):
    async def handler(_, msg):
        body = msg.text or msg.caption or "[وسائط]"
        try:
            await bot.send_message(
                dev_id,
                "⌯ عزيزي وصلتك رسالة جديدة.\n"
                f"⌯ من: {target_username}\n"
                f"⌯ الى: {account_label}\n"
                "⌯ الرسالة:\n"
                f"{body}",
                reply_markup=watch_kb(account_id, target_id),
            )
        except Exception:
            pass

    return handler


async def close_watch(account_id, target_id):
    entry = WATCHERS.pop((account_id, target_id), None)
    if not entry:
        return False
    task = entry.get("task")
    if task:
        task.cancel()
    await uc.stop_watcher(account_id, entry["handler"])
    return True


@dp.callback_query(F.data == "watch_yes")
async def cb_watch_yes(call: CallbackQuery, state: FSMContext):
    data = await state.get_data()
    account_id = data["sender"]
    session_string = await db.get_session(account_id)
    account = await db.get_account(account_id)
    user, err = await uc.resolve_user(account_id, session_string, data["target"])
    if not user:
        await call.answer("⌯ ما كدرت اجيب الشخص.", show_alert=True)
        return
    await close_watch(account_id, user.id)
    callback = await make_callback(
        call.from_user.id, account_id, user_label(account), data["target"], user.id
    )
    handler = await uc.start_watcher(account_id, session_string, user.id, callback)

    async def expire():
        await asyncio.sleep(WATCH_TIMEOUT)
        await close_watch(account_id, user.id)

    WATCHERS[(account_id, user.id)] = {
        "handler": handler,
        "task": asyncio.create_task(expire()),
        "username": data["target"],
    }
    await state.clear()
    await safe_edit(
        call.message,
        f"⌯ طيب، أي رسالة توصل للحساب {user_label(account)} من {data['target']} سأرسلها لك. "
        "سأنتضر لمدة 24 ساعة ثم انهي الاستلام.",
        back_kb(),
    )
    await call.answer()


@dp.callback_query(F.data.startswith("wreply:"))
async def cb_wreply(call: CallbackQuery, state: FSMContext):
    _, account_id, target_id = call.data.split(":")
    await state.update_data(sender=int(account_id), target_id=int(target_id))
    await state.set_state(SendMsg.reply)
    entry = WATCHERS.get((int(account_id), int(target_id)))
    username = entry["username"] if entry else str(target_id)
    await call.message.answer(
        f"⌯ يلا نرد على {username} اكتب رسالتك:", reply_markup=back_kb()
    )
    await call.answer()


@dp.message(SendMsg.reply)
async def on_reply(message: Message, state: FSMContext):
    data = await state.get_data()
    account_id = data["sender"]
    target_id = data["target_id"]
    session_string = await db.get_session(account_id)
    ok, _, err = await uc.send_text(account_id, session_string, target_id, message.text or "")
    entry = WATCHERS.get((account_id, target_id))
    username = entry["username"] if entry else str(target_id)
    await state.clear()
    if ok:
        kb = InlineKeyboardMarkup(
            inline_keyboard=[
                [btn("ايقاف الاستلام", "danger", callback_data=f"wstop:{account_id}:{target_id}")],
                [btn("رجوع", "primary", callback_data="main")],
            ]
        )
        await message.answer(f"⌯ تم ارسال الرد الى {username} بنجاح ✓", reply_markup=kb)
    else:
        await message.answer(f"⌯ فشل ارسال الرد ✗ ({err})", reply_markup=back_kb())


@dp.callback_query(F.data.startswith("wstop:"))
async def cb_wstop(call: CallbackQuery, state: FSMContext):
    _, account_id, target_id = call.data.split(":")
    await close_watch(int(account_id), int(target_id))
    await state.clear()
    await call.answer("⌯ تم ايقاف الاستلام بنجاح ✓", show_alert=True)
    await safe_edit(call.message, MAIN_TEXT, main_kb())


@dp.callback_query(F.data == "edit_menu")
async def cb_edit_menu(call: CallbackQuery):
    kb = InlineKeyboardMarkup(
        inline_keyboard=[
            [
                btn("تعديل الكل", "primary", callback_data="edit_all"),
                btn("تعديل حساب", "primary", callback_data="edit_one:0"),
            ],
            [btn("•رجوع•", "primary", callback_data="main")],
        ]
    )
    await safe_edit(call.message, "⌯ طيب، اختار نوع التعديل الان:", kb)
    await call.answer()


@dp.callback_query(F.data.startswith("edit_one:"))
async def cb_edit_one(call: CallbackQuery):
    text, kb = await edit_list_view(int(call.data.split(":")[1]))
    await safe_edit(call.message, text, kb)
    await call.answer()


@dp.callback_query(F.data.startswith("edit_pick:"))
async def cb_edit_pick(call: CallbackQuery, state: FSMContext):
    await state.update_data(target=int(call.data.split(":")[1]))
    await state.set_state(EditAccount.name_bio)
    await safe_edit(
        call.message,
        "⌯ طيب احتاج منك الاسم والبايو للحساب. ارسلهن بهذا الشكل:\nاحمد:لاشيء يوقفني",
        back_kb(),
    )
    await call.answer()


@dp.message(EditAccount.name_bio)
async def on_name_bio(message: Message, state: FSMContext):
    raw = (message.text or "").strip()
    if ":" not in raw:
        await message.answer("⌯ الصيغة غلط، ارسلها هيك:\nاحمد:لاشيء يوقفني", reply_markup=back_kb())
        return
    name, bio = raw.split(":", 1)
    await state.update_data(name=name.strip(), bio=bio.strip())
    await state.set_state(EditAccount.photo)
    kb = InlineKeyboardMarkup(
        inline_keyboard=[
            [btn("تخطي", "primary", callback_data="skip_photo")],
            [btn("•رجوع•", "primary", callback_data="main")],
        ]
    )
    await message.answer(
        "⌯ طيب، حفظت الاسم والبايو بالذاكرة.\n⌯ ارسل صورة للحساب او اضغط تخطي:", reply_markup=kb
    )


@dp.message(EditAccount.photo, F.photo)
async def on_photo(message: Message, state: FSMContext):
    os.makedirs(config.TEMP_DIR, exist_ok=True)
    path = os.path.join(config.TEMP_DIR, f"{int(time.time() * 1000)}.jpg")
    await bot.download(message.photo[-1], destination=path)
    await apply_edit(message, state, path)


@dp.callback_query(F.data == "skip_photo")
async def cb_skip_photo(call: CallbackQuery, state: FSMContext):
    await call.answer()
    await apply_edit(call.message, state, None)


async def apply_edit(message, state, photo_path):
    data = await state.get_data()
    await state.clear()
    row = await db.get_account(data["target"])
    if not row:
        await message.answer("⌯ الحساب غير موجود.", reply_markup=back_kb())
        return
    wait = await message.answer("⌯ لحضة، اقوم بتعديل الحساب الان.")
    session_string = db.decrypt(row["session"])
    ok, err = await uc.update_profile(
        row["user_id"], session_string, first_name=data["name"], bio=data["bio"], photo_path=photo_path
    )
    if photo_path:
        try:
            os.remove(photo_path)
        except OSError:
            pass
    if ok:
        await db.save_account(
            row["user_id"], row["phone"], data["name"], row["username"], session_string
        )
    try:
        await wait.delete()
    except Exception:
        pass
    status = "نجح التعديل ✓" if ok else f"فشل التعديل ✗ ({err})"
    await message.answer(
        f"⌯ انتهيت من تعديل الحساب {user_label(row)}:\n⌯ حالة التعديل: {status}",
        reply_markup=back_kb(),
    )


@dp.callback_query(F.data == "edit_all")
async def cb_edit_all(call: CallbackQuery, state: FSMContext):
    await state.set_state(EditAll.lines)
    await safe_edit(
        call.message,
        "⌯ طيب احتاج منك الاسماء والبايو الان. ارسلهن بهذا الشكل:\n"
        "احمد:لاشيء يوقفني\nعلي:انا الافضل دائماً",
        back_kb(),
    )
    await call.answer()


@dp.message(EditAll.lines)
async def on_all_lines(message: Message, state: FSMContext):
    pairs = []
    for line in (message.text or "").splitlines():
        line = line.strip()
        if not line or ":" not in line:
            continue
        name, bio = line.split(":", 1)
        pairs.append([name.strip(), bio.strip()])
    if not pairs:
        await message.answer("⌯ الصيغة غلط، ارسلها هيك:\nاحمد:لاشيء يوقفني", reply_markup=back_kb())
        return
    await state.update_data(pairs=pairs, photos=[])
    await state.set_state(EditAll.photos)
    kb = InlineKeyboardMarkup(
        inline_keyboard=[
            [btn("تخطي", "primary", callback_data="skip_all_photos")],
            [btn("•رجوع•", "primary", callback_data="main")],
        ]
    )
    await message.answer(
        "⌯ طيب، حفظت الاسماء والبايوات بالذاكرة.\n⌯ ارسل قائمة صور للحسابات او اضغط تخطي:",
        reply_markup=kb,
    )


@dp.message(EditAll.photos, F.photo)
async def on_all_photos(message: Message, state: FSMContext):
    os.makedirs(config.TEMP_DIR, exist_ok=True)
    path = os.path.join(config.TEMP_DIR, f"{int(time.time() * 1000)}_{message.message_id}.jpg")
    await bot.download(message.photo[-1], destination=path)
    data = await state.get_data()
    photos = data.get("photos", [])
    photos.append(path)
    await state.update_data(photos=photos)
    dev_id = message.from_user.id
    task = ALBUM_TASKS.pop(dev_id, None)
    if task:
        task.cancel()

    async def run():
        await asyncio.sleep(3)
        ALBUM_TASKS.pop(dev_id, None)
        await apply_edit_all(message, state)

    ALBUM_TASKS[dev_id] = asyncio.create_task(run())


@dp.callback_query(F.data == "skip_all_photos")
async def cb_skip_all_photos(call: CallbackQuery, state: FSMContext):
    await call.answer()
    await apply_edit_all(call.message, state)


async def apply_edit_all(message, state):
    data = await state.get_data()
    await state.clear()
    pairs = data.get("pairs", [])
    photos = data.get("photos", [])
    rows = await db.get_accounts()
    total = min(len(pairs), len(rows))
    progress = await message.answer("⌯ يتم تعديل جميع حساباتك الان.\n⌯ تم تعديل: 0 حساب حتى الان.")
    done = 0
    failed = 0
    for index in range(total):
        row = rows[index]
        name, bio = pairs[index]
        photo = photos[index] if index < len(photos) else None
        session_string = db.decrypt(row["session"])
        ok, _ = await uc.update_profile(
            row["user_id"], session_string, first_name=name, bio=bio, photo_path=photo
        )
        if ok:
            done += 1
            await db.save_account(
                row["user_id"], row["phone"], name, row["username"], session_string
            )
        else:
            failed += 1
        try:
            await progress.edit_text(
                f"⌯ يتم تعديل جميع حساباتك الان.\n⌯ تم تعديل: {done} حساب حتى الان."
            )
        except Exception:
            pass
        await asyncio.sleep(2)
    for photo in photos:
        try:
            os.remove(photo)
        except OSError:
            pass
    try:
        await progress.delete()
    except Exception:
        pass
    await message.answer(
        "⌯ انتهيت من تعديل كل حساباتك.\n"
        f"⌯ نجح تعديل: {done} حساب ✓\n"
        f"⌯ فشل تعديل: {failed} حساب ✗",
        reply_markup=back_kb(),
    )


@dp.callback_query(F.data == "add_dev")
async def cb_add_dev(call: CallbackQuery, state: FSMContext):
    await state.set_state(AddDev.dev_id)
    await safe_edit(call.message, "⌯ طيب ارسل ايدي المطور الان.", back_kb())
    await call.answer()


@dp.message(AddDev.dev_id)
async def on_dev_id(message: Message, state: FSMContext):
    raw = re.sub(r"\D", "", message.text or "")
    if not raw:
        await message.answer("⌯ الايدي غير صحيح، ارسله من جديد.", reply_markup=back_kb())
        return
    username = None
    try:
        chat = await bot.get_chat(int(raw))
        username = chat.username
    except Exception:
        pass
    await db.add_dev(int(raw), username)
    await state.clear()
    await message.answer("⌯ تم اضافة المطور للقائمة بنجاح ✓", reply_markup=back_kb())


@dp.callback_query(F.data == "dev_list")
async def cb_dev_list(call: CallbackQuery):
    text, kb = await dev_view()
    await safe_edit(call.message, text, kb)
    await call.answer()


@dp.callback_query(F.data.startswith("del_dev:"))
async def cb_del_dev(call: CallbackQuery):
    user_id = int(call.data.split(":")[1])
    if user_id == call.from_user.id:
        await call.answer("⌯ ما تكدر تحذف نفسك.", show_alert=True)
        return
    label = str(user_id)
    for row in await db.get_devs():
        if row["user_id"] == user_id:
            label = row.get("username") or str(user_id)
    await db.delete_dev(user_id)
    text, kb = await dev_view()
    await safe_edit(call.message, text, kb)
    await call.answer(f"⌯ تم حذف المطور {label} بنجاح ✓", show_alert=True)


@dp.callback_query(F.data == "edit_start")
async def cb_edit_start(call: CallbackQuery, state: FSMContext):
    current = await db.get_setting("start_message", db.DEFAULT_START)
    await state.set_state(EditStart.text)
    await safe_edit(
        call.message,
        f"⌯ الرسالة الحالية:\n{current}\n\n⌯ ارسل الرسالة التي ستضهر للمستخدمين.",
        back_kb(),
    )
    await call.answer()


@dp.message(EditStart.text)
async def on_start_text(message: Message, state: FSMContext):
    await db.set_setting("start_message", message.text)
    await state.clear()
    await message.answer("⌯ تم تعديل رسالة البدء بنجاح ✓", reply_markup=back_kb())


@dp.message()
async def fallback(message: Message):
    if await db.is_dev(message.from_user.id):
        await message.answer(MAIN_TEXT, reply_markup=main_kb())
    else:
        text = await db.get_setting("start_message", db.DEFAULT_START)
        await message.answer(text, reply_markup=guest_kb())


async def main():
    os.makedirs(config.SESSIONS_DIR, exist_ok=True)
    os.makedirs(config.TEMP_DIR, exist_ok=True)
    await db.init_db()
    await dp.start_polling(bot)


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