"""ES 连接封装."""
from __future__ import annotations

from elasticsearch import Elasticsearch
from elasticsearch.exceptions import NotFoundError

from .models import (
    BulkResult,
    ConnectionConfig,
    EsVersionInfo,
    SafetyConfig,
    ScrollResult,
    SearchResult,
)


def create_client(config: ConnectionConfig, es_version: int = 7) -> Elasticsearch:
    """创建 ES 客户端."""
    kwargs = {
        "hosts": [config.url],
        "verify_certs": False,
    }

    if config.username and config.password:
        # ES 8 使用 basic_auth，ES 6/7 使用 http_auth
        if es_version >= 8:
            kwargs["basic_auth"] = (config.username, config.password)
        else:
            kwargs["http_auth"] = (config.username, config.password)

    return Elasticsearch(**kwargs)


def get_es_version(client: Elasticsearch) -> EsVersionInfo:
    """获取 ES 版本信息."""
    info = client.info()
    version_str = info["version"]["number"]
    parts = version_str.split(".")
    return EsVersionInfo(
        major=int(parts[0]),
        minor=int(parts[1]),
        patch=int(parts[2].split("-")[0]) if "-" in parts[2] else int(parts[2]),
    )


def es_search(
    client: Elasticsearch,
    es_version: int,
    index: str,
    query: dict | None = None,
    size: int = 10,
    from_: int = 0,
) -> SearchResult:
    """搜索文档 (from/size 分页)."""
    body = query or {"query": {"match_all": {}}}

    # ES 8+ 不支持 _type
    if es_version >= 8:
        body = {k: v for k, v in body.items() if k != "_type"}

    # 移除 _type 字段
    if "_type" in body:
        del body["_type"]

    # ES 6 需要 doc_type 参数
    kwargs = {"body": body, "size": size, "from_": from_}
    if es_version < 7:
        kwargs["doc_type"] = "_doc"

    resp = client.search(index=index, **kwargs)

    hits = [hit["_source"] for hit in resp["hits"]["hits"]]
    # ES 6 返回 total 是数字，ES 7+ 返回 {"value": n}
    total = resp["hits"]["total"]
    if isinstance(total, dict):
        total = total["value"]

    return SearchResult(
        hits=hits,
        total=total,
        truncated=total > size,
    )


def es_scroll(
    client: Elasticsearch,
    es_version: int,
    index: str,
    query: dict | None = None,
    size: int = 100,
    scroll: str = "5m",
) -> ScrollResult:
    """滚动查询 (首次调用)."""
    body = query or {"query": {"match_all": {}}}

    # ES 8+ 移除 _type
    if es_version >= 8:
        body = {k: v for k, v in body.items() if k != "_type"}

    # ES 6 需要 doc_type 参数
    kwargs = {"body": body, "size": size, "scroll": scroll}
    if es_version < 7:
        kwargs["doc_type"] = "_doc"

    resp = client.search(index=index, **kwargs)

    hits = [hit["_source"] for hit in resp["hits"]["hits"]]
    scroll_id = resp.get("_scroll_id")

    return ScrollResult(
        hits=hits,
        scroll_id=scroll_id,
        took_ms=resp.get("took", 0),
    )


def es_scroll_next(
    client: Elasticsearch,
    scroll_id: str,
    scroll: str = "5m",
) -> ScrollResult:
    """滚动查询下一页."""
    resp = client.scroll(scroll_id=scroll_id, scroll=scroll)

    hits = [hit["_source"] for hit in resp["hits"]["hits"]]
    new_scroll_id = resp.get("_scroll_id")

    return ScrollResult(
        hits=hits,
        scroll_id=new_scroll_id,
        took_ms=resp.get("took", 0),
    )


def es_scroll_clear(client: Elasticsearch, scroll_id: str) -> None:
    """清除滚动上下文."""
    client.clear_scroll(scroll_id=scroll_id)


def es_index_doc(
    client: Elasticsearch,
    es_version: int,
    index: str,
    doc: dict,
    id_: str | None = None,
) -> str:
    """索引文档."""
    kwargs = {"body": doc}

    if es_version >= 8:
        kwargs["index"] = index
        if id_:
            result = client.index(index=index, id=id_, body=doc)
        else:
            result = client.index(index=index, body=doc)
    else:
        kwargs["doc_type"] = "_doc"
        if id_:
            result = client.index(index=index, doc_type="_doc", id=id_, body=doc)
        else:
            result = client.index(index=index, doc_type="_doc", body=doc)

    return result["_id"]


def es_update_doc(
    client: Elasticsearch,
    es_version: int,
    index: str,
    id_: str,
    doc: dict,
) -> None:
    """更新文档."""
    if es_version >= 8:
        client.update(index=index, id=id_, body={"doc": doc})
    else:
        client.update(index=index, doc_type="_doc", id=id_, body={"doc": doc})


def es_delete_doc(
    client: Elasticsearch,
    es_version: int,
    index: str,
    id_: str,
) -> None:
    """删除文档."""
    if es_version >= 8:
        client.delete(index=index, id=id_)
    else:
        client.delete(index=index, doc_type="_doc", id=id_)


def es_bulk(
    client: Elasticsearch,
    es_version: int,
    operations: list[dict],
    refresh: bool = False,
) -> BulkResult:
    """批量操作."""
    if es_version >= 8:
        resp = client.bulk(body=operations, refresh=refresh)
    else:
        resp = client.bulk(body=operations, refresh=refresh)

    succeeded = 0
    failed = 0
    errors = []

    for item in resp.get("items", []):
        action = item.get("index") or item.get("update") or item.get("delete") or item.get("create")
        if action.get("error"):
            failed += 1
            errors.append({
                "action": list(item.keys())[0],
                "id": action.get("_id"),
                "error": action["error"].get("reason", "Unknown error"),
            })
        else:
            succeeded += 1

    return BulkResult(succeeded=succeeded, failed=failed, errors=errors)


def es_create_index(
    client: Elasticsearch,
    es_version: int,
    index: str,
    mappings: dict | None = None,
    settings: dict | None = None,
) -> bool:
    """创建索引."""
    body = {}
    if mappings:
        body["mappings"] = mappings
    if settings:
        body["settings"] = settings

    if es_version >= 8:
        return client.indices.create(index=index, body=body if body else None)
    else:
        return client.indices.create(index=index, doc_type="_doc", body=body if body else None)


def es_delete_index(client: Elasticsearch, index: str) -> bool:
    """删除索引."""
    try:
        client.indices.delete(index=index)
        return True
    except NotFoundError:
        return False


def es_index_exists(client: Elasticsearch, index: str) -> bool:
    """检查索引是否存在."""
    return client.indices.exists(index=index)


def es_get_index_mapping(
    client: Elasticsearch,
    es_version: int,
    index: str,
) -> dict:
    """获取索引 mapping."""
    if es_version >= 8:
        return client.indices.get_mapping(index=index)
    else:
        return client.indices.get_mapping(index=index, doc_type="_doc")


def es_list_indices(client: Elasticsearch, es_version: int = 7) -> list[dict]:
    """列出所有索引."""
    resp = client.cat.indices(format="json")
    indices = []
    for idx in resp:
        # ES 6 字段名可能不同，尝试多种可能的字段名
        name = idx.get("index") or idx.get("uuid")  # ES 6 可能用 uuid
        if not name:
            continue

        # 处理 docs.count 字段名差异
        docs_count = idx.get("docs.count") or idx.get("docs.count") or idx.get("documents.count") or 0
        docs_count = int(docs_count) if docs_count else 0

        # 处理 store.size 字段名差异
        size = idx.get("store.size") or idx.get("store.size") or idx.get("disk.usage") or "0b"

        indices.append({
            "name": name,
            "docs_count": docs_count,
            "size": size,
            "health": idx.get("health", "unknown"),
        })
    return indices
