from __future__ import annotations

from typing import Callable, TypeVar

from ..backend_client import BackendRequestContext, BackendClient
from ..errors import QingflowApiError, raise_tool_error
from ..json_types import JSONObject
from ..session_store import BackendSession, SessionProfile, SessionStore


T = TypeVar("T")


class ToolBase:
    def __init__(self, sessions: SessionStore, backend: BackendClient) -> None:
        self.sessions = sessions
        self.backend = backend

    def _require_context(self, profile: str, *, require_workspace: bool) -> tuple[SessionProfile, BackendSession, BackendRequestContext]:
        session_profile = self.sessions.get_profile(profile)
        if session_profile is None:
            raise QingflowApiError.auth_required(profile)
        backend_session = self.sessions.get_backend_session(profile)
        if backend_session is None:
            raise QingflowApiError.auth_required(profile)
        if require_workspace and session_profile.selected_ws_id is None:
            raise QingflowApiError.workspace_not_selected(profile)
        context = BackendRequestContext(
            base_url=backend_session.base_url,
            token=backend_session.token,
            ws_id=session_profile.selected_ws_id if require_workspace else None,
            qf_version=backend_session.qf_version,
            qf_version_source=backend_session.qf_version_source,
        )
        return session_profile, backend_session, context

    def _run(self, profile: str, func: Callable[[SessionProfile, BackendRequestContext], T], *, require_workspace: bool = True) -> T:
        try:
            session_profile, _, context = self._require_context(profile, require_workspace=require_workspace)
            return func(session_profile, context)
        except QingflowApiError as error:
            self._handle_error(profile, error)
        raise AssertionError("unreachable")

    def _handle_error(self, profile: str, error: QingflowApiError) -> None:
        if error.looks_like_invalid_token():
            self.sessions.invalidate(profile)
            error = QingflowApiError(
                category="auth",
                message=f"Qingflow session for profile '{profile}' has expired. Run auth_login again.",
                backend_code=error.backend_code,
                request_id=error.request_id,
                http_status=error.http_status,
            )
        raise_tool_error(error)

    def _require_dict(self, payload: JSONObject | None, field_name: str = "payload") -> JSONObject:
        if not isinstance(payload, dict) or not payload:
            raise_tool_error(QingflowApiError.config_error(f"{field_name} must be a non-empty object"))
        return payload

    def _high_risk_tool_description(self, *, operation: str, target: str) -> str:
        return (
            f"High-risk {operation} operation for {target}. Read the current state first, "
            "confirm the exact target IDs and intended diff with a human, and avoid running "
            "against production without explicit approval."
        )

    def _attach_human_review_notice(self, response: JSONObject, *, operation: str, target: str) -> JSONObject:
        payload = dict(response)
        payload["requires_human_review"] = True
        payload["risk_notice"] = {
            "operation": operation,
            "target": target,
            "severity": "high",
            "guidance": (
                "Read the current state first, confirm the exact target IDs and intended diff with a human, "
                "and require explicit approval before running against production."
            ),
        }
        return payload
