from __future__ import annotations

from typing import TYPE_CHECKING

from fastapi import APIRouter, HTTPException
from pydantic import BaseModel

from niannian.llm.config_store import (
    LlmConfigError,
    add_custom_llm_provider,
    add_model_for_provider,
    apply_brain_patch,
    bootstrap_llm_config_from_files,
    delete_model_for_provider,
    extract_brain_api,
    init_default_providers,
    list_provider_views,
    persist_brain_api_patch,
    remove_custom_llm_provider,
    sync_all_from_file,
    sync_keys_from_file,
    update_provider,
)
from niannian.llm.model_registry import all_provider_models_view, init_free_models, resolve_llm_models_path
from niannian.llm.provider_catalog import all_provider_options, brain_modules, list_catalog

if TYPE_CHECKING:
    from niannian.client import NianNian


class ProviderPatch(BaseModel):
    api_key: str | None = None
    base_url: str | None = None


class BrainSlotsPatch(BaseModel):
    brains: dict[str, dict[str, str | None]] | None = None
    global_fallback: str | None = None


class ProviderModelCreate(BaseModel):
    model_id: str


class CustomProviderCreate(BaseModel):
    provider_id: str
    label: str = ""
    base_url: str
    api_key: str = ""


def create_llm_config_router(nn: "NianNian") -> APIRouter:
    router = APIRouter(prefix="/v1/llm", tags=["llm"])

    def _reload() -> None:
        nn.reload_llm_config()

    def _err(exc: LlmConfigError) -> HTTPException:
        return HTTPException(status_code=400, detail={"code": "llm_config", "message": str(exc)})

    def _payload() -> dict:
        providers, path = list_provider_views()
        api_cfg = (nn._config.get("api") or {}) if isinstance(nn._config.get("api"), dict) else {}
        from niannian.llm.custom_providers import load_providers_doc

        return {
            "catalog": list_catalog(load_providers_doc()),
            "provider_options": all_provider_options(),
            "brain_modules": brain_modules(),
            "providers": providers,
            "provider_models": all_provider_models_view(),
            "providers_path": path,
            "models_path": str(resolve_llm_models_path(for_write=True)),
            "models": extract_brain_api(api_cfg),
        }

    @router.get("")
    def get_llm_config():
        bootstrap_llm_config_from_files()
        _reload()
        return _payload()

    @router.post("/sync-keys")
    def post_sync_keys():
        sync_keys_from_file()
        _reload()
        return {"providers": list_provider_views()[0]}

    @router.post("/sync")
    def post_sync_all():
        result = sync_all_from_file()
        _reload()
        return {**result, "config": _payload()}

    @router.post("/providers/init")
    def post_init_providers():
        path = init_default_providers()
        init_free_models("siliconflow", "zhipu")
        _reload()
        providers, _ = list_provider_views()
        return {"providers_path": path, "providers": providers}

    @router.patch("/providers/{provider_id}")
    def patch_provider(provider_id: str, payload: ProviderPatch):
        fields = payload.model_dump(exclude_unset=True)
        if not fields:
            raise HTTPException(status_code=400, detail={"code": "empty_patch", "message": "no fields"})
        try:
            view = update_provider(provider_id, **fields)
        except LlmConfigError as exc:
            raise _err(exc) from exc
        _reload()
        return {"provider": view}

    @router.post("/providers/custom")
    def post_custom_provider(payload: CustomProviderCreate):
        try:
            view = add_custom_llm_provider(
                provider_id=payload.provider_id,
                label=payload.label,
                base_url=payload.base_url,
                api_key=payload.api_key,
            )
        except LlmConfigError as exc:
            raise _err(exc) from exc
        _reload()
        return {"provider": view}

    @router.delete("/providers/custom/{provider_id}")
    def delete_custom_provider_route(provider_id: str):
        try:
            remove_custom_llm_provider(provider_id)
        except LlmConfigError as exc:
            raise _err(exc) from exc
        _reload()
        return {"ok": True, "provider_id": provider_id}

    @router.post("/providers/{provider_id}/models")
    def post_provider_model(provider_id: str, payload: ProviderModelCreate):
        try:
            entries = add_model_for_provider(provider_id, payload.model_id)
        except LlmConfigError as exc:
            raise _err(exc) from exc
        return {"provider_id": provider_id, "models": entries}

    @router.delete("/providers/{provider_id}/models/{model_id:path}")
    def delete_provider_model_route(provider_id: str, model_id: str):
        try:
            entries = delete_model_for_provider(provider_id, model_id)
        except LlmConfigError as exc:
            raise _err(exc) from exc
        return {"provider_id": provider_id, "models": entries}

    @router.patch("/models")
    def patch_models(payload: BrainSlotsPatch):
        patch = payload.model_dump(exclude_unset=True)
        if not patch:
            raise HTTPException(status_code=400, detail={"code": "empty_patch", "message": "no fields"})
        api_cfg = nn._config.setdefault("api", {})
        if not isinstance(api_cfg, dict):
            api_cfg = {}
            nn._config["api"] = api_cfg
        nn._config["api"] = apply_brain_patch(api_cfg, patch)
        persist_brain_api_patch(patch)
        _reload()
        return {"models": extract_brain_api(nn._config["api"])}

    return router
