"""Точка входа: FastAPI-приложение.

Эндпоинты:
- GET  /oauth/callback  — приём кода авторизации, обмен на токены, регистрация бота
- POST /bot/events      — события бота (сообщения и нажатия кнопок)
- GET  /healthz         — health-check
"""
from __future__ import annotations

import asyncio
import logging

from fastapi import FastAPI, Request, Response
from fastapi.responses import HTMLResponse

import bitrix
import store
import approval
from poller import poll_loop

logging.basicConfig(level=logging.INFO)
log = logging.getLogger("app")

app = FastAPI(title="Bitrix24 Draft Assistant")


@app.on_event("startup")
async def _startup() -> None:
    store.init_db()
    asyncio.create_task(poll_loop())


@app.get("/healthz")
async def healthz() -> dict:
    return {"ok": True, "portals": store.all_portal_domains()}


@app.get("/oauth/callback")
async def oauth_callback(request: Request) -> Response:
    """Битрикс возвращает code + domain (member_id) после авторизации."""
    code = request.query_params.get("code")
    domain = request.query_params.get("domain")
    if not code or not domain:
        return HTMLResponse("Ожидались параметры code и domain", status_code=400)

    await bitrix.exchange_code(domain, code)

    # Узнаём user_id оператора (тебя) и регистрируем бота согласования.
    me = await bitrix.whoami(domain)
    operator_id = str(me.get("ID") or me.get("id"))
    store.set_portal_field(domain, "operator_id", operator_id)
    await bitrix.register_bot(domain)

    return HTMLResponse(
        f"<h3>Готово</h3><p>Портал <b>{domain}</b> подключён. "
        f"Бот «Черновики» зарегистрирован. Можешь закрыть страницу.</p>"
    )


def _verify_event(payload: dict) -> str | None:
    """Проверяет, что событие пришло от Битрикс (по application_token),
    и возвращает domain или None."""
    auth = payload.get("auth", {})
    member_id = auth.get("member_id")
    app_token = auth.get("application_token")
    domain = store.find_domain_by_member(member_id) if member_id else None
    if not domain:
        return None
    portal = store.get_portal(domain)
    if portal and portal.get("app_token") and app_token != portal["app_token"]:
        return None  # токен не совпал — игнорируем
    return domain


@app.post("/bot/events")
async def bot_events(request: Request) -> dict:
    # Битрикс шлёт application/x-www-form-urlencoded с вложенной структурой.
    form = await request.form()
    payload = _unflatten(dict(form))

    event = (payload.get("event") or "").upper()
    domain = _verify_event(payload)
    if not domain:
        log.warning("event %s: неизвестный/непроверенный источник", event)
        return {"ok": False}

    data = payload.get("data", {})

    if event == "ONIMCOMMANDADD":
        cmd = data.get("COMMAND", {})
        command = (cmd.get("COMMAND") or "").lower()
        params = cmd.get("COMMAND_PARAMS") or ""
        operator_dialog = store.get_portal(domain).get("operator_id")
        await approval.handle_command(domain, command, params, operator_dialog)
        return {"ok": True}

    if event == "ONIMBOTMESSAGEADD":
        p = data.get("PARAMS", {})
        author = str(p.get("FROM_USER_ID") or p.get("AUTHOR_ID") or "")
        text = (p.get("MESSAGE") or "").strip()
        portal = store.get_portal(domain)
        operator_id = portal.get("operator_id")
        # Личное сообщение оператора боту — возможно, это правка черновика.
        if author == str(operator_id) and text:
            pending_id = store.pop_edit_state(domain, operator_id)
            if pending_id:
                await approval.handle_edit_text(domain, operator_id, pending_id, text)
        return {"ok": True}

    # ONAPPINSTALL / ONIMBOTJOINCHAT / ONIMBOTDELETE и пр. — заглушки.
    return {"ok": True}


def _unflatten(flat: dict) -> dict:
    """Превращает ключи вида data[PARAMS][MESSAGE] в вложенный словарь."""
    root: dict = {}
    for key, value in flat.items():
        parts = key.replace("]", "").split("[")
        node = root
        for part in parts[:-1]:
            node = node.setdefault(part, {})
        node[parts[-1]] = value
    return root
