"""Admin endpoints for configuring and exercising AI agents.

Design notes:

* Read endpoints require a valid JWT but not admin. The "AI Agents"
  tab needs to be reachable by anyone who can see ``/admin/ai/prompts``
  today (analysts, teachers auditing prompt changes, etc.).
* Write endpoints — ``PATCH /api/ai/agents/<id>`` and
  ``POST /api/ai/agents/<id>/test`` — additionally require admin
  privileges (``base.group_system``), matching the existing prompt
  controller's policy.
* ``/test`` is deliberately synchronous and uncached: admins use it to
  quickly verify a config change produces sane output. It caps the
  LLM at 500 tokens to keep iteration cheap.
"""

from __future__ import annotations

import json
import logging

from odoo import http
from odoo.http import request

from odoo.addons.encoach_api.controllers.base import (
    _error_response,
    _get_json_body,
    _json_response,
    jwt_required,
)

_logger = logging.getLogger(__name__)


def _require_admin():
    if not request.env.user.has_group("base.group_system"):
        return _error_response("Admin privileges required", 403)
    return None


class EncoachAIAgentsController(http.Controller):

    # ------------------------------------------------------------------
    # GET /api/ai/agents
    # ------------------------------------------------------------------
    @http.route("/api/ai/agents", type="http", auth="none", methods=["GET"], csrf=False)
    @jwt_required
    def list_agents(self, **kw):
        try:
            search = (kw.get("search") or "").strip()
            domain = []
            if search:
                domain = [
                    "|", "|",
                    ("key", "ilike", search),
                    ("name", "ilike", search),
                    ("description", "ilike", search),
                ]
            Agent = request.env["encoach.ai.agent"].sudo()
            records = Agent.search(domain, order="sequence, name")
            items = [r.to_api_dict(include_prompt=False) for r in records]
            return _json_response({
                "items": items,
                "data": items,
                "total": len(items),
            })
        except Exception as exc:
            _logger.exception("list agents failed")
            return _error_response(str(exc), 500)

    # ------------------------------------------------------------------
    # GET /api/ai/agents/<id>
    # ------------------------------------------------------------------
    @http.route(
        "/api/ai/agents/<int:agent_id>",
        type="http", auth="none", methods=["GET"], csrf=False,
    )
    @jwt_required
    def get_agent(self, agent_id, **kw):
        try:
            agent = request.env["encoach.ai.agent"].sudo().browse(int(agent_id))
            if not agent.exists():
                return _error_response("Agent not found", 404)
            data = agent.to_api_dict(include_prompt=True)
            data["tools"] = [t.to_api_dict() for t in agent.tool_ids]
            return _json_response(data)
        except Exception as exc:
            _logger.exception("get agent failed")
            return _error_response(str(exc), 500)

    # ------------------------------------------------------------------
    # PATCH /api/ai/agents/<id>  (admin-only)
    # ------------------------------------------------------------------
    @http.route(
        "/api/ai/agents/<int:agent_id>",
        type="http", auth="none", methods=["PATCH", "PUT"], csrf=False,
    )
    @jwt_required
    def update_agent(self, agent_id, **kw):
        err = _require_admin()
        if err is not None:
            return err
        try:
            agent = request.env["encoach.ai.agent"].sudo().browse(int(agent_id))
            if not agent.exists():
                return _error_response("Agent not found", 404)
            body = _get_json_body() or {}
            vals: dict = {}
            # Whitelist every settable field so callers can't flip `active` or
            # rewrite `key` without knowing they're allowed to.
            for f in (
                "name", "description", "system_prompt", "prompt_key",
                "model", "fallback_model", "response_format",
                "graph_type", "quality_checks",
            ):
                if f in body:
                    vals[f] = body[f] or ""
            for f in ("temperature",):
                if f in body:
                    try:
                        vals[f] = float(body[f])
                    except (TypeError, ValueError):
                        pass
            for f in ("max_tokens", "max_revisions", "sequence"):
                if f in body:
                    try:
                        vals[f] = int(body[f])
                    except (TypeError, ValueError):
                        pass
            if "active" in body:
                vals["active"] = bool(body["active"])
            if "tool_keys" in body and isinstance(body["tool_keys"], list):
                tool_ids = request.env["encoach.ai.tool"].sudo().search(
                    [("key", "in", [str(k) for k in body["tool_keys"]])]
                ).ids
                vals["tool_ids"] = [(6, 0, tool_ids)]
            with request.env.cr.savepoint():
                agent.write(vals)
            return _json_response(agent.to_api_dict(include_prompt=True))
        except Exception as exc:
            _logger.exception("update agent failed")
            return _error_response(str(exc), 400)

    # ------------------------------------------------------------------
    # POST /api/ai/agents/<id>/test  (admin-only)
    # ------------------------------------------------------------------
    @http.route(
        "/api/ai/agents/<int:agent_id>/test",
        type="http", auth="none", methods=["POST"], csrf=False,
    )
    @jwt_required
    def test_agent(self, agent_id, **kw):
        err = _require_admin()
        if err is not None:
            return err
        try:
            agent = request.env["encoach.ai.agent"].sudo().browse(int(agent_id))
            if not agent.exists():
                return _error_response("Agent not found", 404)
            body = _get_json_body() or {}
            variables = body.get("variables") or {}
            payload = body.get("payload")
            language = body.get("language") or request.env.user.lang or "en"

            from odoo.addons.encoach_ai.services.agent_runtime import AgentRuntime
            runtime = AgentRuntime(request.env, agent, language=language)
            # Small-budget test: cap max_tokens so iteration stays cheap.
            original_max = agent.max_tokens
            if original_max > 800:
                agent.sudo().write({"max_tokens": 800})
            try:
                final = runtime.invoke(variables=variables, payload=payload)
            finally:
                if agent.max_tokens != original_max:
                    agent.sudo().write({"max_tokens": original_max})

            output = final.get("output")
            return _json_response({
                "error": final.get("error") or "",
                "output": output,
                "output_raw": (final.get("output_raw") or "")[:6000],
                "tool_results": (final.get("tool_results") or [])[:20],
                "retrieval_hits": len(final.get("retrieval") or []),
                "revisions_used": final.get("revisions_used") or 0,
                "quality_issues": final.get("quality_issues") or [],
                "iterations": final.get("iterations") or 0,
            })
        except Exception as exc:
            _logger.exception("test agent failed")
            return _error_response(str(exc), 500)

    # ------------------------------------------------------------------
    # GET /api/ai/agents/tools
    # ------------------------------------------------------------------
    @http.route(
        "/api/ai/agents/tools",
        type="http", auth="none", methods=["GET"], csrf=False,
    )
    @jwt_required
    def list_tools(self, **kw):
        try:
            tools = request.env["encoach.ai.tool"].sudo().search([], order="category, sequence, name")
            items = [t.to_api_dict() for t in tools]
            return _json_response({"items": items, "data": items, "total": len(items)})
        except Exception as exc:
            _logger.exception("list tools failed")
            return _error_response(str(exc), 500)

    # ------------------------------------------------------------------
    # PATCH /api/ai/agents/tools/<id>  (admin-only; currently toggle active)
    # ------------------------------------------------------------------
    @http.route(
        "/api/ai/agents/tools/<int:tool_id>",
        type="http", auth="none", methods=["PATCH", "PUT"], csrf=False,
    )
    @jwt_required
    def update_tool(self, tool_id, **kw):
        err = _require_admin()
        if err is not None:
            return err
        try:
            tool = request.env["encoach.ai.tool"].sudo().browse(int(tool_id))
            if not tool.exists():
                return _error_response("Tool not found", 404)
            body = _get_json_body() or {}
            vals: dict = {}
            if "active" in body:
                vals["active"] = bool(body["active"])
            for f in ("name", "description", "category"):
                if f in body:
                    vals[f] = body[f] or ""
            if "schema" in body:
                # Accept a parsed dict OR raw JSON string.
                raw = body["schema"]
                if isinstance(raw, (dict, list)):
                    vals["schema_json"] = json.dumps(raw)
                else:
                    vals["schema_json"] = str(raw)
            with request.env.cr.savepoint():
                tool.write(vals)
            return _json_response(tool.to_api_dict())
        except Exception as exc:
            _logger.exception("update tool failed")
            return _error_response(str(exc), 400)
