from __future__ import annotations
import logging
import json
from contextlib import asynccontextmanager
from typing import Optional
from fastapi import FastAPI, Request, HTTPException
from fastapi.responses import JSONResponse
from sqlalchemy import select, text

from app.config import settings
from app.database import engine, async_session
from app.models import Base
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
        _add_column_if_not_exists(conn, "bot_users", "patronymic", "VARCHAR(255)")
        _add_column_if_not_exists(conn, "bot_users", "email", "VARCHAR(255)")
        _add_column_if_not_exists(conn, "bot_users", "organization", "VARCHAR(255)")
        _add_column_if_not_exists(conn, "bot_users", "address", "TEXT")
        _add_column_if_not_exists(conn, "bot_users", "vcf_raw", "TEXT")
        _add_column_if_not_exists(conn, "bot_users", "contact_hash", "VARCHAR(255)")
        _add_column_if_not_exists(conn, "bot_users", "phone_verified", "BOOLEAN DEFAULT FALSE")
        _add_column_if_not_exists(conn, "bot_users", "email_verified", "BOOLEAN DEFAULT FALSE")
        _add_column_if_not_exists(conn, "bot_users", "last_interaction", "TIMESTAMP")
        _add_column_if_not_exists(conn, "bot_users", "total_conversations", "INTEGER DEFAULT 0")
        _add_column_if_not_exists(conn, "bot_users", "total_tickets", "INTEGER DEFAULT 0")


def _add_column_if_not_exists(conn, table: str, column: str, col_type: str):
    from sqlalchemy import text as sa_text
    try:
        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 _seed_default_settings():
    defaults = {
        "assistant_name": "София",
        "assistant_role": "Представитель компании AegisOne Engineering, помогающий клиентам с запросами по безопасности",
        "support_email": "zakaz@aegisone.ru",
        "email_subject": "Обращение через Виртуального помощника AegisOne Engineering",
    }
    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():
    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:
    secret = request.headers.get("X-Max-Bot-Api-Secret", "")
    return secret == settings.webhook_secret


@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}")

    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:
                await handle_greeting(user_id, payload, first_name, last_name, username)

        elif update_type == "message_created":
            user_id = body.get("sender", {}).get("user_id") or body.get("chat_id")
            text = body.get("body", {}).get("text", "")

            attachments = body.get("body", {}).get("attachments", [])
            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 handle_contact_share(user_id, conv_id, phone, vcf_info, vcf_data, contact_hash)
                    return JSONResponse({"ok": True})

            if user_id and text:
                await handle_message(user_id, text)

        elif update_type == "dialog_cleared":
            user_id = body.get("user", {}).get("user_id") or body.get("chat_id")
            if user_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":
            user_id = body.get("user", {}).get("user_id") or body.get("chat_id")
            callback_id = body.get("callback", {}).get("callback_id", "")
            callback_data = body.get("callback", {})

            if user_id and callback_id:
                await handle_callback(user_id, callback_id, callback_data)

        return JSONResponse({"ok": True})

    except Exception as e:
        logger.exception(f"Error processing webhook: {e}")
        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"}

    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/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,
                "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)

        old_status = ticket.status
        if 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})


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


def _extract_name_from_vcf(vcf_info: str) -> str:
    import re
    match = re.search(r"FN:(.+)", vcf_info)
    if match:
        return 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
