# #region AssistantResolvers [C:2] [TYPE Module] [SEMANTICS assistant, resolver, lookup, environment, mapper]
# @BRIEF Environment, dashboard, provider, and task resolution utilities for the assistant API.
# @LAYER API
# @RELATION DEPENDS_ON -> [ConfigManager]
# @RELATION DEPENDS_ON -> [SupersetClient]
# @INVARIANT  Resolution functions never raise; they return None on failure.

from __future__ import annotations

import re
from typing import Any, cast

from sqlalchemy.orm import Session

from src.core.config_manager import ConfigManager
from src.core.logger import logger
from src.core.superset_client import SupersetClient
from src.services.llm_prompt_templates import resolve_bound_provider_id
from src.services.llm_provider import LLMProviderService

logger = cast(Any, logger)


# #region _extract_id [C:2] [TYPE Function]
# @BRIEF Extract first regex match group from text by ordered pattern list.
# @PRE  patterns contain at least one capture group.
# @POST  Returns first matched token or None.
def _extract_id(text: str, patterns: list[str]) -> str | None:
    for p in patterns:
        m = re.search(p, text, flags=re.IGNORECASE)
        if m:
            return m.group(1)
    return None


# #endregion _extract_id

# #region _resolve_env_id [C:2] [TYPE Function]
# @BRIEF Resolve environment identifier/name token to canonical environment id.
# @PRE  config_manager provides environment list.
# @POST  Returns matched environment id or None.
def _resolve_env_id(
    token: str | None, config_manager: ConfigManager
) -> str | None:
    if not token:
        return None

    normalized = token.strip().lower()
    envs = config_manager.get_environments()
    for env in envs:
        if env.id.lower() == normalized or env.name.lower() == normalized:
            return env.id
    return None


# #endregion _resolve_env_id

# #region _is_production_env [C:2] [TYPE Function]
# @BRIEF Determine whether environment token resolves to production-like target.
# @PRE  config_manager provides environments or token text is provided.
# @POST  Returns True for production/prod synonyms, else False.
def _is_production_env(token: str | None, config_manager: ConfigManager) -> bool:
    env_id = _resolve_env_id(token, config_manager)
    if not env_id:
        return (token or "").strip().lower() in {"prod", "production", "прод"}

    env = next((e for e in config_manager.get_environments() if e.id == env_id), None)
    if not env:
        return False
    target = f"{env.id} {env.name}".lower()
    return "prod" in target or "production" in target or "прод" in target


# #endregion _is_production_env

# #region _resolve_provider_id [C:2] [TYPE Function]
# @BRIEF Resolve provider token to provider id with active/default fallback.
# @PRE  db session can load provider list through LLMProviderService.
# @POST  Returns provider id or None when no providers configured.
def _resolve_provider_id(
    provider_token: str | None,
    db: Session,
    config_manager: ConfigManager | None = None,
    task_key: str | None = None,
) -> str | None:
    service = LLMProviderService(db)
    providers = service.get_all_providers()
    if not providers:
        return None

    if provider_token:
        needle = provider_token.strip().lower()
        for p in providers:
            if p.id.lower() == needle or p.name.lower() == needle:
                return p.id

    if config_manager and task_key:
        try:
            llm_settings = config_manager.get_config().settings.llm
            bound_provider_id = resolve_bound_provider_id(llm_settings, task_key)
            if bound_provider_id and any(p.id == bound_provider_id for p in providers):
                return bound_provider_id
        except Exception:
            logger.debug("Could not resolve bound provider for task_key=%s", task_key)

    active = next((p for p in providers if p.is_active), None)
    return active.id if active else providers[0].id


# #endregion _resolve_provider_id

# #region _get_default_environment_id [C:2] [TYPE Function]
# @BRIEF Resolve default environment id from settings or first configured environment.
# @PRE  config_manager returns environments list.
# @POST  Returns default environment id or None when environment list is empty.
def _get_default_environment_id(config_manager: ConfigManager) -> str | None:
    configured = config_manager.get_environments()
    if not configured:
        return None
    preferred = None
    if hasattr(config_manager, "get_config"):
        try:
            preferred = config_manager.get_config().settings.default_environment_id
        except Exception:
            preferred = None
    if preferred and any(env.id == preferred for env in configured):
        return preferred
    explicit_default = next(
        (env.id for env in configured if getattr(env, "is_default", False)), None
    )
    return explicit_default or configured[0].id


# #endregion _get_default_environment_id

# #region _resolve_dashboard_id_by_ref [C:2] [TYPE Function]
# @BRIEF Resolve dashboard id by title or slug reference in selected environment.
# @PRE  dashboard_ref is a non-empty string-like token.
# @POST  Returns dashboard id when uniquely matched, otherwise None.
def _resolve_dashboard_id_by_ref(
    dashboard_ref: str | None,
    env_id: str | None,
    config_manager: ConfigManager,
) -> int | None:
    if not dashboard_ref or not env_id:
        return None
    env = next(
        (item for item in config_manager.get_environments() if item.id == env_id), None
    )
    if not env:
        return None

    needle = dashboard_ref.strip().lower()
    try:
        client = SupersetClient(env)
        _, dashboards = client.get_dashboards(query={"page_size": 200})
    except Exception as exc:
        logger.warning(
            f"[assistant.dashboard_resolve][failed] ref={dashboard_ref} env={env_id} error={exc}"
        )
        return None

    exact = next(
        (
            d
            for d in dashboards
            if str(d.get("slug", "")).lower() == needle
            or str(d.get("dashboard_title", "")).lower() == needle
            or str(d.get("title", "")).lower() == needle
        ),
        None,
    )
    if exact:
        return int(exact.get("id"))

    partial = [
        d
        for d in dashboards
        if needle in str(d.get("dashboard_title", d.get("title", ""))).lower()
    ]
    if len(partial) == 1 and partial[0].get("id") is not None:
        return int(partial[0]["id"])
    return None


# #endregion _resolve_dashboard_id_by_ref

# #region _resolve_dashboard_id_entity [C:2] [TYPE Function]
# @BRIEF Resolve dashboard id from intent entities using numeric id or dashboard_ref fallback.
# @PRE  entities may contain dashboard_id as int/str and optional dashboard_ref.
# @POST  Returns resolved dashboard id or None when ambiguous/unresolvable.
def _resolve_dashboard_id_entity(
    entities: dict[str, Any],
    config_manager: ConfigManager,
    env_hint: str | None = None,
) -> int | None:
    raw_dashboard_id = entities.get("dashboard_id")
    dashboard_ref = entities.get("dashboard_ref")

    if isinstance(raw_dashboard_id, int):
        return raw_dashboard_id

    if isinstance(raw_dashboard_id, str):
        token = raw_dashboard_id.strip()
        if token.isdigit():
            return int(token)
        if token and not dashboard_ref:
            dashboard_ref = token

    if not dashboard_ref:
        return None

    env_token = (
        env_hint
        or entities.get("environment")
        or entities.get("source_env")
        or entities.get("target_env")
    )
    env_id = (
        _resolve_env_id(env_token, config_manager)
        if env_token
        else _get_default_environment_id(config_manager)
    )
    return _resolve_dashboard_id_by_ref(str(dashboard_ref), env_id, config_manager)


# #endregion _resolve_dashboard_id_entity

# #region _get_environment_name_by_id [C:2] [TYPE Function]
# @BRIEF Resolve human-readable environment name by id.
# @PRE  environment id may be None.
# @POST  Returns matching environment name or fallback id.
def _get_environment_name_by_id(
    env_id: str | None, config_manager: ConfigManager
) -> str:
    if not env_id:
        return "unknown"
    env = next(
        (item for item in config_manager.get_environments() if item.id == env_id), None
    )
    return env.name if env else env_id


# #endregion _get_environment_name_by_id

# #region _extract_result_deep_links [C:2] [TYPE Function]
# @BRIEF Build deep-link actions to verify task result from assistant chat.
# @PRE  task object is available.
# @POST  Returns zero or more assistant actions for dashboard open/diff.
def _extract_result_deep_links(
    task: Any, config_manager: ConfigManager
) -> list:
    from ._schemas import AssistantAction

    plugin_id = getattr(task, "plugin_id", None)
    params = getattr(task, "params", {}) or {}
    result = getattr(task, "result", {}) or {}
    actions: list = []
    dashboard_id: int | None = None
    env_id: str | None = None

    if plugin_id == "superset-migration":
        migrated = (
            result.get("migrated_dashboards") if isinstance(result, dict) else None
        )
        if isinstance(migrated, list) and migrated:
            first = migrated[0]
            if isinstance(first, dict) and first.get("id") is not None:
                dashboard_id = int(first.get("id"))
        if (
            dashboard_id is None
            and isinstance(params.get("selected_ids"), list)
            and params["selected_ids"]
        ):
            dashboard_id = int(params["selected_ids"][0])
        env_id = params.get("target_env_id")
    elif plugin_id == "superset-backup":
        dashboards = result.get("dashboards") if isinstance(result, dict) else None
        if isinstance(dashboards, list) and dashboards:
            first = dashboards[0]
            if isinstance(first, dict) and first.get("id") is not None:
                dashboard_id = int(first.get("id"))
        if (
            dashboard_id is None
            and isinstance(params.get("dashboard_ids"), list)
            and params["dashboard_ids"]
        ):
            dashboard_id = int(params["dashboard_ids"][0])
        env_id = params.get("environment_id") or _resolve_env_id(
            result.get("environment"), config_manager
        )
    elif plugin_id == "llm_dashboard_validation":
        if params.get("dashboard_id") is not None:
            dashboard_id = int(params["dashboard_id"])
        env_id = params.get("environment_id")

    if dashboard_id is not None and env_id:
        env_name = _get_environment_name_by_id(env_id, config_manager)
        actions.append(
            AssistantAction(
                type="open_route",
                label=f"Открыть дашборд в {env_name}",
                target=f"/dashboards/{dashboard_id}?env_id={env_id}",
            )
        )
    if dashboard_id is not None:
        actions.append(
            AssistantAction(
                type="open_diff",
                label="Показать Diff",
                target=str(dashboard_id),
            )
        )
    return actions


# #endregion _extract_result_deep_links

# #region _build_task_observability_summary [C:2] [TYPE Function]
# @BRIEF Build compact textual summary for completed tasks to reduce "black box" effect.
# @PRE  task may contain plugin-specific result payload.
# @POST  Returns non-empty summary line for known task types or empty string fallback.
def _build_task_observability_summary(task: Any, config_manager: ConfigManager) -> str:
    plugin_id = getattr(task, "plugin_id", None)
    status = str(getattr(task, "status", "")).upper()
    params = getattr(task, "params", {}) or {}
    result = getattr(task, "result", {}) or {}

    if plugin_id == "superset-migration" and isinstance(result, dict):
        migrated = len(result.get("migrated_dashboards") or [])
        failed_rows = result.get("failed_dashboards") or []
        failed = len(failed_rows)
        selected = result.get("selected_dashboards", migrated + failed)
        mappings = result.get("mapping_count", 0)
        target_env_id = params.get("target_env_id")
        target_env_name = _get_environment_name_by_id(target_env_id, config_manager)
        warning = ""
        if failed_rows:
            first = failed_rows[0]
            warning = (
                f" Внимание: {first.get('title') or first.get('id')}: "
                f"{first.get('error') or 'ошибка'}."
            )
        return (
            f"Сводка миграции: выбрано {selected}, перенесено {migrated}, "
            f"с ошибками {failed}, маппингов {mappings}, целевая среда {target_env_name}."
            f"{warning}"
        )

    if plugin_id == "superset-backup" and isinstance(result, dict):
        total = int(result.get("total_dashboards", 0) or 0)
        ok = int(result.get("backed_up_dashboards", 0) or 0)
        failed = int(result.get("failed_dashboards", 0) or 0)
        env_id = params.get("environment_id") or _resolve_env_id(
            result.get("environment"), config_manager
        )
        env_name = _get_environment_name_by_id(env_id, config_manager)
        failures = result.get("failures") or []
        warning = ""
        if failures:
            first = failures[0]
            warning = (
                f" Внимание: {first.get('title') or first.get('id')}: "
                f"{first.get('error') or 'ошибка'}."
            )
        return (
            f"Сводка бэкапа: среда {env_name}, всего {total}, успешно {ok}, "
            f"с ошибками {failed}. {status}.{warning}"
        )

    if plugin_id == "llm_dashboard_validation" and isinstance(result, dict):
        report_status = result.get("status") or status
        report_summary = result.get("summary") or "Итог недоступен."
        issues = result.get("issues") or []
        return f"Сводка валидации: статус {report_status}, проблем {len(issues)}. {report_summary}"

    # Fallback for unknown task payloads.
    if status in {"SUCCESS", "FAILED"}:
        return f"Задача завершена со статусом {status}."
    return ""


# #endregion _build_task_observability_summary

# #endregion AssistantResolvers
