"""Точка входа MCP-сервера с HTTP-транспортом (streamable-http).

VMK Data MCP Server предоставляет AI-агентам безопасный read-only доступ
к базе данных недвижимости vmk_data (PostgreSQL + pgvector).

Данные: ~35 полей объявлений (цена, площадь, район, метро, тип сделки, ...).
Поиск: семантический (vector 768d через Ollama) + полнотекстовый (FTS).
Язык: украинский (запросы переводятся агентом перед вызовом).
Зачем: AI-агент может искать квартиры/дома по описанию пользователя,
фильтровать по бюджету и району, показывать детали объявлений.
"""

import json
import logging
from contextlib import asynccontextmanager

import asyncpg
import httpx
from mcp.server import FastMCP

from vmk_data_mcp.config import settings
from vmk_data_mcp.db import close_pool, init_pool
from vmk_data_mcp.embedder import close_client
from vmk_data_mcp.models import (
    GetListingInput,
    MetadataFilters,
    PaginationParams,
    SearchMetadataInput,
    SearchSimilarInput,
)
from vmk_data_mcp.tools import (
    describe_schema,
    get_listing_by_id,
    search_by_metadata,
    search_similar_listings,
)

logging.basicConfig(level=logging.INFO)
logger = logging.getLogger(__name__)


def _error_json(message: str) -> str:
    """Возвращает стандартизированный JSON с ошибкой."""
    return json.dumps({"error": message}, indent=2, ensure_ascii=False)


@asynccontextmanager
async def app_lifespan(server):
    """Жизненный цикл приложения — инициализация и закрытие ресурсов."""
    logger.info("Initializing DB pool ...")
    await init_pool()
    logger.info("DB pool ready.")
    yield
    logger.info("Shutting down ...")
    await close_pool()
    await close_client()
    logger.info("Shutdown complete.")


SERVER_INSTRUCTIONS = """\
# VMK Data MCP Server — инструкция для AI-агента

## Назначение
Этот сервер предоставляет инструменты для поиска объявлений о недвижимости
(квартиры, дома, аренда, продажа) из базы данных `vmk_data`.

## Язык
Все текстовые запросы (`query`) должны быть на **украинском языке**.
AI-агент переводит запрос пользователя перед вызовом инструмента.

## Два инструмента поиска — выбирай правильно

### search_similar_listings — векторный (семантический)
Используй, когда пользователь описывает **желания, атмосферу, качества**:
- "уютная квартира с ремонтом у метро"
- "светлая студия в центрі міста"
- "простора 3-кімнатна з балконом"

Запрос на украинском. Конкретно, без стоп-слов.
✓ Хорошо: "2-кімнатна квартира біля метро з ремонтом"
✗ Плохо:  "дай квартиру недорого"

### search_by_metadata — полнотекстовый (FTS)
Используй, когда пользователь называет **конкретные ключевые слова**:
- "Печерський район Арсенальна метро"
- "вул. Шевченка Львів оренда"
- "новобудова моноліт Солом'янський"

Запрос на украинском. Имена собственные и термины.
✓ Хорошо: "Печерський район Арсенальна метро"
✗ Плохо:  "хочу квартиру" (слишком общее — используй vector)

## Фильтры (общие для обоих инструментов)
- Обязательно указывай `currency` при `min_price`/`max_price`.
- `city`, `district`, `metro_station` — поиск по подстроке (ILIKE), регистр не важен.
- `rooms_count`: 0 = студия.
- Если результатов мало — убери 1–2 фильтра (обычно district или metro_station).

## Сортировка (только search_by_metadata)
- `relevance` — по релевантности FTS (по умолчанию)
- `price_asc`/`price_desc` — по цене
- `date_desc` — новые сверху
- `area_desc` — по площади

## Fallback при 0 результатов
1. Попробуй другой инструмент (vector → FTS или FTS → vector).
2. Убери 1–2 жёстких фильтра.
3. Упрости query — убери разговорные слова.
4. Проверь, что query на украинском.

## Пагинация
- `total` > `limit` → есть следующая страница.
- Следующая: `offset += limit`. Предыдущая: `offset -= limit` (≥ 0).
- `limit` от 1 до 100.

## describe_schema
Возвращает полное описание таблицы, гайды, примеры запросов и лучшие практики.
Вызывай, если не уверен какой инструмент выбрать или какие фильтры применить.
"""


mcp = FastMCP(
    settings.mcp_server_name,
    host="0.0.0.0",
    port=settings.mcp_port,
    lifespan=app_lifespan,
    instructions=SERVER_INSTRUCTIONS,
)

# ── Prompts (гайды для AI-агента) ───────────────────────────────────


@mcp.prompt()
def search_guide() -> str:
    """📘 Гайд: как эффективно искать объявления через VMK Data MCP."""
    return SERVER_INSTRUCTIONS


# ── Регистрация инструментов ────────────────────────────────────────


@mcp.tool()
async def search_similar_listings_tool(
    query: str,
    filters: MetadataFilters | None = None,
    pagination: PaginationParams | None = None,
) -> str:
    """🔍 Векторный (семантический) поиск объявлений по смыслу.

    Используй, когда пользователь описывает **желания, атмосферу, качества**
    («уютная квартира с ремонтом», «светлая студия у метро»).
    Не используй для точных ключевых слов (район, метро) — для этого
    используй `search_by_metadata`.

    **Важно:** запрос `query` должен быть на **украинском** языке.
    Формулируй конкретно, без разговорных стоп-слов.

    **Фильтры:** объект `MetadataFilters` — city, district, deal_type,
    rooms_count, min/max_price (с обязательной currency), building_type и др.
    `city`/`district`/`metro_station` — поиск по подстроке, регистр не важен.
    `rooms_count`: 0 = студия.

    **Пагинация:** объект `PaginationParams` с `limit` (1–100, умолч. 20) и
    `offset` (≥ 0). Следующая страница: `offset += limit`.

    **Fallback при 0 результатов:** сервер вернёт подсказку с рекомендациями
    (попробовать FTS, убрать фильтры, упростить query).
    """
    try:
        args = SearchSimilarInput(
            query=query,
            filters=filters or MetadataFilters(),
            pagination=pagination or PaginationParams(),
        )
        return await search_similar_listings(args)
    except httpx.HTTPError as e:
        logger.warning("Ollama error: %s", e)
        return _error_json(f"Сервис эмбеддингов недоступен: {e}")
    except (asyncpg.PostgresError, ValueError) as e:
        logger.warning("DB/validation error: %s", e)
        return _error_json(f"Ошибка при поиске: {e}")
    except Exception as e:
        logger.exception("Unexpected error in search_similar_listings")
        return _error_json(f"Неожиданная ошибка: {e}")


@mcp.tool()
async def search_by_metadata_tool(
    query: str,
    filters: MetadataFilters | None = None,
    pagination: PaginationParams | None = None,
    sort_by: str = "relevance",
) -> str:
    """📋 Полнотекстовый поиск + фильтры по метаданным.

    Используй, когда пользователь называет **конкретные ключевые слова**
    («Печерський район», «Арсенальна метро», «вул. Шевченка»).
    Работает через готовую FTS-колонку `search_vector` (украинский конфиг) + GIN-индекс.

    **Важно:** запрос `query` должен быть на **украинском** языке.
    Используй имена собственные и термины, а не разговорные описания.

    **Фильтры:** объект `MetadataFilters` — те же поля, что и для vector-поиска.
    Обязательно указывай `currency` при `min_price`/`max_price`.

    **Сортировка:**
    - `relevance` — по релевантности FTS (по умолчанию)
    - `price_asc`/`price_desc` — по цене
    - `date_desc` — новые сверху
    - `area_desc` — по площади

    **Пагинация:** объект `PaginationParams`.

    **Fallback при 0 результатов:** сервер вернёт подсказку с рекомендациями
    (попробовать vector search, убрать фильтры).
    """
    try:
        args = SearchMetadataInput(
            query=query,
            filters=filters or MetadataFilters(),
            pagination=pagination or PaginationParams(),
            sort_by=sort_by,
        )
        return await search_by_metadata(args)
    except (asyncpg.PostgresError, ValueError) as e:
        logger.warning("DB/validation error: %s", e)
        return _error_json(f"Ошибка при поиске: {e}")
    except Exception as e:
        logger.exception("Unexpected error in search_by_metadata")
        return _error_json(f"Неожиданная ошибка: {e}")


@mcp.tool()
async def get_listing_by_id_tool(listing_id: int) -> str:
    """📄 Получить полную карточку объявления по ID.

    Возвращает все пользовательские поля объявления.
    Если объявление не найдено — вернёт ошибку с подсказкой
    (проверить ID, возможен статус archived/removed).
    """
    try:
        args = GetListingInput(listing_id=listing_id)
        return await get_listing_by_id(args)
    except (asyncpg.PostgresError, ValueError) as e:
        logger.warning("DB/validation error: %s", e)
        return _error_json(f"Ошибка при получении объявления: {e}")
    except Exception as e:
        logger.exception("Unexpected error in get_listing_by_id")
        return _error_json(f"Неожиданная ошибка: {e}")


@mcp.tool()
async def describe_schema_tool() -> str:
    """ℹ️ Описание схемы данных и гайд по поиску.

    Возвращает:
    - описание таблицы `property_listings` (поля, типы, enum-значения)
    - гайд когда использовать vector/FTS
    - примеры хороших и плохих запросов
    - лучшие комбинации фильтров
    - стратегию fallback и пагинации

    Вызывай, если не уверен какой инструмент выбрать или какие фильтры применить.
    """
    try:
        return await describe_schema()
    except Exception as e:
        logger.exception("Unexpected error in describe_schema")
        return _error_json(f"Неожиданная ошибка: {e}")


if __name__ == "__main__":
    mcp.run(transport="streamable-http")
