"""
Intradesk REST API: создание заявок и чтение истории/комментариев.

Эндпоинты:
  POST /changes/v3/tasks              — создать заявку
  GET  /taskhistory/api/v2.0/lifetime/{id}/full — история (комментарии, смена статуса...)
"""
from __future__ import annotations

import json
import logging
import re

import httpx

import config

logger = logging.getLogger(__name__)

_TAG_RE = re.compile(r"<[^>]+>")


def _strip_html(text: str) -> str:
    """Убрать HTML-теги и лишние пробелы из текста комментария."""
    if not text:
        return text
    cleaned = _TAG_RE.sub("", text)
    # Схлопнуть множественные пробелы/переносы
    cleaned = re.sub(r"\s{2,}", " ", cleaned).strip()
    return cleaned


async def get_task_last_comment(task_id: int) -> dict | None:
    """
    Получить последний комментарий к заявке из Intradesk.

    Возвращает dict: {text, author, date}  или  None если нет комментариев.
    """
    key = config.INTRADESK_TASKS_API_KEY
    if not key:
        return None

    url = (
        f"{config.INTRADESK_BASE_URL.rstrip('/')}"
        f"/taskhistory/api/v2.0/lifetime/{task_id}/full"
        f"?ApiKey={key}&sortDirection=Desc&top=10&events=50"
    )
    try:
        async with httpx.AsyncClient() as client:
            resp = await client.get(url, timeout=15.0)
        if resp.status_code != 200:
            logger.warning(
                "get_task_last_comment task=%s → HTTP %s", task_id, resp.status_code
            )
            return None
        data = resp.json()
    except Exception as exc:
        logger.warning("get_task_last_comment error: %s", exc)
        return None

    # Ищем последнее событие с blockname="comment" (type=50)
    for entry in data.get("data") or []:
        for ev in (entry.get("events") or {}).get("data") or []:
            if ev.get("blockname") == "comment" and ev.get("type") == 50:
                raw_date = entry.get("eventat") or ""
                # "2021-03-15T11:54:36.92Z" → "2021-03-15 11:54"
                short_date = raw_date[:16].replace("T", " ")
                return {
                    "text": _strip_html(ev.get("stringvalue") or ""),
                    "author": entry.get("username") or "—",
                    "date": short_date,
                }
    return None


async def count_client_open_tasks(client_id: int) -> int:
    """Количество незакрытых заявок клиента через API.

    Intradesk не возвращает @odata.count при $top=0, поэтому берём $top=1
    — count в ответе отражает полный итог, а не размер страницы.
    """
    key = config.INTRADESK_TASKS_API_KEY
    if not key:
        return 0
    url = (
        f"{config.INTRADESK_BASE_URL.rstrip('/')}/tasklist/odata/v3/tasks"
        f"?ApiKey={key}"
        f"&$filter=(clientid eq {client_id}) and (closedat eq null) and (isarchived eq false)"
        f"&$top=1&$count=true"
    )
    try:
        async with httpx.AsyncClient() as client:
            resp = await client.get(url, timeout=10.0)
        if resp.status_code != 200:
            return 0
        data = resp.json()
        # @odata.count — полный итог по фильтру, не зависит от $top
        count = data.get("@odata.count")
        if count is not None:
            return int(count)
        # Fallback: если count не вернулся — считаем по размеру value
        return len(data.get("value") or [])
    except Exception:
        return 0


async def get_client_last_comment(client_id: int) -> dict | None:
    """
    Найти последний комментарий по любой незакрытой заявке клиента.

    Берём задачу с самым свежим updatedat → ищем последний комментарий в её истории.
    Возвращает {text, author, date, task_number} или None.
    """
    key = config.INTRADESK_TASKS_API_KEY
    if not key:
        return None

    # Шаг 1: самая свежая задача клиента
    url = (
        f"{config.INTRADESK_BASE_URL.rstrip('/')}/tasklist/odata/v3/tasks"
        f"?ApiKey={key}"
        f"&$filter=(clientid eq {client_id}) and (closedat eq null) and (isarchived eq false)"
        f"&$orderby=updatedat desc&$top=1"
    )
    try:
        async with httpx.AsyncClient() as client:
            resp = await client.get(url, timeout=15.0)
        if resp.status_code != 200:
            return None
        data = resp.json()
    except Exception:
        return None

    tasks = data.get("value") or []
    if not tasks:
        return None

    task = tasks[0]
    task_id = task.get("id")
    task_number = task.get("tasknumber")
    if not task_id:
        return None

    # Шаг 2: последний комментарий этой задачи
    comment = await get_task_last_comment(task_id)
    if comment:
        comment["task_number"] = task_number
    return comment


async def get_client_open_tasks(
    client_id: int,
    page: int = 0,
    page_size: int = 5,
) -> dict:
    """
    Получить незакрытые заявки клиента из Intradesk API.

    Возвращает {"tasks": [...], "total": int}.
    Последний комментарий загружается параллельно через history API для каждой задачи.
    """
    import asyncio

    key = config.INTRADESK_TASKS_API_KEY
    if not key:
        return {"tasks": [], "total": 0}

    skip = page * page_size
    url = (
        f"{config.INTRADESK_BASE_URL.rstrip('/')}/tasklist/odata/v3/tasks"
        f"?ApiKey={key}"
        f"&$filter=(clientid eq {client_id}) and (closedat eq null) and (isarchived eq false)"
        f"&$orderby=updatedat desc"
        f"&$top={page_size}&$skip={skip}"
        f"&$count=true"
    )
    try:
        async with httpx.AsyncClient() as client:
            resp = await client.get(url, timeout=20.0)
        if resp.status_code != 200:
            logger.warning("get_client_open_tasks client=%s → HTTP %s", client_id, resp.status_code)
            return {"tasks": [], "total": 0}
        data = resp.json()
    except Exception as exc:
        logger.warning("get_client_open_tasks error: %s", exc)
        return {"tasks": [], "total": 0}

    total = data.get("@odata.count") or 0
    raw_tasks = data.get("value") or []

    # Словари для разрешения имён
    dicts_map: dict[int, str] = {}
    for d in data.get("dictionaries") or []:
        did = d.get("id")
        dname = d.get("name") or ""
        if isinstance(dname, dict):
            dname = dname.get("ru") or dname.get("en") or ""
        if did:
            dicts_map[did] = dname

    # Базовые данные задач
    base_tasks = []
    for t in raw_tasks:
        status_id = t.get("status")
        base_tasks.append({
            "id": t.get("id"),
            "task_number": t.get("tasknumber"),
            "name": t.get("name") or "",
            "status_name": dicts_map.get(status_id, "") if status_id else "",
            "created_at": t.get("createdat") or "",
            "updated_at": t.get("updatedat") or "",
        })

    # Параллельно загружаем последний комментарий для каждой задачи
    task_ids = [t["id"] for t in base_tasks if t["id"]]
    comments = await asyncio.gather(
        *[get_task_last_comment(tid) for tid in task_ids],
        return_exceptions=True,
    )

    tasks = []
    for task, comment in zip(base_tasks, comments):
        task["last_comment"] = comment if isinstance(comment, dict) else None
        tasks.append(task)

    return {"tasks": tasks, "total": total}


def _extract_last_comment_from_lifetime(lifetime: dict | None) -> dict | None:
    """Извлечь последний комментарий из поля lifetime задачи (если последнее событие — комментарий)."""
    if not lifetime:
        return None
    for entry in (lifetime.get("data") or []):
        for ev in (entry.get("events") or {}).get("data") or []:
            if ev.get("blockname") == "comment" and ev.get("type") == 50:
                raw_date = entry.get("eventat") or ""
                short_date = raw_date[:16].replace("T", " ")  # "2026-03-26 11:54"
                return {
                    "text": _strip_html(ev.get("stringvalue") or ""),
                    "author": entry.get("username") or "—",
                    "date": short_date,
                }
    return None


async def add_task_comment(
    task_number: int,
    text: str,
    contact_person_id: int | None = None,
) -> bool:
    """
    Добавить комментарий к заявке через Intradesk API (PUT /changes/v3/tasks).

    Возвращает True при успехе, False при ошибке.
    """
    key = config.INTRADESK_CHANGES_API_KEY
    if not key:
        logger.warning("add_task_comment: INTRADESK_CHANGES_API_KEY не задан")
        return False

    blocks: dict[str, str] = {
        "comment": json.dumps({"value": text}, ensure_ascii=False),
    }
    body = {"number": task_number, "blocks": blocks, "Channel": "api"}
    url = f"{config.INTRADESK_BASE_URL.rstrip('/')}/changes/v3/tasks?ApiKey={key}"
    if contact_person_id is not None:
        url += f"&userid={contact_person_id}"

    if config.DRY_RUN:
        print(
            "\n[DRY-RUN] add_task_comment — запрос НЕ отправлен\n"
            f"  PUT {url}\n"
            f"  Body: {json.dumps(body, ensure_ascii=False, indent=2)}\n"
        )
        return False

    try:
        async with httpx.AsyncClient() as client:
            resp = await client.put(
                url,
                json=body,
                headers={"Content-Type": "application/json"},
                timeout=20.0,
            )
        if resp.status_code not in (200, 201):
            logger.error(
                "add_task_comment → HTTP %s: %s",
                resp.status_code,
                resp.text[:500],
            )
            return False
        logger.info("add_task_comment ok: task_number=%s", task_number)
        return True
    except Exception as exc:
        logger.exception("add_task_comment error: %s", exc)
        return False


async def create_intradesk_task(
    name: str,
    description: str,
    client_intradesk_id: int | None = None,
    contact_person_id: int | None = None,
    attachment_urls: list[str] | None = None,
) -> dict | None:
    """
    Создать заявку в Intradesk.

    Возвращает ответ API (dict с полями id, tasknumber и др.) или None при ошибке.
    Вложения — пока добавляются как ссылки в конец описания (до появления upload API).
    В dry-run режиме запрос печатается в консоль и возвращается None.
    """
    key = config.INTRADESK_CHANGES_API_KEY
    if not key:
        logger.warning("create_intradesk_task: INTRADESK_CHANGES_API_KEY не задан")
        return None

    # Вложения — ссылки в конце описания
    full_desc = description
    if attachment_urls:
        full_desc += "\n\nВложения:\n" + "\n".join(attachment_urls)

    blocks: dict[str, str] = {
        "name": json.dumps({"value": name}, ensure_ascii=False),
        "description": json.dumps({"value": full_desc}, ensure_ascii=False),
    }
    # initiator: вариант 3 из документации — клиент + пользователь клиента.
    # clientid заполняется автоматически из initiatorclient, отдельно не нужен.
    if client_intradesk_id is not None and contact_person_id is not None:
        blocks["initiator"] = json.dumps(
            {"value": {"groupid": client_intradesk_id, "userid": contact_person_id}},
            ensure_ascii=False,
        )
    elif client_intradesk_id is not None:
        # только клиент (нет конкретного контактного лица)
        blocks["initiator"] = json.dumps(
            {"value": {"groupid": client_intradesk_id}}, ensure_ascii=False
        )
    if config.INTRADESK_DEFAULT_SERVICE:
        blocks["service"] = json.dumps(
            {"value": config.INTRADESK_DEFAULT_SERVICE}, ensure_ascii=False
        )
    if config.INTRADESK_DEFAULT_TASK_TYPE:
        blocks["tasktype"] = json.dumps(
            {"value": int(config.INTRADESK_DEFAULT_TASK_TYPE)}, ensure_ascii=False
        )

    body = {"blocks": blocks, "Channel": "web"}
    url = f"{config.INTRADESK_BASE_URL.rstrip('/')}/changes/v3/tasks?ApiKey={key}"
    if contact_person_id is not None:
        url += f"&userid={contact_person_id}"

    if config.DRY_RUN:
        print(
            "\n[DRY-RUN] create_intradesk_task — запрос НЕ отправлен\n"
            f"  POST {url}\n"
            f"  Body: {json.dumps(body, ensure_ascii=False, indent=2)}\n"
        )
        return None

    try:
        async with httpx.AsyncClient() as client:
            resp = await client.post(
                url,
                json=body,
                headers={"Content-Type": "application/json"},
                timeout=20.0,
            )
        if resp.status_code not in (200, 201):
            logger.error(
                "create_intradesk_task → HTTP %s: %s",
                resp.status_code,
                resp.text[:500],
            )
            return None
        result = resp.json()
        # API может вернуть как "Number"/"Id" (PascalCase), так и "number"/"id"
        task_id = result.get("id") or result.get("Id") or result.get("ID")
        task_number = (
            result.get("tasknumber")
            or result.get("number")
            or result.get("Number")
            or result.get("TaskNumber")
        )
        logger.info("create_intradesk_task ok: id=%s number=%s", task_id, task_number)
        return result
    except Exception as exc:
        logger.exception("create_intradesk_task error: %s", exc)
        return None
