#!/usr/bin/env python3
"""
Telnyx KB Sync — MODULE 10
===========================
Syncs platform KB chunks from Qdrant to a Telnyx AI Assistant's knowledge base.

Stories implemented:
  10.01 — Telnyx KB API Wrapper (upload, list, delete documents via REST)
  10.02 — sync_kb_to_assistant (pull chunks from Qdrant, push docs to Telnyx)

Usage:
    from core.kb.telnyx_sync import (
        telnyx_upload_document,
        telnyx_list_documents,
        telnyx_delete_document,
        sync_kb_to_assistant,
    )
"""

from __future__ import annotations

import logging
import os
from collections import defaultdict
from typing import Optional

import httpx

# Imported at module level so they can be patched in tests via
# patch("core.kb.telnyx_sync.embed_text") / patch("core.kb.telnyx_sync.search_platform").
from core.kb.embedder import embed_text
from core.kb.qdrant_store import search_platform

logger = logging.getLogger(__name__)

# ──────────────────────────────────────────────────────────────────────────────
# Config
# ──────────────────────────────────────────────────────────────────────────────

TELNYX_API_KEY: str = os.getenv("TELNYX_API_KEY", "")
TELNYX_API_BASE: str = "https://api.telnyx.com/v2"

# HTTP timeout for Telnyx API calls (seconds)
_HTTP_TIMEOUT: float = 30.0

# ──────────────────────────────────────────────────────────────────────────────
# Story 10.01 — Telnyx KB API Wrapper
# ──────────────────────────────────────────────────────────────────────────────


def _auth_headers() -> dict[str, str]:
    """Return Authorization headers for the Telnyx API."""
    return {
        "Authorization": f"Bearer {TELNYX_API_KEY}",
        "Content-Type": "application/json",
        "Accept": "application/json",
    }


async def telnyx_upload_document(
    assistant_id: str,
    title: str,
    content: str,
) -> dict:
    """
    Upload a document to a Telnyx AI Assistant's knowledge base.

    Sends a POST to /ai_assistants/{assistant_id}/knowledge_base_documents
    with the document title and content as plain text.

    Returns:
        API response dict on success (contains "data" with document_id, etc.).
        Error dict {"error": str, "status_code": int} on failure.
    """
    url = f"{TELNYX_API_BASE}/ai_assistants/{assistant_id}/knowledge_base_documents"
    payload = {
        "title": title,
        "content": content,
    }

    try:
        async with httpx.AsyncClient(timeout=_HTTP_TIMEOUT) as client:
            response = await client.post(url, headers=_auth_headers(), json=payload)

        if response.status_code in (200, 201):
            return response.json()

        # Non-2xx — return structured error
        logger.warning(
            "telnyx_upload_document: HTTP %d for assistant=%s title=%r",
            response.status_code,
            assistant_id,
            title,
        )
        return {
            "error": f"HTTP {response.status_code}: {response.text[:200]}",
            "status_code": response.status_code,
        }

    except httpx.TimeoutException as exc:
        logger.error("telnyx_upload_document: timeout — %s", exc)
        return {"error": f"Timeout: {exc}", "status_code": 0}
    except Exception as exc:  # noqa: BLE001
        logger.error("telnyx_upload_document: unexpected error — %s", exc)
        return {"error": str(exc), "status_code": 0}


async def telnyx_list_documents(assistant_id: str) -> list[dict]:
    """
    List all documents in a Telnyx AI Assistant's knowledge base.

    Returns:
        List of document dicts from the API response (may be empty on error).
    """
    url = f"{TELNYX_API_BASE}/ai_assistants/{assistant_id}/knowledge_base_documents"

    try:
        async with httpx.AsyncClient(timeout=_HTTP_TIMEOUT) as client:
            response = await client.get(url, headers=_auth_headers())

        if response.status_code == 200:
            data = response.json()
            # Telnyx wraps results in {"data": [...]}
            if isinstance(data, dict) and "data" in data:
                return data["data"]
            if isinstance(data, list):
                return data
            return []

        logger.warning(
            "telnyx_list_documents: HTTP %d for assistant=%s",
            response.status_code,
            assistant_id,
        )
        return []

    except Exception as exc:  # noqa: BLE001
        logger.error("telnyx_list_documents: error — %s", exc)
        return []


async def telnyx_delete_document(assistant_id: str, document_id: str) -> bool:
    """
    Delete a document from a Telnyx AI Assistant's knowledge base.

    Returns:
        True if deletion succeeded (HTTP 200/204), False otherwise.
    """
    url = f"{TELNYX_API_BASE}/ai_assistants/{assistant_id}/knowledge_base_documents/{document_id}"

    try:
        async with httpx.AsyncClient(timeout=_HTTP_TIMEOUT) as client:
            response = await client.delete(url, headers=_auth_headers())

        if response.status_code in (200, 204):
            return True

        logger.warning(
            "telnyx_delete_document: HTTP %d for assistant=%s doc=%s",
            response.status_code,
            assistant_id,
            document_id,
        )
        return False

    except Exception as exc:  # noqa: BLE001
        logger.error("telnyx_delete_document: error — %s", exc)
        return False


# ──────────────────────────────────────────────────────────────────────────────
# Story 10.02 — sync_kb_to_assistant
# ──────────────────────────────────────────────────────────────────────────────


def _group_chunks_by_url(chunks: list[dict]) -> dict[str, list[dict]]:
    """
    Group chunk dicts by source_url.

    Within each group, chunks are sorted by chunk_index (ascending) so that
    document content is assembled in the correct reading order.
    """
    groups: dict[str, list[dict]] = defaultdict(list)
    for chunk in chunks:
        url = chunk.get("source_url", "")
        groups[url].append(chunk)

    # Sort each group by chunk_index
    for url in groups:
        groups[url].sort(key=lambda c: c.get("chunk_index", 0))

    return dict(groups)


def _build_document(url: str, ordered_chunks: list[dict]) -> tuple[str, str]:
    """
    Build a (title, content) tuple from an ordered list of chunk dicts.

    - Title  = first chunk's ``title`` field (falls back to the URL).
    - Content = chunk texts joined by double newline.
    """
    title = ordered_chunks[0].get("title", "") if ordered_chunks else ""
    if not title:
        title = url

    content = "\n\n".join(c.get("text", "") for c in ordered_chunks if c.get("text", ""))
    return title, content


async def sync_kb_to_assistant(
    platform: str,
    assistant_id: str,
    customer_id: Optional[str] = None,
    max_chunks: int = 500,
) -> dict:
    """
    Sync platform KB chunks from Qdrant to a Telnyx AI Assistant's knowledge base.

    Steps:
    1. Embed the platform name as a broad query vector to retrieve all chunks.
    2. Search Qdrant for up to max_chunks chunks for this platform.
    3. Group chunks by source_url — one Telnyx document per unique URL.
    4. Upload each document to the Telnyx assistant.
    5. Return sync statistics.

    Returns:
        {
            "platform": str,
            "assistant_id": str,
            "documents_synced": int,
            "chunks_total": int,
            "errors": int,
            "error_details": list[str],
        }
    """
    stats: dict = {
        "platform": platform,
        "assistant_id": assistant_id,
        "documents_synced": 0,
        "chunks_total": 0,
        "errors": 0,
        "error_details": [],
    }

    # Step 1: Generate a broad query vector by embedding the platform name
    try:
        query_vector = embed_text(platform)
    except Exception as exc:  # noqa: BLE001
        msg = f"embed_text failed for platform={platform!r}: {exc}"
        logger.error("sync_kb_to_assistant: %s", msg)
        stats["errors"] += 1
        stats["error_details"].append(msg)
        return stats

    # Step 2: Retrieve chunks from Qdrant (score_threshold=0.0 → return all matches)
    try:
        chunks = search_platform(
            query_vector=query_vector,
            platform=platform,
            customer_id=customer_id,
            top_k=max_chunks,
            score_threshold=0.0,
        )
    except Exception as exc:  # noqa: BLE001
        msg = f"search_platform failed for platform={platform!r}: {exc}"
        logger.error("sync_kb_to_assistant: %s", msg)
        stats["errors"] += 1
        stats["error_details"].append(msg)
        return stats

    stats["chunks_total"] = len(chunks)

    if not chunks:
        logger.info(
            "sync_kb_to_assistant: 0 chunks found for platform=%r — nothing to sync",
            platform,
        )
        return stats

    # Step 3: Group by source_url
    grouped = _group_chunks_by_url(chunks)

    # Step 4: Upload one document per URL group
    for url, ordered_chunks in grouped.items():
        title, content = _build_document(url, ordered_chunks)

        if not content.strip():
            logger.debug("sync_kb_to_assistant: empty content for url=%r — skipping", url)
            continue

        result = await telnyx_upload_document(
            assistant_id=assistant_id,
            title=title,
            content=content,
        )

        if "error" in result:
            stats["errors"] += 1
            stats["error_details"].append(
                f"Upload failed for url={url!r}: {result['error']}"
            )
            logger.warning(
                "sync_kb_to_assistant: upload error for url=%r — %s",
                url,
                result["error"],
            )
        else:
            stats["documents_synced"] += 1
            logger.debug(
                "sync_kb_to_assistant: uploaded document for url=%r (title=%r)",
                url,
                title,
            )

    return stats


# VERIFICATION_STAMP
# Story: M10 — Telnyx KB Sync (Stories 10.01–10.02)
# Verified By: parallel-builder (claude-sonnet-4-6)
# Verified At: 2026-02-26
# Tests: see tests/kb/test_m10_telnyx_sync_integration.py
# Coverage: 100% of stories implemented
