"""Telegram userbot, встроенный в панель: статус, вход по коду, 2FA."""

from __future__ import annotations

import asyncio
import re
import sqlite3
from typing import Optional

from telethon import TelegramClient
from telethon.errors import (
    FloodWaitError,
    PasswordHashInvalidError,
    PhoneCodeExpiredError,
    PhoneCodeInvalidError,
    PhoneNumberInvalidError,
    SessionPasswordNeededError,
)

from server.userbot import API_HASH, API_ID, SESSION_PATH, register_handlers


def normalize_tg_phone(raw: str) -> str:
    digits = re.sub(r"\D", "", str(raw or ""))
    if len(digits) == 11 and digits[0] in ("7", "8"):
        digits = "7" + digits[1:]
    if len(digits) == 10:
        digits = "7" + digits
    if len(digits) != 11 or not digits.startswith("7"):
        raise ValueError("Укажите номер в формате +7…")
    return "+" + digits


def _me_payload(me) -> Optional[dict]:
    if not me:
        return None
    return {
        "id": me.id,
        "first_name": me.first_name or "",
        "last_name": me.last_name or "",
        "username": me.username or "",
        "phone": me.phone or "",
    }


def _friendly_error(exc: BaseException) -> str:
    if isinstance(exc, FloodWaitError):
        return f"Telegram просит подождать {exc.seconds} сек."
    if isinstance(exc, PhoneNumberInvalidError):
        return "Неверный номер телефона"
    if isinstance(exc, PhoneCodeInvalidError):
        return "Неверный код"
    if isinstance(exc, PhoneCodeExpiredError):
        return "Код устарел, запросите новый"
    if isinstance(exc, PasswordHashInvalidError):
        return "Неверный пароль 2FA"
    text = str(exc) or exc.__class__.__name__
    if "database is locked" in text.lower() or "locked" in text.lower() and "session" in text.lower():
        return "Сессия занята. Останови python userbot.py и оставь только панель."
    return text[:240]


class UserbotRuntime:
    def __init__(self) -> None:
        self.client: Optional[TelegramClient] = None
        self.status = "offline"
        self.phone: Optional[str] = None
        self.phone_code_hash: Optional[str] = None
        self.me: Optional[dict] = None
        self.error: Optional[str] = None
        self._lock = asyncio.Lock()

    def snapshot(self) -> dict:
        return {
            "status": self.status,
            "phone": self.phone,
            "error": self.error,
            "me": self.me,
        }

    async def _new_client(self) -> TelegramClient:
        if not API_ID or not API_HASH:
            raise RuntimeError("Не заданы TG_API_ID / TG_API_HASH")
        client = TelegramClient(SESSION_PATH, API_ID, API_HASH)
        register_handlers(client)
        return client

    async def ensure_client(self) -> TelegramClient:
        if self.client is None:
            self.client = await self._new_client()
        if not self.client.is_connected():
            await self.client.connect()
        register_handlers(self.client)
        return self.client

    async def boot(self) -> None:
        async with self._lock:
            try:
                client = await self.ensure_client()
                if await client.is_user_authorized():
                    self.me = _me_payload(await client.get_me())
                    self.status = "online"
                    self.error = None
                    self.phone = None
                    self.phone_code_hash = None
                else:
                    self.status = "logged_out"
                    self.me = None
                    self.error = None
            except Exception as exc:
                self.status = "error"
                self.error = _friendly_error(exc)
                self.me = None

    async def shutdown(self) -> None:
        async with self._lock:
            if self.client is not None:
                try:
                    await self.client.disconnect()
                except Exception:
                    pass
            self.client = None
            if self.status == "online":
                self.status = "offline"

    async def request_code(self, phone_raw: str) -> dict:
        phone = normalize_tg_phone(phone_raw)
        async with self._lock:
            try:
                client = await self.ensure_client()
                if await client.is_user_authorized():
                    self.me = _me_payload(await client.get_me())
                    self.status = "online"
                    self.error = None
                    return self.snapshot()
                sent = await client.send_code_request(phone)
                self.phone = phone
                self.phone_code_hash = sent.phone_code_hash
                self.status = "awaiting_code"
                self.error = None
                self.me = None
            except Exception as exc:
                self.status = "logged_out"
                self.error = _friendly_error(exc)
                raise RuntimeError(self.error) from exc
            return self.snapshot()

    async def submit_code(self, code_raw: str) -> dict:
        code = re.sub(r"\D", "", str(code_raw or ""))
        if len(code) < 5:
            raise ValueError("Введите код из Telegram")
        async with self._lock:
            if self.status not in ("awaiting_code", "awaiting_password"):
                raise RuntimeError("Сначала запросите код")
            if not self.phone:
                raise RuntimeError("Номер не задан")
            try:
                client = await self.ensure_client()
                await client.sign_in(
                    phone=self.phone,
                    code=code,
                    phone_code_hash=self.phone_code_hash,
                )
                self.me = _me_payload(await client.get_me())
                self.status = "online"
                self.error = None
                self.phone_code_hash = None
            except SessionPasswordNeededError:
                self.status = "awaiting_password"
                self.error = None
            except Exception as exc:
                self.error = _friendly_error(exc)
                raise RuntimeError(self.error) from exc
            return self.snapshot()

    async def submit_password(self, password: str) -> dict:
        password = str(password or "")
        if not password:
            raise ValueError("Введите пароль 2FA")
        async with self._lock:
            if self.status != "awaiting_password":
                raise RuntimeError("Сейчас пароль не нужен")
            try:
                client = await self.ensure_client()
                await client.sign_in(password=password)
                self.me = _me_payload(await client.get_me())
                self.status = "online"
                self.error = None
                self.phone_code_hash = None
            except Exception as exc:
                self.error = _friendly_error(exc)
                raise RuntimeError(self.error) from exc
            return self.snapshot()

    async def cancel_login(self) -> dict:
        async with self._lock:
            self.phone = None
            self.phone_code_hash = None
            self.error = None
            try:
                client = await self.ensure_client()
                if await client.is_user_authorized():
                    self.me = _me_payload(await client.get_me())
                    self.status = "online"
                else:
                    self.me = None
                    self.status = "logged_out"
            except Exception:
                self.me = None
                self.status = "logged_out"
            return self.snapshot()

    async def logout(self) -> dict:
        async with self._lock:
            if self.client is not None:
                try:
                    if self.client.is_connected() and await self.client.is_user_authorized():
                        await self.client.log_out()
                    elif self.client.is_connected():
                        await self.client.disconnect()
                except sqlite3.OperationalError as exc:
                    self.error = _friendly_error(exc)
                    raise RuntimeError(self.error) from exc
                except Exception:
                    try:
                        await self.client.disconnect()
                    except Exception:
                        pass
            self.client = None
            self.me = None
            self.phone = None
            self.phone_code_hash = None
            self.error = None
            self.status = "logged_out"
            try:
                await self.ensure_client()
            except Exception as exc:
                self.status = "error"
                self.error = _friendly_error(exc)
            return self.snapshot()


    async def list_dialogs(self, limit: int = 200) -> list[dict]:
        client = self.client
        if client is None or not client.is_connected() or self.status != "online":
            raise RuntimeError("Юзербот не в сети")
        items: list[dict] = []
        async for dialog in client.iter_dialogs(limit=limit):
            if dialog.is_user:
                kind = "user"
            elif dialog.is_group:
                kind = "group"
            else:
                kind = "channel"
            items.append({
                "id": dialog.id,
                "title": (dialog.name or "").strip() or str(dialog.id),
                "kind": kind,
            })
        return items


runtime = UserbotRuntime()
