from typing import Annotated, Any, Optional
from datetime import datetime, timedelta
import hashlib
from secrets import token_urlsafe

from fastapi import APIRouter, Depends, Header, HTTPException
from pydantic import BaseModel
from sqlalchemy import select
from sqlalchemy.orm import Session

from app.core.config import get_telegram_config
from app.core.database import get_db
from app.core.models import AIUsageSource, SystemConfig, TelegramBinding, TelegramBindingStatus, TelegramBindingToken, TelegramLog, TelegramLogStatus, Tenant, TenantStatus, User, UserStatus
from app.core.security import require_admin, require_permission
from app.services.ai_quota_service import AIQuotaExceeded
from app.services import receipt_service
from app.services.telegram_service import (
    download_telegram_file,
    get_telegram_webhook_info,
    send_telegram_message,
    set_telegram_webhook,
)

router = APIRouter(prefix="/api/telegram", tags=["telegram"])
_TELEGRAM_CONTEXT_MESSAGE_LIMIT = 16
_TELEGRAM_CHAT_SESSIONS: dict[tuple[int, str], dict[str, Any]] = {}


class TelegramSendRequest(BaseModel):
    chat_id: str
    message: str


class TelegramWebhookUpdate(BaseModel):
    url: str


class TelegramBindingRequest(BaseModel):
    user_id: int


def _binding_code() -> tuple[str, str]:
    code = token_urlsafe(18)
    return code, hashlib.sha256(code.encode()).hexdigest()


@router.post("/bindings")
def create_binding(data: TelegramBindingRequest, current_user: User = Depends(require_admin), db: Session = Depends(get_db)):
    user = db.execute(select(User).where(User.id == data.user_id, User.tenant_id == current_user.tenant_id)).scalar_one_or_none()
    if user is None:
        raise HTTPException(status_code=404, detail="User not found")
    code, token_hash = _binding_code()
    db.add(TelegramBindingToken(tenant_id=current_user.tenant_id, user_id=user.id, token_hash=token_hash, expires_at=datetime.utcnow() + timedelta(minutes=15), created_by_user_id=current_user.id))
    db.commit()
    return {"code": code, "expires_in_seconds": 900}


@router.get("/bindings")
def list_bindings(current_user: User = Depends(require_admin), db: Session = Depends(get_db)):
    return db.execute(select(TelegramBinding).where(TelegramBinding.tenant_id == current_user.tenant_id, TelegramBinding.status == TelegramBindingStatus.active)).scalars().all()


@router.delete("/bindings/{binding_id}", status_code=204)
def unbind(binding_id: int, current_user: User = Depends(require_admin), db: Session = Depends(get_db)):
    binding = db.execute(select(TelegramBinding).where(TelegramBinding.id == binding_id, TelegramBinding.tenant_id == current_user.tenant_id)).scalar_one_or_none()
    if binding is None:
        raise HTTPException(status_code=404, detail="Binding not found")
    binding.status, binding.unbound_at = TelegramBindingStatus.unbound, datetime.utcnow()
    db.commit()


class ChatResponse(BaseModel):
    reply: str
    action: Optional[str] = None
    draft_id: Optional[int] = None
    customer_id: Optional[int] = None
    source: Optional[str] = None
    tool_name: Optional[str] = None
    result: Optional[dict[str, Any]] = None


def _raise_telegram_error(result: dict) -> None:
    raise HTTPException(
        status_code=int(result.get("status_code") or 400),
        detail=result.get("error") or "Telegram send failed",
    )


def _chat_id_from_update(update: dict) -> str:
    message = update.get("message") or update.get("edited_message") or {}
    chat_id = (message.get("chat") or {}).get("id")
    return str(chat_id).strip() if chat_id is not None else ""


def _telegram_user_id_from_update(update: dict) -> str:
    message = update.get("message") or update.get("edited_message") or {}
    user_id = (message.get("from") or {}).get("id")
    return str(user_id).strip() if user_id is not None else ""


def _telegram_username(update: dict) -> str | None:
    message = update.get("message") or update.get("edited_message") or {}
    return ((message.get("from") or {}).get("username") or "").strip() or None


def _tenant_id_for_webhook_secret(db: Session, secret: str) -> int | None:
    tenant_ids = db.execute(
        select(SystemConfig.tenant_id)
        .join(Tenant, Tenant.id == SystemConfig.tenant_id)
        .where(
            Tenant.status == TenantStatus.active,
            SystemConfig.key_name == "telegram_webhook_secret",
            SystemConfig.key_value == secret,
        )
    ).scalars().all()
    return tenant_ids[0] if len(tenant_ids) == 1 else None


def _chat_belongs_to_tenant(db: Session, tenant_id: int, chat_id: str) -> bool:
    return db.execute(
        select(SystemConfig.id).where(
            SystemConfig.tenant_id == tenant_id,
            SystemConfig.key_name == "telegram_chat_id",
            SystemConfig.key_value == chat_id,
        )
    ).scalar_one_or_none() is not None


def _consume_binding_code(db: Session, code: str, telegram_user_id: str, chat_id: str, bot_tenant_id: int) -> TelegramBinding | None:
    token = db.execute(select(TelegramBindingToken).where(
        TelegramBindingToken.token_hash == hashlib.sha256(code.encode()).hexdigest(),
        TelegramBindingToken.used_at.is_(None),
        TelegramBindingToken.expires_at > datetime.utcnow(),
    )).scalar_one_or_none()
    if token is None or token.user_id is None:
        return None
    bot_tenant = db.get(Tenant, bot_tenant_id)
    if token.tenant_id != bot_tenant_id and (bot_tenant is None or bot_tenant.company_code != "platform"):
        return None
    user = db.get(User, token.user_id)
    target_tenant = db.get(Tenant, token.tenant_id)
    if (
        user is None
        or user.tenant_id != token.tenant_id
        or user.status != UserStatus.active
        or target_tenant is None
        or target_tenant.status != TenantStatus.active
        or not target_tenant.telegram_enabled
    ):
        return None
    binding = db.execute(select(TelegramBinding).where(TelegramBinding.user_id == token.user_id)).scalar_one_or_none()
    if binding is None:
        binding = TelegramBinding(tenant_id=token.tenant_id, user_id=token.user_id, telegram_user_id=telegram_user_id, telegram_chat_id=chat_id)
        db.add(binding)
    else:
        binding.telegram_user_id, binding.telegram_chat_id = telegram_user_id, chat_id
        binding.status, binding.unbound_at = TelegramBindingStatus.active, None
    token.used_at = datetime.utcnow()
    db.commit()
    db.refresh(binding)
    return binding


def _active_binding(db: Session, telegram_user_id: str) -> tuple[TelegramBinding, User] | None:
    binding = db.execute(select(TelegramBinding).where(
        TelegramBinding.telegram_user_id == telegram_user_id,
        TelegramBinding.status == TelegramBindingStatus.active,
    )).scalar_one_or_none()
    if binding is None:
        return None
    user = db.get(User, binding.user_id)
    tenant = db.get(Tenant, binding.tenant_id)
    if user is None or user.status != UserStatus.active or tenant is None or tenant.status != TenantStatus.active or not tenant.telegram_enabled:
        return None
    return binding, user


def _receipt_file_id_from_update(update: dict) -> str | None:
    message = update.get("message") or update.get("edited_message") or {}
    photos = message.get("photo") or []
    if photos:
        return max(photos, key=lambda item: int(item.get("file_size") or 0)).get("file_id")
    document = message.get("document") or {}
    mime_type = (document.get("mime_type") or "").lower()
    file_name = (document.get("file_name") or "").lower()
    if mime_type.startswith("image/") or mime_type == "application/pdf" or file_name.endswith(".pdf"):
        return document.get("file_id")
    return None


def _text_from_update(update: dict) -> str:
    message = update.get("message") or update.get("edited_message") or {}
    return str(message.get("text") or message.get("caption") or "").strip()


def _receipt_summary(expense_id: int, processing_status: str) -> str:
    return (
        f"Receipt draft #{expense_id} created. Processing status: {processing_status}.\n\n"
        "This has not been posted to financial reports or GST. Confirm it in the receipt inbox."
    )


def _get_chat_session(tenant_id: int, chat_id: str) -> dict[str, Any]:
    return _TELEGRAM_CHAT_SESSIONS.setdefault((tenant_id, chat_id), {"active": True, "context": []})


def _reset_chat_session(tenant_id: int, chat_id: str) -> None:
    _TELEGRAM_CHAT_SESSIONS[(tenant_id, chat_id)] = {"active": True, "context": []}


def _stop_chat_session(tenant_id: int, chat_id: str) -> None:
    _TELEGRAM_CHAT_SESSIONS[(tenant_id, chat_id)] = {"active": False, "context": []}


def _append_chat_history(tenant_id: int, chat_id: str, message: str, reply: str) -> None:
    session = _get_chat_session(tenant_id, chat_id)
    context = session["context"]
    context.extend([{"role": "user", "content": message}, {"role": "assistant", "content": reply}])
    if len(context) > _TELEGRAM_CONTEXT_MESSAGE_LIMIT:
        session["context"] = context[-_TELEGRAM_CONTEXT_MESSAGE_LIMIT:]


async def handle_ai_chat_message(
    db: Session,
    message: str,
    context: list[dict] | None = None,
    tenant_id: int | None = None,
    user_id: int | None = None,
    user: User | None = None,
):
    from app.api.ai import handle_ai_chat_message as shared_ai_chat_handler

    return await shared_ai_chat_handler(
        db=db,
        message=message,
        context=context,
        tenant_id=tenant_id,
        source=AIUsageSource.telegram,
        user_id=user_id,
        user=user,
    )


async def _reply(db: Session, tenant_id: int, chat_id: str, message: str) -> None:
    await send_telegram_message(chat_id, message, db=db, tenant_id=tenant_id)


@router.post("/webhook")
async def telegram_webhook(
    update: dict,
    x_telegram_bot_api_secret_token: Annotated[
        str | None,
        Header(alias="X-Telegram-Bot-Api-Secret-Token"),
    ] = None,
    db: Session = Depends(get_db),
):
    tenant_id = _tenant_id_for_webhook_secret(db, x_telegram_bot_api_secret_token or "")
    if tenant_id is None:
        raise HTTPException(status_code=403, detail="Invalid Telegram webhook secret")
    chat_id = _chat_id_from_update(update)
    telegram_user_id = _telegram_user_id_from_update(update)
    text = _text_from_update(update)
    if text.lower().startswith("/start "):
        binding = _consume_binding_code(db, text.split(maxsplit=1)[1].strip(), telegram_user_id, chat_id, tenant_id)
        if binding is None:
            await _reply(db, tenant_id, chat_id, "Binding code is invalid or expired.")
            return {"ok": True, "handled": "invalid_binding"}
        await _reply(db, tenant_id, chat_id, "Telegram linked. You can now use the ERP assistant.")
        return {"ok": True, "handled": "bound"}
    resolved = _active_binding(db, telegram_user_id)
    if resolved is None:
        await _reply(db, tenant_id, chat_id, "Please ask your ERP administrator for a binding code, then send /start <code>.")
        return {"ok": True, "handled": "unbound"}
    binding, bound_user = resolved
    tenant_id = binding.tenant_id

    file_id = _receipt_file_id_from_update(update)
    if not file_id:
        if not text:
            return {"ok": True, "ignored": True}
        command = text.lower()
        if command == "/new":
            _reset_chat_session(tenant_id, chat_id)
            await _reply(db, tenant_id, chat_id, "Started a new AI chat with /new.")
            return {"ok": True, "handled": "new_chat"}
        if command == "/stop":
            _stop_chat_session(tenant_id, chat_id)
            await _reply(db, tenant_id, chat_id, "AI chat stopped. Send /new to restart.")
            return {"ok": True, "handled": "stop_chat"}

        session = _get_chat_session(tenant_id, chat_id)
        if not session["active"]:
            await _reply(db, tenant_id, chat_id, "AI chat is stopped. Send /new to restart.")
            return {"ok": True, "handled": "chat_stopped"}
        try:
            response = await handle_ai_chat_message(
                db=db,
                message=text,
                context=list(session["context"]),
                tenant_id=tenant_id,
                user_id=bound_user.id,
                user=bound_user,
            )
        except AIQuotaExceeded as exc:
            await _reply(db, tenant_id, chat_id, f"AI {exc.window} credit limit reached.")
            return {"ok": True, "handled": "ai_quota_reached"}
        await _reply(db, tenant_id, chat_id, response.reply)
        _append_chat_history(tenant_id, chat_id, text, response.reply)
        return {"ok": True, "handled": "ai_chat", "action": response.action}

    await _reply(db, tenant_id, chat_id, "Receipt received; saving and analyzing it now.")
    downloaded = await download_telegram_file(file_id, db=db, tenant_id=tenant_id)
    if not downloaded.get("success"):
        await _reply(db, tenant_id, chat_id, f"Receipt download failed: {downloaded.get('error') or 'unknown error'}")
        return {"ok": False, "error": downloaded.get("error")}
    result = await receipt_service.process_receipt_upload(
        db=db,
        filename=downloaded["filename"],
        content_type=downloaded["content_type"],
        file_bytes=downloaded["file_bytes"],
        upload_user=f"telegram:{_telegram_username(update) or chat_id}",
        source_channel="telegram",
        tenant_id=tenant_id,
    )
    await _reply(db, tenant_id, chat_id, _receipt_summary(result["expense_id"], result["processing_status"]))
    return {"ok": True, **result}


@router.post("/send")
async def send_message(
    req: TelegramSendRequest,
    current_user: Annotated[dict, Depends(require_permission("settings.write"))],
    db: Session = Depends(get_db),
):
    result = await send_telegram_message(req.chat_id, req.message, db=db, tenant_id=current_user.tenant_id)
    db.add(TelegramLog(tenant_id=current_user.tenant_id, chat_id=req.chat_id, message=req.message,
                       status=TelegramLogStatus.sent if result["success"] else TelegramLogStatus.failed,
                       error_message=result.get("error")))
    db.commit()
    if not result["success"]:
        _raise_telegram_error(result)
    return {"success": True}


@router.post("/test")
async def test_telegram(
    current_user: Annotated[dict, Depends(require_permission("settings.write"))],
    db: Session = Depends(get_db),
):
    chat_id = (get_telegram_config(db, current_user.tenant_id).get("chat_id") or "").strip()
    if not chat_id:
        raise HTTPException(status_code=400, detail="Telegram chat_id not configured")
    result = await send_telegram_message(chat_id, "ERP System Test Message", db=db, tenant_id=current_user.tenant_id)
    db.add(TelegramLog(
        tenant_id=current_user.tenant_id,
        chat_id=chat_id,
        message="ERP System Test Message",
        status=TelegramLogStatus.sent if result["success"] else TelegramLogStatus.failed,
        error_message=result.get("error"),
    ))
    db.commit()
    if not result["success"]:
        _raise_telegram_error(result)
    return {"success": True, "message": "Test sent"}


@router.get("/webhook-info")
async def webhook_info(
    current_user: Annotated[dict, Depends(require_permission("settings.read"))],
    db: Session = Depends(get_db),
):
    result = await get_telegram_webhook_info(db=db, tenant_id=current_user.tenant_id)
    if not result.get("success"):
        _raise_telegram_error(result)
    return result


@router.post("/webhook-url")
async def update_webhook_url(
    req: TelegramWebhookUpdate,
    current_user: Annotated[dict, Depends(require_permission("settings.write"))],
    db: Session = Depends(get_db),
):
    secret_row = db.execute(
        select(SystemConfig).where(
            SystemConfig.tenant_id == current_user.tenant_id,
            SystemConfig.key_name == "telegram_webhook_secret",
        )
    ).scalar_one_or_none()
    if secret_row is None:
        secret_row = SystemConfig(
            tenant_id=current_user.tenant_id,
            key_name="telegram_webhook_secret",
            key_value=token_urlsafe(32),
            is_secret=True,
        )
        db.add(secret_row)
        db.flush()

    result = await set_telegram_webhook(
        req.url,
        db=db,
        tenant_id=current_user.tenant_id,
        secret_token=secret_row.key_value,
    )
    if not result.get("success"):
        db.rollback()
        _raise_telegram_error(result)
    db.commit()
    return result


@router.get("/logs")
async def get_logs(
    current_user: Annotated[dict, Depends(require_permission("settings.read"))],
    page: int = 1,
    page_size: int = 50,
    db: Session = Depends(get_db),
):
    query = db.query(TelegramLog).filter(TelegramLog.tenant_id == current_user.tenant_id).order_by(TelegramLog.sent_at.desc())
    total = query.count()
    return {"items": query.offset((page - 1) * page_size).limit(page_size).all(), "total": total, "page": page, "page_size": page_size}
