"""
Главный модуль max_bot FastAPI-приложения.

Эндпоинты:
  - /webhook: входящие webhook-и от MAX Platform
  - /health: healthcheck
  - /api/bot/*: CRUD для настроек, KB, заявок, диалогов, пользователей
"""
from __future__ import annotations
import logging
from contextlib import asynccontextmanager
from fastapi import FastAPI, Request, HTTPException
from fastapi.responses import JSONResponse
from sqlalchemy import select, text, func
from sqlalchemy.orm import selectinload

from app.config import settings
from app.database import engine, async_session

processed_callbacks = set()
from app.models import Base, BotConversation, BotProcessingLog
from app.max_api import max_api
from app.settings_cache import settings_cache
from app.yandex_gpt import yandex_gpt
from app.handlers.greeting import handle_greeting, handle_message
from app.handlers.handoff import handle_callback, handle_contact_share
from fastapi.middleware.cors import CORSMiddleware

logging.basicConfig(
    level=logging.INFO,
    format="%(asctime)s [%(levelname)s] %(name)s: %(message)s",
)
logger = logging.getLogger(__name__)


@asynccontextmanager
async def lifespan(app: FastAPI):
    logger.info("Starting max_bot service...")
    async with engine.begin() as conn:
        await conn.run_sync(Base.metadata.create_all)
    logger.info("Database tables ensured.")

    await _apply_migrations()
    await _seed_default_settings()
    await _register_webhook()

    yield

    await max_api.close()
    logger.info("max_bot service stopped.")


app = FastAPI(title="AegisOne MAX Bot", lifespan=lifespan)

app.add_middleware(
    CORSMiddleware,
    allow_origins=["https://service.aegisone.ru", "https://aegisone.ru"],
    allow_credentials=True,
    allow_methods=["*"],
    allow_headers=["*"],
)


async def _apply_migrations():
    """Автоматические миграции: добавление колонок/таблиц при старте."""
    async with engine.begin() as conn:
        try:
            await conn.execute(text("""
                DO $$ BEGIN
                    IF NOT EXISTS (
                        SELECT 1 FROM information_schema.columns
                        WHERE table_name='bot_tickets' AND column_name='status'
                    ) THEN
                        ALTER TABLE bot_tickets ADD COLUMN status VARCHAR(20) DEFAULT 'Новая';
                    END IF;
                END $$;
            """))
            await conn.execute(text("""
                UPDATE bot_tickets SET status = 'Новая' WHERE status IS NULL OR status NOT IN ('Новая','В работе','Закрыта');
            """))
        except Exception as e:
            logger.warning(f"Migration note: {e}")

        # BotUser new columns
        await _add_column_if_not_exists(conn, "bot_users", "patronymic", "VARCHAR(255)")
        await _add_column_if_not_exists(conn, "bot_users", "email", "VARCHAR(255)")
        await _add_column_if_not_exists(conn, "bot_users", "organization", "VARCHAR(255)")
        await _add_column_if_not_exists(conn, "bot_users", "address", "TEXT")
        await _add_column_if_not_exists(conn, "bot_users", "vcf_raw", "TEXT")
        await _add_column_if_not_exists(conn, "bot_users", "contact_hash", "VARCHAR(255)")
        await _add_column_if_not_exists(conn, "bot_users", "phone_verified", "BOOLEAN DEFAULT FALSE")
        await _add_column_if_not_exists(conn, "bot_users", "email_verified", "BOOLEAN DEFAULT FALSE")
        await _add_column_if_not_exists(conn, "bot_users", "last_interaction", "TIMESTAMP")
        await _add_column_if_not_exists(conn, "bot_users", "total_conversations", "INTEGER DEFAULT 0")
        await _add_column_if_not_exists(conn, "bot_users", "total_tickets", "INTEGER DEFAULT 0")

        # BotProcessingLog table
        await _create_table_if_not_exists(conn, "bot_processing_logs", """
            CREATE TABLE IF NOT EXISTS bot_processing_logs (
                id BIGSERIAL PRIMARY KEY,
                user_id BIGINT NOT NULL REFERENCES bot_users(id) ON DELETE CASCADE,
                conversation_id BIGINT REFERENCES bot_conversations(id) ON DELETE SET NULL,
                step VARCHAR(50) NOT NULL,
                status VARCHAR(20) DEFAULT 'ok',
                message TEXT,
                detail TEXT,
                created_at TIMESTAMP DEFAULT now()
            )
        """)


async def _add_column_if_not_exists(conn, table: str, column: str, col_type: str):
    """Безопасное добавление колонки (idempotent)."""
    from sqlalchemy import text as sa_text
    _validate_sql_name(table)
    _validate_sql_name(column)
    _validate_sql_type(col_type)
    try:
        await conn.execute(sa_text(f"""
            DO $$ BEGIN
                IF NOT EXISTS (
                    SELECT 1 FROM information_schema.columns
                    WHERE table_name='{table}' AND column_name='{column}'
                ) THEN
                    ALTER TABLE "{table}" ADD COLUMN "{column}" {col_type};
                END IF;
            END $$;
        """))
    except Exception as e:
        logger.warning(f"Migration add column {table}.{column}: {e}")


async def _create_table_if_not_exists(conn, table: str, create_sql: str):
    """Безопасное создание таблицы (idempotent)."""
    from sqlalchemy import text as sa_text
    try:
        await conn.execute(sa_text(f"""
            DO $$ BEGIN
                IF NOT EXISTS (
                    SELECT 1 FROM information_schema.tables
                    WHERE table_name='{table}'
                ) THEN
                    {create_sql};
                END IF;
            END $$;
        """))
    except Exception as e:
        logger.warning(f"Migration create table {table}: {e}")


def _validate_sql_name(name: str) -> None:
    """Валидация SQL-идентификатора (только буквы, цифры, подчёркивания)."""
    import re
    if not re.match(r'^[a-zA-Z_][a-zA-Z0-9_ ]*$', name):
        raise ValueError(f"Invalid SQL identifier: {name}")


def _validate_sql_type(col_type: str) -> None:
    """Валидация SQL-типа колонки (разрешённые типы)."""
    import re
    allowed = r'^(VARCHAR\(\d+\)|TEXT|INTEGER( DEFAULT \d+)?|BOOLEAN( DEFAULT (TRUE|FALSE))?|TIMESTAMP)$'
    if not re.match(allowed, col_type.strip()):
        raise ValueError(f"Invalid SQL type: {col_type}")


async def _log_processing(user_id: int, step: str, status: str = "ok", message: str = None, detail: str = None, conversation_id: int = None):
    """Запись лога обработки в bot_processing_logs."""
    try:
        async with async_session() as db:
            db.add(BotProcessingLog(
                user_id=user_id,
                conversation_id=conversation_id,
                step=step,
                status=status,
                message=message,
                detail=detail,
            ))
            await db.commit()
    except Exception as e:
        logger.warning(f"Failed to write processing log: {e}")


async def _seed_default_settings():
    """Заполнение настроек по умолчанию при первом запуске."""
    defaults = {
        "assistant_name": "София",
        "assistant_role": "Представитель компании AegisOne Engineering, помогающий клиентам с запросами по безопасности",
        "support_email": "zakaz@aegisone.ru",
        "email_subject": "Обращение через Виртуального помощника AegisOne Engineering",
        "ai_provider": "yandex",
    }
    async with async_session() as db:
        from app.models import BotSetting
        for key, value in defaults.items():
            result = await db.execute(select(BotSetting).where(BotSetting.key == key))
            if not result.scalar_one_or_none():
                db.add(BotSetting(key=key, value=value))
        await db.commit()
    settings_cache.invalidate()


async def _register_webhook():
    """Регистрация webhook-а в MAX Platform."""
    try:
        subs = await max_api.get_subscriptions()
        logger.info(f"Current subscriptions: {subs}")
    except Exception as e:
        logger.warning(f"Could not get subscriptions: {e}")

    try:
        result = await max_api.subscribe_webhook()
        logger.info(f"Webhook subscription result: {result}")
    except Exception as e:
        logger.error(f"Failed to subscribe webhook: {e}")


def _verify_secret(request: Request) -> bool:
    """Проверка webhook-секрета (пустой заголовок = OK для обратной совместимости)."""
    secret = request.headers.get("X-Max-Bot-Api-Secret", "")
    if not secret:
        return True
    return secret == settings.webhook_secret


async def _get_active_conv_id(user_id: int) -> int | None:
    """Получение ID активного диалога пользователя."""
    try:
        async with async_session() as db:
            result = await db.execute(
                select(BotConversation)
                .where(BotConversation.user_id == user_id)
                .order_by(BotConversation.id.desc())
                .limit(1)
            )
            conv = result.scalar_one_or_none()
            return conv.id if conv else None
    except Exception:
        return None


@app.post("/webhook")
async def webhook(request: Request):
    if not _verify_secret(request):
        logger.warning("Invalid webhook secret")
        raise HTTPException(status_code=401, detail="Invalid secret")

    try:
        body = await request.json()
    except Exception:
        raise HTTPException(status_code=400, detail="Invalid JSON")

    update_type = body.get("update_type", "")
    logger.info(f"Received webhook: type={update_type}")

    user_id = None
    is_test = False
    try:
        if update_type == "bot_started":
            user_id = body.get("user", {}).get("user_id") or body.get("chat_id")
            payload = body.get("payload")
            user_info = body.get("user", {})
            first_name = user_info.get("first_name", "")
            last_name = user_info.get("last_name", "")
            username = user_info.get("username", "")
            if user_id:
                is_test = max_api.is_test_user(user_id)
                if is_test:
                    max_api.mock_replies.clear()
                conv_id = await _get_active_conv_id(user_id)
                await _log_processing(user_id, "bot_started", "ok", "Диалог начат", conversation_id=conv_id)
                await handle_greeting(user_id, payload, first_name, last_name, username)

        elif update_type == "message_created":
            message = body.get("message", {})
            if not isinstance(message, dict):
                message = {}
            sender = message.get("sender", {})
            user_id = sender.get("user_id") if isinstance(sender, dict) else None
            body_data = message.get("body", {})
            text = body_data.get("text", "") if isinstance(body_data, dict) else ""

            logger.info(f"message_created: user_id={user_id} text={text[:80] if text else '(empty)'}")

            if user_id:
                is_test = max_api.is_test_user(user_id)
                if is_test:
                    max_api.mock_replies.clear()
                conv_id = await _get_active_conv_id(user_id)
                await _log_processing(user_id, "message_received", "ok", f"Получено сообщение: {text[:100] if text else '(вложение)'}", conversation_id=conv_id)

            attachments = body_data.get("attachments", []) if isinstance(body_data, dict) else []
            for att in attachments:
                if att.get("type") == "contact":
                    if user_id:
                        vcf_info = att.get("payload", {}).get("vcf_info", "")
                        contact_hash = att.get("payload", {}).get("hash", "")
                        vcf_data = _parse_vcf(vcf_info)
                        phone = vcf_data.get("phone") or _extract_phone_from_vcf(vcf_info)
                        async with async_session() as db:
                            result = await db.execute(
                                select(BotConversation)
                                .where(BotConversation.user_id == user_id)
                                .order_by(BotConversation.id.desc())
                                .limit(1)
                            )
                            conv = result.scalar_one_or_none()
                            conv_id = conv.id if conv else 0
                        await _log_processing(user_id, "contact_share", "ok", f"Получен контакт: {phone}", conversation_id=conv_id)
                        await handle_contact_share(user_id, conv_id, phone, vcf_info, vcf_data, contact_hash)
                    bot_replies = max_api.pop_mock_replies() if is_test else []
                    return JSONResponse({"ok": True, "bot_replies": bot_replies})

            if user_id and text:
                logger.info(f"message_created: user={user_id} text={text[:80]}")
                conv_id = await _get_active_conv_id(user_id)
                await _log_processing(user_id, "intent_analysis", "ok", f"Анализ сообщения: {text[:100]}", conversation_id=conv_id)
                try:
                    await handle_message(user_id, text)
                except Exception as e:
                    logger.exception(f"handle_message FAILED: {e}")

        elif update_type == "dialog_cleared":
            user_id = body.get("user", {}).get("user_id") or body.get("chat_id")
            if user_id:
                conv_id = await _get_active_conv_id(user_id)
                await _log_processing(user_id, "dialog_cleared", "ok", "Диалог сброшен", conversation_id=conv_id)
                async with async_session() as db:
                    result = await db.execute(
                        select(BotConversation)
                        .where(BotConversation.user_id == user_id)
                        .order_by(BotConversation.id.desc())
                        .limit(1)
                    )
                    conv = result.scalar_one_or_none()
                    if conv:
                        conv.state = "greeting"
                        await db.commit()
                logger.info(f"Dialog cleared for user {user_id}, state reset")

        elif update_type == "message_callback":
            callback_data = body.get("callback", {})
            message_data = body.get("message", {})
            recipient = message_data.get("recipient", {}) if isinstance(message_data, dict) else {}
            user_id = recipient.get("user_id") if isinstance(recipient, dict) else None
            payload = callback_data.get("payload", "") if isinstance(callback_data, dict) else ""
            callback_id = callback_data.get("callback_id", "") if isinstance(callback_data, dict) else ""

            logger.info(f"message_callback: user_id={user_id} payload={payload} callback_id={callback_id[:30] if callback_id else ''}")

            if callback_id and callback_id in processed_callbacks:
                logger.info(f"message_callback: duplicate callback_id={callback_id[:30]}, skipping")
                return JSONResponse({"ok": True})

            if callback_id:
                processed_callbacks.add(callback_id)
                if len(processed_callbacks) > 1000:
                    processed_callbacks.clear()

            if user_id and payload:
                is_test = max_api.is_test_user(user_id)
                if is_test:
                    max_api.mock_replies.clear()
                conv_id = await _get_active_conv_id(user_id)
                await _log_processing(user_id, "callback_received", "ok", f"Callback: {payload}", conversation_id=conv_id)
                await handle_callback(user_id, payload, callback_data)

        bot_replies = max_api.pop_mock_replies() if is_test else []
        return JSONResponse({"ok": True, "bot_replies": bot_replies})

    except Exception as e:
        logger.exception(f"Error processing webhook: {e}")
        if user_id is not None:
            await _log_processing(user_id, "error", "error", f"Ошибка обработки: {str(e)[:200]}")
        return JSONResponse({"ok": False, "error": str(e)}, status_code=500)


@app.get("/health")
async def health():
    return {"status": "ok"}


SENSITIVE_KEYS = {"yandex_gpt_key", "yandex_folder_id", "yandex_disk_token",
                   "yandex_disk_root", "yandex_disk_client_id", "yandex_disk_client_secret"}


@app.get("/api/bot/settings")
async def api_get_settings():
    all_settings = await settings_cache.get_all()
    masked = dict(all_settings)
    for key in SENSITIVE_KEYS:
        if key in masked and masked[key]:
            masked[key] = masked[key][:6] + "..." if len(masked[key]) > 6 else "***"
    return JSONResponse(masked)


@app.post("/api/bot/settings/save")
async def api_save_settings(request: Request):
    try:
        body = await request.json()
    except Exception:
        raise HTTPException(status_code=400, detail="Invalid JSON")

    yandex_keys = {"yandex_gpt_key", "yandex_folder_id", "yandex_disk_token",
                   "yandex_disk_root", "yandex_disk_client_id", "yandex_disk_client_secret",
                   "assistant_name", "assistant_role", "support_email", "email_subject",
                   "max_bot_id", "ai_provider"}

    for key, value in body.items():
        if key in yandex_keys:
            await settings_cache.set(key, str(value))

    return JSONResponse({"ok": True})


@app.get("/api/bot/kb")
async def api_get_kb():
    from app.models import BotKnowledgeBase
    async with async_session() as db:
        result = await db.execute(
            select(BotKnowledgeBase).order_by(BotKnowledgeBase.sort_order)
        )
        cards = result.scalars().all()
        data = []
        for c in cards:
            data.append({
                "id": c.id,
                "category_id": c.category_id,
                "question": c.question,
                "answer": c.answer or "",
                "keywords": c.keywords or [],
                "is_active": c.is_active,
                "source_url": c.source_url or "",
                "sort_order": c.sort_order,
            })
    return JSONResponse(data)


@app.post("/api/bot/kb")
async def api_create_kb(request: Request):
    from app.models import BotKnowledgeBase
    body = await request.json()
    if not body.get("question") or not body["question"].strip():
        return JSONResponse({"ok": False, "error": "Вопрос обязателен"}, status_code=400)
    async with async_session() as db:
        card = BotKnowledgeBase(
            category_id=body.get("category_id"),
            question=body["question"].strip(),
            answer=body.get("answer"),
            keywords=body.get("keywords", []),
            is_active=body.get("is_active", True),
            source_url=body.get("source_url"),
            sort_order=body.get("sort_order", 0),
        )
        db.add(card)
        await db.commit()
    return JSONResponse({"ok": True})


@app.delete("/api/bot/kb/{card_id}")
async def api_delete_kb(card_id: int):
    from app.models import BotKnowledgeBase
    async with async_session() as db:
        result = await db.execute(
            select(BotKnowledgeBase).where(BotKnowledgeBase.id == card_id)
        )
        card = result.scalar_one_or_none()
        if card:
            await db.delete(card)
            await db.commit()
    return JSONResponse({"ok": True})


@app.put("/api/bot/kb/{card_id}")
async def api_update_kb(card_id: int, request: Request):
    from app.models import BotKnowledgeBase
    body = await request.json()
    async with async_session() as db:
        result = await db.execute(
            select(BotKnowledgeBase).where(BotKnowledgeBase.id == card_id)
        )
        card = result.scalar_one_or_none()
        if not card:
            return JSONResponse({"ok": False, "error": "Not found"}, status_code=404)
        if "category_id" in body:
            card.category_id = body["category_id"]
        if "question" in body:
            card.question = body["question"]
        if "answer" in body:
            card.answer = body.get("answer")
        if "keywords" in body:
            card.keywords = body["keywords"]
        if "is_active" in body:
            card.is_active = body["is_active"]
        if "source_url" in body:
            card.source_url = body.get("source_url")
        if "sort_order" in body:
            card.sort_order = body["sort_order"]
        await db.commit()
    return JSONResponse({"ok": True})


@app.get("/api/bot/unknown-questions")
async def api_get_unknown_questions():
    from app.models import BotUnknownQuestion
    status_filter = None
    async with async_session() as db:
        query = select(BotUnknownQuestion).order_by(BotUnknownQuestion.id.desc())
        result = await db.execute(query)
        items = result.scalars().all()
        data = []
        for q in items:
            data.append({
                "id": q.id,
                "question_text": q.question_text,
                "normalized_text": q.normalized_text or "",
                "ask_count": q.ask_count,
                "user_ids": q.user_ids or [],
                "status": q.status,
                "generated_answer": q.generated_answer or "",
                "generated_keywords": q.generated_keywords or [],
                "generated_category_id": q.generated_category_id,
                "created_at": q.created_at.isoformat() if q.created_at else "",
            })
    return JSONResponse(data)


@app.put("/api/bot/unknown-questions/{qid}/approve")
async def api_approve_unknown_question(qid: int, request: Request):
    from app.models import BotUnknownQuestion, BotKnowledgeBase
    body = await request.json()
    async with async_session() as db:
        result = await db.execute(select(BotUnknownQuestion).where(BotUnknownQuestion.id == qid))
        q = result.scalar_one_or_none()
        if not q:
            return JSONResponse({"ok": False, "error": "Not found"}, status_code=404)
        card = BotKnowledgeBase(
            category_id=body.get("category_id", q.generated_category_id or 1),
            question=body.get("question", q.normalized_text or q.question_text),
            answer=body.get("answer", q.generated_answer),
            keywords=body.get("keywords", q.generated_keywords or []),
            is_active=True,
            sort_order=0,
        )
        db.add(card)
        q.status = "reviewed"
        q.reviewed_at = datetime.datetime.utcnow()
        await db.commit()
    return JSONResponse({"ok": True})


@app.put("/api/bot/unknown-questions/{qid}/edit")
async def api_edit_unknown_question(qid: int, request: Request):
    from app.models import BotUnknownQuestion, BotKnowledgeBase
    body = await request.json()
    async with async_session() as db:
        result = await db.execute(select(BotUnknownQuestion).where(BotUnknownQuestion.id == qid))
        q = result.scalar_one_or_none()
        if not q:
            return JSONResponse({"ok": False, "error": "Not found"}, status_code=404)
        card = BotKnowledgeBase(
            category_id=body.get("category_id", 1),
            question=body.get("question", q.question_text),
            answer=body.get("answer"),
            keywords=body.get("keywords", []),
            is_active=True,
            sort_order=0,
        )
        db.add(card)
        q.status = "reviewed"
        q.reviewed_at = datetime.datetime.utcnow()
        q.generated_answer = body.get("answer", q.generated_answer)
        q.generated_keywords = body.get("keywords", q.generated_keywords)
        q.generated_category_id = body.get("category_id", q.generated_category_id)
        await db.commit()
    return JSONResponse({"ok": True})


@app.put("/api/bot/unknown-questions/{qid}/ignore")
async def api_ignore_unknown_question(qid: int):
    from app.models import BotUnknownQuestion
    async with async_session() as db:
        result = await db.execute(select(BotUnknownQuestion).where(BotUnknownQuestion.id == qid))
        q = result.scalar_one_or_none()
        if not q:
            return JSONResponse({"ok": False, "error": "Not found"}, status_code=404)
        q.status = "ignored"
        q.reviewed_at = datetime.datetime.utcnow()
        await db.commit()
    return JSONResponse({"ok": True})


@app.get("/api/bot/users/consented")
async def api_get_consented_users():
    from app.models import BotUser
    async with async_session() as db:
        result = await db.execute(
            select(BotUser).where(BotUser.consent_given == True).order_by(BotUser.consent_date.desc())
        )
        users = result.scalars().all()
        data = []
        for u in users:
            data.append({
                "id": u.id,
                "first_name": u.first_name or "",
                "last_name": u.last_name or "",
                "patronymic": u.patronymic or "",
                "username": u.username or "",
                "phone": u.phone or "",
                "email": u.email or "",
                "organization": u.organization or "",
                "consent_given": u.consent_given,
                "consent_date": u.consent_date.isoformat() if u.consent_date else "",
                "phone_verified": u.phone_verified,
                "last_interaction": u.last_interaction.isoformat() if u.last_interaction else "",
                "total_conversations": u.total_conversations,
                "total_tickets": u.total_tickets,
                "created_at": u.created_at.isoformat() if u.created_at else "",
            })
    return JSONResponse(data)


@app.post("/api/bot/broadcast")
async def api_broadcast(request: Request):
    body = await request.json()
    user_ids = body.get("user_ids", [])
    text = body.get("text", "")
    send_all = body.get("all", False)

    if not text:
        return JSONResponse({"ok": False, "error": "Text is required"}, status_code=400)

    if send_all:
        from app.models import BotUser
        async with async_session() as db:
            result = await db.execute(select(BotUser))
            users = result.scalars().all()
            user_ids = [u.id for u in users if u.consent_given]

    if not user_ids:
        return JSONResponse({"ok": False, "error": "No recipients"}, status_code=400)

    sent = 0
    errors = 0
    import asyncio
    for i, uid in enumerate(user_ids):
        try:
            await max_api.send_message(uid, text)
            sent += 1
        except Exception as e:
            logger.error(f"Broadcast failed for user {uid}: {e}")
            errors += 1
        if i % 10 == 9:
            await asyncio.sleep(0.5)

    return JSONResponse({"ok": True, "sent": sent, "errors": errors})


@app.post("/api/bot/broadcast/preview")
async def api_broadcast_preview(request: Request):
    body = await request.json()
    text = body.get("text", "")
    return JSONResponse({"ok": True, "preview": text})


@app.get("/api/bot/tickets")
async def api_get_tickets():
    from app.models import BotTicket
    async with async_session() as db:
        result = await db.execute(
            select(BotTicket).order_by(BotTicket.id.desc()).limit(100)
        )
        tickets = result.scalars().all()
        data = []
        for t in tickets:
            data.append({
                "id": t.id,
                "user_id": t.user_id,
                "title": t.title or "",
                "description": t.description or "",
                "priority": t.priority,
                "status": t.status,
                "contact_name": t.contact_name or "",
                "contact_phone": t.contact_phone or "",
                "contact_email": t.contact_email or "",
                "created_at": t.created_at.isoformat() if t.created_at else "",
            })
    return JSONResponse(data)


@app.patch("/api/bot/tickets/{ticket_id}")
async def api_update_ticket(ticket_id: int, request: Request):
    from app.models import BotTicket, BotTicketStatus
    body = await request.json()
    status = body.get("status")
    if status and status not in ("Новая", "В работе", "Закрыта"):
        return JSONResponse({"ok": False, "error": "Invalid status"}, status_code=400)

    async with async_session() as db:
        result = await db.execute(
            select(BotTicket).where(BotTicket.id == ticket_id)
        )
        ticket = result.scalar_one_or_none()
        if not ticket:
            return JSONResponse({"ok": False, "error": "Not found"}, status_code=404)

        if status and status != ticket.status:
            old_status = ticket.status
            ticket.status = status
            db.add(BotTicketStatus(
                ticket_id=ticket_id,
                old_status=old_status,
                new_status=status,
                changed_by="engineer",
            ))
        await db.commit()

    return JSONResponse({"ok": True})


@app.post("/api/bot/test/clear")
async def api_clear_test_data():
    from sqlalchemy import text as sa_text
    async with engine.begin() as conn:
        await conn.execute(sa_text("DELETE FROM bot_processing_logs WHERE user_id >= 999999000"))
        result = await conn.execute(sa_text("DELETE FROM bot_users WHERE id >= 999999000"))
        deleted = result.rowcount
    return JSONResponse({"ok": True, "deleted": deleted})


@app.get("/api/bot/conversations")
async def api_get_conversations():
    from app.models import BotUser, BotMessage
    async with async_session() as db:
        result = await db.execute(
            select(BotConversation).options(
                selectinload(BotConversation.user)
            ).order_by(BotConversation.id.desc()).limit(100)
        )
        convs = result.unique().scalars().all()
        data = []
        for c in convs:
            msg_count = await db.scalar(
                select(func.count()).select_from(BotMessage).where(BotMessage.conversation_id == c.id)
            )
            data.append({
                "id": c.id,
                "user_id": c.user_id,
                "user_name": f"{c.user.first_name or ''} {c.user.last_name or ''}".strip() or str(c.user_id),
                "state": c.state,
                "intent": c.intent or "",
                "inquiry_text": c.inquiry_text or "",
                "message_count": msg_count or 0,
                "created_at": c.created_at.isoformat() if c.created_at else "",
                "updated_at": c.updated_at.isoformat() if c.updated_at else "",
            })
    return JSONResponse(data)


@app.get("/api/bot/conversations/{conv_id}/messages")
async def api_get_conversation_messages(conv_id: int):
    from app.models import BotMessage
    async with async_session() as db:
        result = await db.execute(
            select(BotMessage).where(BotMessage.conversation_id == conv_id).order_by(BotMessage.id)
        )
        msgs = result.scalars().all()
        data = []
        for m in msgs:
            data.append({
                "id": m.id,
                "direction": m.direction,
                "text": m.text or "",
                "attachment_json": m.attachment_json or "",
                "created_at": m.created_at.isoformat() if m.created_at else "",
            })
    return JSONResponse(data)


@app.get("/api/bot/conversations/{conv_id}/logs")
async def api_get_conversation_logs(conv_id: int):
    async with async_session() as db:
        conv = await db.get(BotConversation, conv_id)
        if not conv:
            return JSONResponse({"ok": False, "error": "Not found"}, status_code=404)
        result = await db.execute(
            select(BotProcessingLog).where(BotProcessingLog.user_id == conv.user_id).order_by(BotProcessingLog.id.desc()).limit(200)
        )
        logs = result.scalars().all()
        data = []
        for l in logs:
            data.append({
                "id": l.id,
                "step": l.step,
                "status": l.status,
                "message": l.message or "",
                "detail": l.detail or "",
                "created_at": l.created_at.isoformat() if l.created_at else "",
            })
    return JSONResponse(data)


@app.delete("/api/bot/conversations/{conv_id}")
async def api_delete_conversation(conv_id: int):
    from app.models import BotMessage, BotProcessingLog
    from sqlalchemy import delete as sa_delete
    async with async_session() as db:
        conv = await db.get(BotConversation, conv_id)
        if not conv:
            return JSONResponse({"ok": False, "error": "Not found"}, status_code=404)
        await db.execute(sa_delete(BotProcessingLog).where(BotProcessingLog.conversation_id == conv_id))
        await db.execute(sa_delete(BotMessage).where(BotMessage.conversation_id == conv_id))
        await db.delete(conv)
        await db.commit()
    return JSONResponse({"ok": True})


@app.delete("/api/bot/users/{user_id}")
async def api_delete_bot_user(user_id: int):
    """Каскадное удаление пользователя: BotUser + BotConversation + BotMessage + BotProcessingLog. Заявки остаются."""
    from app.models import BotMessage, BotProcessingLog
    from sqlalchemy import delete as sa_delete
    async with async_session() as db:
        user = await db.get(BotUser, user_id)
        if not user:
            return JSONResponse({"ok": False, "error": "Not found"}, status_code=404)
        conv_ids_q = await db.execute(
            select(BotConversation.id).where(BotConversation.user_id == user_id)
        )
        conv_ids = [r[0] for r in conv_ids_q.all()]
        if conv_ids:
            await db.execute(sa_delete(BotProcessingLog).where(BotProcessingLog.conversation_id.in_(conv_ids)))
            await db.execute(sa_delete(BotMessage).where(BotMessage.conversation_id.in_(conv_ids)))
            await db.execute(sa_delete(BotConversation).where(BotConversation.id.in_(conv_ids)))
        await db.delete(user)
        await db.commit()
    return JSONResponse({"ok": True})


def _extract_phone_from_vcf(vcf_info: str) -> str:
    import re
    match = re.search(r"TEL[^:]*:(\+?\d[\d\s\-()]*)", vcf_info)
    if match:
        return re.sub(r"[\s\-()]", "", match.group(1).strip())
    return ""


def _parse_vcf(vcf_info: str) -> dict:
    import re
    result = {
        "first_name": "",
        "last_name": "",
        "patronymic": "",
        "phone": "",
        "email": "",
        "organization": "",
        "address": "",
    }

    # FN: full name (first + last)
    fn_match = re.search(r"FN:(.+)", vcf_info)
    if fn_match:
        full_name = fn_match.group(1).strip()
        parts = full_name.split(None, 2)
        if len(parts) >= 2:
            result["first_name"] = parts[0]
            result["last_name"] = parts[1]
            if len(parts) >= 3:
                result["patronymic"] = parts[2]
        elif len(parts) == 1:
            result["first_name"] = parts[0]

    # N: structured name (last;first;middle;prefix;suffix)
    n_match = re.search(r"N(?:;[^:]*)?:([^\r\n]+)", vcf_info)
    if n_match:
        n_parts = n_match.group(1).split(";")
        if len(n_parts) >= 1 and n_parts[0]:
            result["last_name"] = n_parts[0]
        if len(n_parts) >= 2 and n_parts[1]:
            result["first_name"] = n_parts[1]
        if len(n_parts) >= 3 and n_parts[2]:
            result["patronymic"] = n_parts[2]

    # TEL: phone
    tel_match = re.search(r"TEL[^:]*:(\+?\d[\d\s\-()]*)", vcf_info)
    if tel_match:
        phone_raw = tel_match.group(1).strip()
        result["phone"] = re.sub(r"[\s\-()]", "", phone_raw)

    # EMAIL
    email_match = re.search(r"EMAIL[^:]*:([^\r\n]+)", vcf_info)
    if email_match:
        result["email"] = email_match.group(1).strip()

    # ORG
    org_match = re.search(r"ORG[^:]*:([^\r\n]+)", vcf_info)
    if org_match:
        result["organization"] = org_match.group(1).strip()

    # ADR: structured address
    adr_match = re.search(r"ADR[^:]*:([^\r\n]+)", vcf_info)
    if adr_match:
        result["address"] = adr_match.group(1).strip().replace(";", ", ")

    return result
