"""Telegram API портального бота: инвайт-ссылки и проверка чатов."""

from __future__ import annotations

import hashlib
import json
import re
import urllib.error
import urllib.request
from typing import Optional

from api.db_api import (
	add_portal_chat,
	delete_portal_chat,
	get_portal_chat,
	get_portal_chat_by_chat_id,
	get_portal_chat_by_username,
	list_portal_chats,
	update_portal_chat,
)
from server.config import PORTAL_BOT_TOKEN

_INVITE_LINK_NAME = "OZ Portal"
_BOT_USERNAME_RE = re.compile(r"^[A-Za-z][A-Za-z0-9_]{4,31}$")


def portal_bot_token() -> str:
	return str(PORTAL_BOT_TOKEN or "").strip()


def parse_portal_chat_ref(raw) -> int | str:
	text = str(raw or "").strip()
	if not text:
		raise ValueError("Укажите ID чата")
	if text.startswith("@"):
		name = text[1:].strip()
		if not name:
			raise ValueError("Укажите username чата")
		return f"@{name}"
	compact = text.replace(" ", "").replace("\u2212", "-")
	try:
		return int(compact)
	except ValueError as exc:
		raise ValueError("Некорректный ID чата") from exc


def parse_portal_bot_username(raw) -> str:
	text = str(raw or "").strip()
	if not text:
		raise ValueError("Укажите username бота")
	text = text.split()[0]
	text = text.replace("https://", "").replace("http://", "")
	text = text.split("?", 1)[0].split("#", 1)[0].strip().strip("/")
	lower = text.lower()
	for prefix in ("t.me/", "telegram.me/", "telegram.dog/"):
		if prefix in lower:
			idx = lower.rfind(prefix) + len(prefix)
			text = text[idx:]
			break
	name = text.lstrip("@").strip().strip("/")
	if not _BOT_USERNAME_RE.match(name):
		raise ValueError("Некорректный username. Пример: @NotifyBot")
	return name


def _bot_record_chat_id(username: str) -> int:
	digest = hashlib.sha256(username.lower().encode("utf-8")).digest()
	value = int.from_bytes(digest[:8], "big") % 10**12
	return 9 * 10**15 + value


def _clean_tg_error(description: str) -> str:
	text = str(description or "").strip()
	for prefix in ("Bad Request: ", "Forbidden: ", "Unauthorized: ", "Not Found: "):
		if text.startswith(prefix):
			text = text[len(prefix) :]
			break
	low = text.lower()
	if "chat not found" in low:
		return "Чат не найден. Проверьте ID и добавьте бота в чат."
	if "bot is not a member" in low:
		return "Бот не состоит в этом чате. Добавьте его как администратора."
	if "not enough rights" in low or "need administrator" in low:
		return "У бота нет прав приглашать пользователей."
	if "chat_invite_link" in low and "kicked" in low:
		return "Бота исключили из чата."
	if "unauthorized" in low:
		return "Неверный PORTAL_BOT_TOKEN."
	return text or "Ошибка Telegram"


def portal_api(method: str, payload: Optional[dict] = None) -> dict:
	token = portal_bot_token()
	if not token:
		raise ValueError("Укажите PORTAL_BOT_TOKEN в config.py")
	body = json.dumps(payload or {}).encode("utf-8")
	request = urllib.request.Request(
		f"https://api.telegram.org/bot{token}/{method}",
		data=body,
		method="POST",
		headers={"Content-Type": "application/json", "Accept": "application/json"},
	)
	try:
		with urllib.request.urlopen(request, timeout=20) as response:
			data = json.loads(response.read().decode("utf-8") or "{}")
	except urllib.error.HTTPError as exc:
		raw = exc.read().decode("utf-8", errors="replace") if exc.fp else ""
		try:
			data = json.loads(raw or "{}")
		except json.JSONDecodeError:
			raise ValueError(_clean_tg_error(str(exc))) from exc
	except Exception as exc:
		raise ValueError("Не удалось связаться с Telegram") from exc
	if not isinstance(data, dict) or not data.get("ok"):
		raise ValueError(_clean_tg_error((data or {}).get("description") or "Ошибка Telegram"))
	result = data.get("result")
	return result if isinstance(result, dict) or isinstance(result, list) else {}


def portal_bot_info() -> dict:
	token = portal_bot_token()
	if not token:
		return {"configured": False, "username": None, "id": None}
	try:
		me = portal_api("getMe")
	except ValueError as exc:
		return {"configured": True, "username": None, "id": None, "error": str(exc)}
	username = str((me or {}).get("username") or "").strip() or None
	return {
		"configured": True,
		"username": username,
		"id": (me or {}).get("id"),
	}


def fetch_chat(chat_ref) -> dict:
	info = portal_api("getChat", {"chat_id": chat_ref})
	if not isinstance(info, dict):
		raise ValueError("Не удалось получить чат")
	chat_type = str(info.get("type") or "")
	if chat_type not in {"group", "supergroup", "channel"}:
		raise ValueError("Нужен ID группы или канала, не лички")
	try:
		chat_id = int(info.get("id"))
	except (TypeError, ValueError) as exc:
		raise ValueError("Telegram не вернул ID чата") from exc
	username = str(info.get("username") or "").strip() or None
	title = str(info.get("title") or "").strip() or username or str(chat_id)
	return {
		"id": chat_id,
		"title": title,
		"username": username,
		"type": chat_type,
	}


def public_chat_url(username: Optional[str]) -> Optional[str]:
	name = str(username or "").strip().lstrip("@")
	if not name:
		return None
	return f"https://t.me/{name}"


def _is_additional_invite(link: str) -> bool:
	text = str(link or "").strip()
	return "t.me/+" in text or "joinchat/" in text


def create_invite_link(chat_id: int) -> str:
	data = portal_api(
		"createChatInviteLink",
		{"chat_id": int(chat_id), "name": _INVITE_LINK_NAME},
	)
	link = str((data or {}).get("invite_link") or "").strip()
	if not link:
		raise ValueError("Telegram не вернул ссылку-приглашение")
	return link


def revoke_invite_link(chat_id: int, invite_link: str) -> None:
	link = str(invite_link or "").strip()
	if not link:
		return
	portal_api(
		"revokeChatInviteLink",
		{"chat_id": int(chat_id), "invite_link": link},
	)


def resolve_invite_link(
	chat_id: int,
	*,
	username: Optional[str] = None,
	old_link: Optional[str] = None,
	rotate: bool = False,
) -> str:
	public = public_chat_url(username)
	if public:
		return public
	if rotate and _is_additional_invite(old_link or ""):
		try:
			revoke_invite_link(chat_id, old_link)
		except ValueError:
			pass
	return create_invite_link(chat_id)


def portal_status() -> dict:
	return {
		"bot": portal_bot_info(),
		"chats": list_portal_chats(),
	}


def provision_portal_chat(title: str, chat_ref) -> dict:
	ref = parse_portal_chat_ref(chat_ref)
	info = fetch_chat(ref)
	if get_portal_chat_by_chat_id(info["id"]):
		raise ValueError("Этот чат уже добавлен")
	resolved_title = str(title or "").strip() or info["title"]
	warning = None
	link = None
	try:
		link = resolve_invite_link(info["id"], username=info.get("username"))
	except ValueError as exc:
		warning = str(exc)
	row = add_portal_chat(
		resolved_title,
		info["id"],
		username=info.get("username"),
		invite_link=link,
		kind="chat",
	)
	if warning:
		row = dict(row)
		row["warning"] = warning
	return row


def provision_portal_bot(title: str, username_raw) -> dict:
	username = parse_portal_bot_username(username_raw)
	if get_portal_chat_by_username(username):
		raise ValueError("Этот бот уже добавлен")
	link = public_chat_url(username)
	if not link:
		raise ValueError("Не удалось собрать ссылку")
	resolved_title = str(title or "").strip() or f"@{username}"
	return add_portal_chat(
		resolved_title,
		_bot_record_chat_id(username),
		username=username,
		invite_link=link,
		kind="bot",
	)


def refresh_portal_chat_link(chat_pk: int) -> dict:
	row = get_portal_chat(chat_pk)
	if not row:
		raise ValueError("Чат не найден")
	if row.get("kind") == "bot":
		username = str(row.get("username") or "").strip()
		if not username:
			raise ValueError("У бота нет username")
		link = public_chat_url(username)
		updated = update_portal_chat(row["id"], invite_link=link, username=username)
		if not updated:
			raise ValueError("Не удалось сохранить ссылку")
		return updated
	info = fetch_chat(row["chat_id"])
	link = resolve_invite_link(
		info["id"],
		username=info.get("username"),
		old_link=row.get("invite_link"),
		rotate=True,
	)
	title = row.get("title") or info["title"]
	updated = update_portal_chat(
		row["id"],
		title=title,
		username=info.get("username"),
		invite_link=link,
	)
	if not updated:
		raise ValueError("Не удалось сохранить ссылку")
	return updated


def refresh_all_portal_links() -> dict:
	chats = []
	errors = []
	for row in list_portal_chats():
		try:
			chats.append(refresh_portal_chat_link(row["id"]))
		except ValueError as exc:
			errors.append({"id": row["id"], "title": row.get("title"), "error": str(exc)})
			chats.append(get_portal_chat(row["id"]) or row)
	return {"chats": chats, "errors": errors}


def remove_portal_chat(chat_pk: int) -> bool:
	row = get_portal_chat(chat_pk)
	if not row:
		return False
	if _is_additional_invite(row.get("invite_link") or ""):
		try:
			revoke_invite_link(row["chat_id"], row["invite_link"])
		except ValueError:
			pass
	return delete_portal_chat(row["id"])
