"""
agent67.core.ollama_lam — 基于 Ollama 本地模型的 Lam 实现

无需 API Key，本地运行，完全离线可用。

Lambda 语义不变:
    OllamaLam("name", "prompt") ≡ λ_D . F_{ollama,D}
    调用 = β-规约 = ollama.chat 解码

用法:
    brain = OllamaLam("assistant", "你是一个助手", model="qwen2.5:7b")
    result = brain("帮我查看文件", ctx)
"""
from __future__ import annotations

import json
import time
import urllib.request
import urllib.error
from typing import Any, Callable, Optional

import sys
from pathlib import Path
PROJECT_ROOT = Path(__file__).resolve().parent.parent.parent.parent
sys.path.insert(0, str(PROJECT_ROOT))

from lambdagent.core import Term, Context


OLLAMA_BASE_URL = "http://localhost:11434"


def ollama_available(base_url=OLLAMA_BASE_URL):
    """检测 Ollama 服务是否在运行。"""
    try:
        req = urllib.request.Request("{}/api/tags".format(base_url))
        with urllib.request.urlopen(req, timeout=3) as resp:
            data = json.loads(resp.read())
            return [m["name"] for m in data.get("models", [])]
    except Exception:
        return []


class OllamaLam(Term):
    """
    基于 Ollama 本地模型的 Lambda 抽象。

    通过 Ollama HTTP API (localhost:11434) 调用本地模型，
    支持 Qwen2.5、GLM-4、Llama3 等所有 Ollama 支持的模型。

    Lambda: OllamaLam("name", "prompt", model="qwen2.5:7b") = λx. ollama(prompt, x)
    """

    def __init__(
        self,
        name: str,
        prompt: str,
        model: str = "qwen2.5:7b",
        temperature: float = 0.3,
        max_tokens: int = 4096,
        base_url: str = OLLAMA_BASE_URL,
        output_parser: Callable[[str], Any] | None = None,
    ):
        super().__init__(name)
        self.prompt = prompt
        self.model = model
        self.temperature = temperature
        self.max_tokens = max_tokens
        self.base_url = base_url
        self.output_parser = output_parser or (lambda x: x)

    def apply(self, input: Any, ctx: Context | None = None) -> Any:
        """β-规约: (λ_D x) → ollama.chat(prompt, x)"""
        ctx = ctx or Context()
        t0 = time.time()

        raw = self._call_ollama(str(input))
        duration = (time.time() - t0) * 1000
        result = self.output_parser(raw)

        ctx.log(self._name, self._trace_id, input, result, duration,
                "ollama/{}".format(self.model))
        return result

    def _call_ollama(self, input_text: str) -> str:
        """通过 Ollama HTTP API 执行 β-规约。"""
        url = "{}/api/chat".format(self.base_url)

        body = json.dumps({
            "model": self.model,
            "messages": [
                {"role": "system", "content": self.prompt},
                {"role": "user", "content": input_text},
            ],
            "options": {
                "temperature": self.temperature,
                "num_predict": self.max_tokens,
            },
            "stream": False,
        }).encode("utf-8")

        req = urllib.request.Request(
            url, data=body,
            headers={"Content-Type": "application/json"},
        )

        try:
            with urllib.request.urlopen(req, timeout=120) as resp:
                data = json.loads(resp.read())
                message = data.get("message", {})
                return message.get("content", "").strip() or "[无输出]"
        except urllib.error.URLError as e:
            return "[Ollama 错误] 连接失败: {}。请确保 ollama serve 在运行".format(e)
        except Exception as e:
            return "[Ollama 错误] {}".format(e)

    def __rshift__(self, other):
        """支持 >> 组合"""
        from lambdagent.primitives import Compose
        return Compose(self, other)
