"""App-mode configuration routes."""

from __future__ import annotations

import logging
from pathlib import Path
from typing import Literal

from fastapi import APIRouter, Depends, HTTPException, Request, status
from fastapi.responses import HTMLResponse
from fastapi.templating import Jinja2Templates
from pydantic import BaseModel, Field, field_validator
from sqlalchemy.orm import Session

from app.core.config import settings
from app.core.db import get_db
from app.core.i18n import get_language_pack, normalize_language, translate
from app.services.ai import provider_registry as ai_provider_registry
from app.services import runtime_config
from app.services.integrations import service as integrations_service
from app.services.ui_events import event_hub

TEMPLATES_DIR = Path(__file__).resolve().parents[2] / "templates"
try:
    templates = Jinja2Templates(directory=str(TEMPLATES_DIR))
except Exception:  # pragma: no cover - dependency availability issue at runtime
    templates = None
else:
    templates.env.globals["app_name"] = settings.app_name

router = APIRouter(tags=["configuration"])
LOGGER = logging.getLogger("sentineledge.configuration")


def _translate_save_warning(message: str, language: str) -> str:
    """Translate known config save warnings into the selected UI language."""
    text = str(message or "").strip()
    if not text:
        return ""

    if text.startswith("Vault path not found:"):
        path = text.split(":", 1)[1].strip() if ":" in text else ""
        return translate("config.messages.warning_vault_path_not_found", language, path=path)

    warning_map = {
        "Todoist token seems short. It was saved, but verify it if requests fail.": "config.messages.warning_todoist_short",
        "Todoist token includes whitespace. It was saved exactly as provided.": "config.messages.warning_todoist_whitespace",
        "Gemini provider selected without API key. AI responses may be unavailable.": "config.messages.warning_gemini_key_missing",
    }
    key = warning_map.get(text)
    if key:
        return translate(key, language)
    return text


def _sanitize_providers_snapshot(snapshot: dict[str, dict[str, object]]) -> dict[str, dict[str, object]]:
    """Return provider snapshot without exposing credential values to the browser."""
    sanitized: dict[str, dict[str, object]] = {}
    for provider_id, payload in snapshot.items():
        if not isinstance(payload, dict):
            continue
        provider_payload = dict(payload)
        credentials = provider_payload.get("credentials")
        if isinstance(credentials, dict):
            provider_payload["credentials"] = {
                str(key): ""
                for key, value in credentials.items()
                if str(key).strip() and str(value or "").strip()
            }
        sanitized[provider_id] = provider_payload
    return sanitized


class ConfigSaveRequest(BaseModel):
    """Payload used to persist runtime app configuration."""

    language: Literal["es", "en"] | None = None
    assistant_default_language: Literal["es", "en"] | None = None
    todoist_api_token: str | None = None
    vault_paths: list[str] | None = Field(default=None)
    ai_provider: Literal["gemini", "ollama"] | None = None
    gemini_api_key: str | None = None
    gemini_model: str | None = None
    ollama_model: str | None = None
    allow_fallback: bool | None = None
    default_task_provider: str | None = None
    default_notes_provider: str | None = None

    @field_validator("todoist_api_token")
    @classmethod
    def _normalize_token(cls, value: str | None) -> str | None:
        if value is None:
            return None
        return value.strip() or None

    @field_validator("vault_paths")
    @classmethod
    def _normalize_paths(cls, values: list[str] | None) -> list[str] | None:
        if values is None:
            return None
        normalized: list[str] = []
        for value in values:
            text = str(value or "").strip()
            if text:
                normalized.append(text)
        return normalized

    @field_validator("gemini_api_key", "gemini_model", "ollama_model")
    @classmethod
    def _normalize_optional_strings(cls, value: str | None) -> str | None:
        if value is None:
            return None
        return value.strip() or None


class ConfigAITestRequest(BaseModel):
    """Payload for testing configured AI provider connectivity."""

    provider: Literal["gemini", "ollama"] | None = None
    gemini_api_key: str | None = None
    gemini_model: str | None = None
    ollama_model: str | None = None

    @field_validator("gemini_api_key", "gemini_model", "ollama_model")
    @classmethod
    def _normalize_optional_strings(cls, value: str | None) -> str | None:
        if value is None:
            return None
        return value.strip() or None


@router.get("/config", response_class=HTMLResponse)
def config_page(request: Request) -> HTMLResponse:
    """Render app-mode configuration screen."""
    embedded_dashboard = request.query_params.get("embedded") == "dashboard"
    from_assistant = request.query_params.get("from") == "assistant"
    LOGGER.info(
        "Config UI render embedded=%s from_assistant=%s current_mode=%s",
        embedded_dashboard,
        from_assistant,
        runtime_config.get_app_ui_mode(),
    )
    if not embedded_dashboard:
        runtime_config.set_app_ui_mode(runtime_config.UI_MODE_CONFIG)
        event_hub.broadcast({"type": "state", "state": "paused"})
    if templates is None:
        return HTMLResponse(
            status_code=status.HTTP_503_SERVICE_UNAVAILABLE,
            content=(
                "<html><head>"
                "<link rel='icon' type='image/png' href='/static/favicon.png'>"
                "<link rel='apple-touch-icon' href='/static/favicon.png'>"
                "</head><body><h1>Configuration UI dependencies missing</h1>"
                "<p>Install requirements to enable Jinja2 templates.</p>"
                "</body></html>"
            ),
        )

    state = runtime_config.get_config_state()
    config = runtime_config.get_runtime_config()
    ui_language = normalize_language(config.language if config else state.language)

    context = {
        "request": request,
        "title": translate("config.title", ui_language),
        "ui_language": ui_language,
        "t": lambda key, **kwargs: translate(key, ui_language, **kwargs),
        "i18n_strings": get_language_pack(ui_language, namespace="config"),
        "config_state": state.as_dict(),
        "language": config.language if config else ui_language,
        "todoist_token": "",
        "vault_paths": config.vault_paths if config else [],
        "ai_provider": config.ai_provider if config else runtime_config.get_ai_provider(),
        "gemini_api_key": "",
        "gemini_model": config.gemini_model if config else runtime_config.get_gemini_model(),
        "ollama_model": config.ollama_model if config else runtime_config.get_ollama_model(),
        "allow_fallback": bool(config.allow_fallback) if config else runtime_config.get_allow_fallback(False),
        "providers_snapshot": _sanitize_providers_snapshot(runtime_config.get_providers_snapshot()),
        "integrations_status": integrations_service.get_integrations_status().as_dict(),
        "from_assistant": from_assistant,
        "embedded_dashboard": embedded_dashboard,
    }
    return templates.TemplateResponse("config.html", context=context)


@router.post("/config/save")
def save_config(
    payload: ConfigSaveRequest,
    db: Session = Depends(get_db),
) -> dict[str, object]:
    """Persist app-mode settings and return current status."""
    current = runtime_config.get_runtime_config()
    provided = payload.model_fields_set

    todoist_api_token = (
        payload.todoist_api_token
        if "todoist_api_token" in provided
        else (current.todoist_api_token if current else None)
    )
    vault_paths = (
        payload.vault_paths
        if "vault_paths" in provided
        else (list(current.vault_paths) if current else [])
    )
    assistant_default_language = (
        payload.language
        if "language" in provided
        else payload.assistant_default_language
        if "assistant_default_language" in provided
        else (current.assistant_default_language if current else "es")
    )
    ai_provider = (
        payload.ai_provider
        if "ai_provider" in provided
        else (current.ai_provider if current else runtime_config.get_ai_provider())
    )
    gemini_api_key = (
        payload.gemini_api_key
        if "gemini_api_key" in provided
        else (current.gemini_api_key if current else runtime_config.get_gemini_api_key(None))
    )
    gemini_model = (
        payload.gemini_model
        if "gemini_model" in provided
        else (current.gemini_model if current else runtime_config.get_gemini_model())
    )
    ollama_model = (
        payload.ollama_model
        if "ollama_model" in provided
        else (current.ollama_model if current else runtime_config.get_ollama_model())
    )
    allow_fallback = (
        payload.allow_fallback
        if "allow_fallback" in provided
        else (bool(current.allow_fallback) if current else runtime_config.get_allow_fallback(False))
    )
    language_configured = (
        True
        if "assistant_default_language" in provided or "language" in provided
        else bool(current.language_configured) if current else False
    )
    default_task_provider = (
        payload.default_task_provider
        if "default_task_provider" in provided
        else (current.default_task_provider if current else "todoist")
    )
    default_notes_provider = (
        payload.default_notes_provider
        if "default_notes_provider" in provided
        else (current.default_notes_provider if current else "vault_local")
    )

    providers = runtime_config.get_providers_snapshot()
    if "todoist_api_token" in provided:
        todoist_provider = dict(providers.get("todoist", {}))
        todoist_credentials = dict(todoist_provider.get("credentials", {}))
        if todoist_api_token:
            todoist_credentials["token"] = todoist_api_token
            todoist_provider["enabled"] = True
        else:
            todoist_credentials.pop("token", None)
            todoist_provider["enabled"] = False
        todoist_provider["credentials"] = todoist_credentials
        todoist_provider["last_error"] = None
        providers["todoist"] = todoist_provider

    if "vault_paths" in provided:
        vault_provider = dict(providers.get("vault_local", {}))
        vault_settings = dict(vault_provider.get("settings", {}))
        vault_settings["paths"] = list(vault_paths or [])
        vault_provider["settings"] = vault_settings
        vault_provider["enabled"] = bool(vault_paths)
        vault_provider["last_error"] = None
        providers["vault_local"] = vault_provider

    result = runtime_config.save_config(
        todoist_api_token=todoist_api_token,
        vault_paths=vault_paths or [],
        assistant_default_language=assistant_default_language or "es",
        language_configured=language_configured,
        ai_provider=ai_provider,
        gemini_api_key=gemini_api_key,
        gemini_model=gemini_model,
        ollama_model=ollama_model,
        allow_fallback=allow_fallback,
        providers=providers,
        default_task_provider=default_task_provider,
        default_notes_provider=default_notes_provider,
        db=db,
    )
    resolved_ai = ai_provider_registry.resolve_ai_config()
    LOGGER.debug(
        "Config save applied provider=%s provider_source=%s model=%s model_source=%s api_key_present=%s key_source=%s",
        resolved_ai.provider,
        resolved_ai.provider_source,
        resolved_ai.model,
        resolved_ai.model_source,
        resolved_ai.api_key_present,
        resolved_ai.api_key_source,
    )
    db.commit()
    state = runtime_config.get_config_state()
    ui_language = normalize_language(assistant_default_language or "es")
    warnings = [
        translated
        for translated in (_translate_save_warning(item, ui_language) for item in result.warnings)
        if translated
    ]
    return {
        "ok": True,
        "warnings": warnings,
        "missing_paths": result.missing_paths,
        "vault_paths": list(result.config.vault_paths),
        "state": state.as_dict(),
    }


@router.post("/config/ai/test")
def test_ai_provider(payload: ConfigAITestRequest) -> dict[str, object]:
    """Run health test for selected AI provider in config screen."""
    resolved_before = ai_provider_registry.resolve_ai_config()
    LOGGER.debug(
        "Config AI test requested provider=%s resolved_provider=%s provider_source=%s resolved_model=%s resolved_model_source=%s api_key_present=%s key_source=%s",
        payload.provider,
        resolved_before.provider,
        resolved_before.provider_source,
        resolved_before.model,
        resolved_before.model_source,
        resolved_before.api_key_present,
        resolved_before.api_key_source,
    )
    try:
        result = ai_provider_registry.test_provider(
            provider_id=payload.provider,
            gemini_api_key=payload.gemini_api_key,
            gemini_model=payload.gemini_model,
            ollama_model=payload.ollama_model,
        )
    except Exception as exc:  # pragma: no cover - runtime defensive branch
        raise HTTPException(
            status_code=status.HTTP_400_BAD_REQUEST,
            detail=str(exc) or "AI provider test failed",
        ) from exc
    language = runtime_config.get_default_language("es")
    provider_key = "config.providers.gemini" if result.get("provider") == "gemini" else "config.providers.ollama"
    provider_label = translate(provider_key, language)
    if result.get("ok"):
        detail = str(result.get("detail") or translate("config.messages.connection_ok", language, provider=provider_label))
    else:
        failure_detail = str(result.get("detail") or "").strip()
        detail = failure_detail or translate("config.messages.connection_failed", language, provider=provider_label)
    return {
        **result,
        "detail": detail,
    }


@router.post("/config/skip")
def skip_config() -> dict[str, object]:
    """Mark configuration step as skipped for this app run."""
    state = runtime_config.mark_skipped_for_run()
    return {
        "ok": True,
        "state": state.as_dict(),
    }


@router.get("/config/state")
def config_state() -> dict[str, object]:
    """Return current config status for UI checks."""
    return runtime_config.get_config_state().as_dict()
