"""
OpenFang Python Client — REST API client for controlling OpenFang remotely.

Usage:

    from openfang_client import OpenFang

    client = OpenFang("http://localhost:3000")

    # Create an agent
    agent = client.agents.create(template="assistant")
    print(agent["id"])

    # Send a message
    reply = client.agents.message(agent["id"], "Hello!")
    print(reply)

    # Stream a response
    for event in client.agents.stream(agent["id"], "Tell me a joke"):
        if event.get("type") == "text_delta":
            print(event["delta"], end="", flush=True)

Note: This is the REST API *client* library.
      For writing Python agents that run inside OpenFang, see openfang_sdk.py instead.
"""

import json
from typing import Any, Dict, Generator, Optional
from urllib.request import urlopen, Request
from urllib.error import HTTPError
from urllib.parse import urlencode, quote


class OpenFangError(Exception):
    def __init__(self, message: str, status: int = 0, body: str = ""):
        super().__init__(message)
        self.status = status
        self.body = body


class _Resource:
    def __init__(self, client: "OpenFang"):
        self._c = client


class OpenFang:
    """OpenFang REST API client. Zero dependencies — uses only stdlib urllib."""

    def __init__(self, base_url: str, headers: Optional[Dict[str, str]] = None):
        self.base_url = base_url.rstrip("/")
        self._headers = {"Content-Type": "application/json"}
        if headers:
            self._headers.update(headers)

        self.agents = _AgentResource(self)
        self.sessions = _SessionResource(self)
        self.workflows = _WorkflowResource(self)
        self.skills = _SkillResource(self)
        self.channels = _ChannelResource(self)
        self.tools = _ToolResource(self)
        self.models = _ModelResource(self)
        self.providers = _ProviderResource(self)
        self.memory = _MemoryResource(self)
        self.triggers = _TriggerResource(self)
        self.schedules = _ScheduleResource(self)

    def _request(self, method: str, path: str, body: Any = None) -> Any:
        url = self.base_url + path
        data = json.dumps(body).encode() if body is not None else None
        req = Request(url, data=data, headers=self._headers, method=method)
        try:
            with urlopen(req) as resp:
                ct = resp.headers.get("content-type", "")
                text = resp.read().decode()
                if "application/json" in ct:
                    return json.loads(text)
                return text
        except HTTPError as e:
            body_text = e.read().decode() if e.fp else ""
            raise OpenFangError(f"HTTP {e.code}: {body_text}", e.code, body_text) from e

    def _stream(self, method: str, path: str, body: Any = None) -> Generator[Dict, None, None]:
        """SSE streaming. Yields parsed JSON events."""
        url = self.base_url + path
        data = json.dumps(body).encode() if body is not None else None
        headers = dict(self._headers)
        headers["Accept"] = "text/event-stream"
        req = Request(url, data=data, headers=headers, method=method)
        try:
            resp = urlopen(req)
        except HTTPError as e:
            body_text = e.read().decode() if e.fp else ""
            raise OpenFangError(f"HTTP {e.code}: {body_text}", e.code, body_text) from e

        buffer = ""
        while True:
            chunk = resp.read(4096)
            if not chunk:
                break
            buffer += chunk.decode()
            lines = buffer.split("\n")
            buffer = lines.pop()
            for line in lines:
                line = line.strip()
                if line.startswith("data: "):
                    data_str = line[6:]
                    if data_str == "[DONE]":
                        return
                    try:
                        yield json.loads(data_str)
                    except json.JSONDecodeError:
                        yield {"raw": data_str}
        resp.close()

    def health(self) -> Any:
        return self._request("GET", "/api/health")

    def health_detail(self) -> Any:
        return self._request("GET", "/api/health/detail")

    def status(self) -> Any:
        return self._request("GET", "/api/status")

    def version(self) -> Any:
        return self._request("GET", "/api/version")

    def metrics(self) -> str:
        return self._request("GET", "/api/metrics")

    def usage(self) -> Any:
        return self._request("GET", "/api/usage")

    def config(self) -> Any:
        return self._request("GET", "/api/config")


# ── Agent Resource ──────────────────────────────────────────────

class _AgentResource(_Resource):

    def list(self):
        return self._c._request("GET", "/api/agents")

    def get(self, agent_id: str):
        return self._c._request("GET", f"/api/agents/{agent_id}")

    def create(self, **kwargs):
        return self._c._request("POST", "/api/agents", kwargs)

    def delete(self, agent_id: str):
        return self._c._request("DELETE", f"/api/agents/{agent_id}")

    def stop(self, agent_id: str):
        return self._c._request("POST", f"/api/agents/{agent_id}/stop")

    def clone(self, agent_id: str):
        return self._c._request("POST", f"/api/agents/{agent_id}/clone")

    def update(self, agent_id: str, **data):
        return self._c._request("PUT", f"/api/agents/{agent_id}/update", data)

    def set_mode(self, agent_id: str, mode: str):
        return self._c._request("PUT", f"/api/agents/{agent_id}/mode", {"mode": mode})

    def set_model(self, agent_id: str, model: str):
        return self._c._request("PUT", f"/api/agents/{agent_id}/model", {"model": model})

    def message(self, agent_id: str, text: str, **opts):
        body = {"message": text, **opts}
        return self._c._request("POST", f"/api/agents/{agent_id}/message", body)

    def stream(self, agent_id: str, text: str, **opts) -> Generator[Dict, None, None]:
        """Stream response events. Usage:
            for event in client.agents.stream(id, "Hello"):
                if event.get("type") == "text_delta":
                    print(event["delta"], end="")
        """
        body = {"message": text, **opts}
        return self._c._stream("POST", f"/api/agents/{agent_id}/message/stream", body)

    def session(self, agent_id: str):
        return self._c._request("GET", f"/api/agents/{agent_id}/session")

    def reset_session(self, agent_id: str):
        return self._c._request("POST", f"/api/agents/{agent_id}/session/reset")

    def compact_session(self, agent_id: str):
        return self._c._request("POST", f"/api/agents/{agent_id}/session/compact")

    def list_sessions(self, agent_id: str):
        return self._c._request("GET", f"/api/agents/{agent_id}/sessions")

    def create_session(self, agent_id: str, label: Optional[str] = None):
        return self._c._request("POST", f"/api/agents/{agent_id}/sessions", {"label": label})

    def switch_session(self, agent_id: str, session_id: str):
        return self._c._request("POST", f"/api/agents/{agent_id}/sessions/{session_id}/switch")

    def get_skills(self, agent_id: str):
        return self._c._request("GET", f"/api/agents/{agent_id}/skills")

    def set_skills(self, agent_id: str, skills):
        return self._c._request("PUT", f"/api/agents/{agent_id}/skills", skills)

    def set_identity(self, agent_id: str, **identity):
        return self._c._request("PATCH", f"/api/agents/{agent_id}/identity", identity)

    def patch_config(self, agent_id: str, **config):
        return self._c._request("PATCH", f"/api/agents/{agent_id}/config", config)


# ── Session Resource ────────────────────────────────────────────

class _SessionResource(_Resource):

    def list(self):
        return self._c._request("GET", "/api/sessions")

    def delete(self, session_id: str):
        return self._c._request("DELETE", f"/api/sessions/{session_id}")

    def set_label(self, session_id: str, label: str):
        return self._c._request("PUT", f"/api/sessions/{session_id}/label", {"label": label})


# ── Workflow Resource ───────────────────────────────────────────

class _WorkflowResource(_Resource):

    def list(self):
        return self._c._request("GET", "/api/workflows")

    def create(self, **workflow):
        return self._c._request("POST", "/api/workflows", workflow)

    def run(self, workflow_id: str, input_data=None):
        return self._c._request("POST", f"/api/workflows/{workflow_id}/run", input_data)

    def runs(self, workflow_id: str):
        return self._c._request("GET", f"/api/workflows/{workflow_id}/runs")


# ── Skill Resource ──────────────────────────────────────────────

class _SkillResource(_Resource):

    def list(self):
        return self._c._request("GET", "/api/skills")

    def install(self, **skill):
        return self._c._request("POST", "/api/skills/install", skill)

    def uninstall(self, **skill):
        return self._c._request("POST", "/api/skills/uninstall", skill)

    def search(self, query: str):
        return self._c._request("GET", f"/api/marketplace/search?q={quote(query)}")


# ── Channel Resource ────────────────────────────────────────────

class _ChannelResource(_Resource):

    def list(self):
        return self._c._request("GET", "/api/channels")

    def configure(self, name: str, **config):
        return self._c._request("POST", f"/api/channels/{name}/configure", config)

    def remove(self, name: str):
        return self._c._request("DELETE", f"/api/channels/{name}/configure")

    def test(self, name: str):
        return self._c._request("POST", f"/api/channels/{name}/test")


# ── Tool Resource ───────────────────────────────────────────────

class _ToolResource(_Resource):

    def list(self):
        return self._c._request("GET", "/api/tools")


# ── Model Resource ──────────────────────────────────────────────

class _ModelResource(_Resource):

    def list(self):
        return self._c._request("GET", "/api/models")

    def get(self, model_id: str):
        return self._c._request("GET", f"/api/models/{model_id}")

    def aliases(self):
        return self._c._request("GET", "/api/models/aliases")


# ── Provider Resource ───────────────────────────────────────────

class _ProviderResource(_Resource):

    def list(self):
        return self._c._request("GET", "/api/providers")

    def set_key(self, name: str, key: str):
        return self._c._request("POST", f"/api/providers/{name}/key", {"key": key})

    def delete_key(self, name: str):
        return self._c._request("DELETE", f"/api/providers/{name}/key")

    def test(self, name: str):
        return self._c._request("POST", f"/api/providers/{name}/test")


# ── Memory Resource ─────────────────────────────────────────────

class _MemoryResource(_Resource):

    def get_all(self, agent_id: str):
        return self._c._request("GET", f"/api/memory/agents/{agent_id}/kv")

    def get(self, agent_id: str, key: str):
        return self._c._request("GET", f"/api/memory/agents/{agent_id}/kv/{key}")

    def set(self, agent_id: str, key: str, value):
        return self._c._request("PUT", f"/api/memory/agents/{agent_id}/kv/{key}", {"value": value})

    def delete(self, agent_id: str, key: str):
        return self._c._request("DELETE", f"/api/memory/agents/{agent_id}/kv/{key}")


# ── Trigger Resource ────────────────────────────────────────────

class _TriggerResource(_Resource):

    def list(self):
        return self._c._request("GET", "/api/triggers")

    def create(self, **trigger):
        return self._c._request("POST", "/api/triggers", trigger)

    def update(self, trigger_id: str, **trigger):
        return self._c._request("PUT", f"/api/triggers/{trigger_id}", trigger)

    def delete(self, trigger_id: str):
        return self._c._request("DELETE", f"/api/triggers/{trigger_id}")


# ── Schedule Resource ───────────────────────────────────────────

class _ScheduleResource(_Resource):

    def list(self):
        return self._c._request("GET", "/api/schedules")

    def create(self, **schedule):
        return self._c._request("POST", "/api/schedules", schedule)

    def update(self, schedule_id: str, **schedule):
        return self._c._request("PUT", f"/api/schedules/{schedule_id}", schedule)

    def delete(self, schedule_id: str):
        return self._c._request("DELETE", f"/api/schedules/{schedule_id}")

    def run(self, schedule_id: str):
        return self._c._request("POST", f"/api/schedules/{schedule_id}/run")
