from __future__ import annotations

from typing import AsyncIterator, Any, Dict, Tuple, Union, Iterator, Optional
from loguru import logger
import httpx
import json
import asyncio
import time
from urllib.parse import urlencode
from functools import partial
from urllib.parse import urlparse

import uuid

import requests

from turbo_agent_core.schema.enums import JSON, RequestMethod
from turbo_agent_core.schema.events import (
    BaseEvent,
    RunLifecycleCreatedEvent,
    RunLifecycleCreatedPayload,
    RunLifecycleCompletedEvent,
    RunLifecycleCompletedPayload,
    RunLifecycleFailedEvent,
    RunLifecycleFailedPayload,
    ContentAgentResultStartEvent,
    ContentAgentResultStartPayload,
    ContentAgentResultEndEvent,
    ContentAgentResultEndPayload,
    ExecutorMetadata,
    UserInfo,
)
from turbo_agent_core.schema.agents import APITool
from turbo_agent_core.schema.external import Secret as ExternalSecret
from turbo_agent_runtime.utils.file_materializer import materialize_knowledge_resources
from turbo_agent_runtime.utils.executor_identity import build_executor_id

class APIToolRuntime(APITool):
    """运行 APITool 版本：执行一次 HTTP 请求（若可用），否则回退 echo。
    仅关注最小输入输出，不写库，不做鉴权扩展。"""

    def _resolve_base_url(self) -> Optional[str]:
        endpoint = getattr(self.platform, "endpoint", None)
        if not endpoint:
            return None

        base_url = getattr(endpoint, "baseURL", None) or getattr(endpoint, "url", None)
        if not base_url:
            return None

        if isinstance(base_url, str) and base_url.startswith(("http://", "https://")):
            return base_url

        protocol = "http"
        proto_obj = getattr(endpoint, "protocol", None)
        if proto_obj is not None:
            protocol = proto_obj.value.lower() if hasattr(proto_obj, "value") else str(proto_obj).lower()
            if protocol in {"webprotocol.http", "http"}:
                protocol = "http"
            elif protocol in {"webprotocol.https", "https"}:
                protocol = "https"

        return f"{protocol}://{base_url}"

    def _resolve_user_metadata(self, **kwargs):
        raw = kwargs.get("user_metadata") or kwargs.get("user_info")
        if isinstance(raw, UserInfo):
            return raw
        if isinstance(raw, dict):
            try:
                return UserInfo.model_validate(raw)
            except Exception:
                pass
        user_id = kwargs.get("user_id")
        username = kwargs.get("username")
        return UserInfo(id=str(user_id or "local"), username=str(username or "local"))

    def _should_trust_env(self, base_url: Optional[str]) -> bool:
        if not base_url:
            return True
        try:
            host = (urlparse(base_url).hostname or "").lower()
        except Exception:
            return True
        if host in {"localhost", "127.0.0.1", "::1"}:
            return False
        return True

    def _log_request_context(
        self,
        *,
        phase: str,
        method: str,
        base_url: Optional[str],
        url_path: str,
        query_params: Dict[str, Any],
        body_params: Dict[str, Any],
        trust_env: bool,
        extra: Optional[Dict[str, Any]] = None,
    ) -> None:
        import os

        env_proxy = {
            "HTTP_PROXY": os.getenv("HTTP_PROXY") or os.getenv("http_proxy"),
            "HTTPS_PROXY": os.getenv("HTTPS_PROXY") or os.getenv("https_proxy"),
            "ALL_PROXY": os.getenv("ALL_PROXY") or os.getenv("all_proxy"),
            "NO_PROXY": os.getenv("NO_PROXY") or os.getenv("no_proxy"),
        }
        payload = {
            "阶段": phase,
            "tool_id": getattr(self, "id", None),
            "tool_name": getattr(self, "name", None),
            "method": method,
            "base_url": base_url,
            "url_path": url_path,
            "query": query_params,
            "body": body_params,
            "trust_env": trust_env,
            "env_proxy": env_proxy,
        }
        if extra:
            payload.update(extra)
        logger.debug(f"APITool 请求诊断: {json.dumps(payload, ensure_ascii=False, default=str)}")
    
    def _build_request_params(self, valid_input: Dict[str, Any]) -> Tuple[str, Dict[str, Any], Dict[str, Any]]:
        """
        Construct request parameters from validated input based on tool definition.
        Returns: (url_path, query_dict, body_dict)
        """
        query_dict = {}
        body_dict = {}
        path_dict = {}
        
        if self.input:
            for param in self.input:
                name = param.name
                value = valid_input.get(name)
                if value is None:
                    continue
                position = param.position
                if position == "query":
                    query_dict[name] = value
                elif position == "body":
                    body_dict[name] = value
                elif position == "path":
                    path_dict[name] = value
                elif position == "urlpath": # 兼容旧版
                    path_dict[name] = value
                else:
                    # Fallback logic
                    if self.method in [RequestMethod.POST, RequestMethod.PUT, RequestMethod.PATCH]:
                        body_dict[name] = value
                    else:
                        query_dict[name] = value
        else:
            # 如果没有 input 定义，则根据 method 全部放入 body 或 query
            if self.method in [RequestMethod.POST, RequestMethod.PUT, RequestMethod.PATCH]:
                body_dict.update(valid_input)
            else:
                query_dict.update(valid_input)

        url_path = self.url_path_template or "/"
        if path_dict:
            try:
                url_path = url_path.format(**path_dict)
            except KeyError as e:
                logger.warning(f"Missing path parameter: {e}")
        
        return url_path, query_dict, body_dict

    def _prepare(self, input: JSON, **kwargs) -> Tuple[JSON, str, str, Dict[str, Any], Dict[str, Any]]:
        """
        Prepare request components.
        Returns: (valid_input, url_path, method, query_params, body_params)
        """
        # 1. 验证输入
        data = input if isinstance(input, dict) else {"value": input}
        valid = self.validate_input(data)
        
        # 2. 知识资源（文档）序列化为实际内容
        schema = getattr(self, "input_schema", {}) or {}
        if isinstance(schema, dict) and schema:
            valid = materialize_knowledge_resources(schema, valid)
            
        # 3. 构建参数
        url_path, query_dict, body_dict = self._build_request_params(valid)
        
        # 4. Method
        method = self.method.value if hasattr(self.method, "value") else str(self.method)
        
        return valid, url_path, method, query_dict, body_dict

    def run(self, input: JSON, **kwargs) -> JSON:
        """同步执行"""
        try:
            _, url_path, method, query_params, body_params = self._prepare(input, **kwargs)

            base_url = self._resolve_base_url()
            trust_env = self._should_trust_env(base_url)
            headers = kwargs.get("headers") if isinstance(kwargs.get("headers"), dict) else None
            self._log_request_context(
                phase="请求前(同步)",
                method=method,
                base_url=base_url,
                url_path=url_path,
                query_params=query_params,
                body_params=body_params,
                trust_env=trust_env,
                extra={"header_keys": list(headers.keys()) if headers else []},
            )

            if kwargs.get("oauth1_signer"):
                logger.warning("当前为直连请求模式，已忽略 oauth1_signer。")

            with httpx.Client(base_url=base_url or "", trust_env=trust_env) as session:
                resp = session.request(
                    method=method,
                    url=url_path,
                    params=query_params,
                    json=body_params if method in ["POST", "PUT", "PATCH", "DELETE"] else None,
                    timeout=180,
                    headers=headers,
                )
            
            # 兼容 httpx.Response 与 requests.Response
            raw = self._process_response(resp, str(resp.url))
            self._log_request_context(
                phase="响应后(同步)",
                method=method,
                base_url=base_url,
                url_path=url_path,
                query_params=query_params,
                body_params=body_params,
                trust_env=trust_env,
                extra={
                    "status_code": raw.get("status_code"),
                    "response_url": raw.get("url"),
                    "response_preview": (str(raw.get("data"))[:500] if raw.get("data") is not None else None),
                },
            )
                
            validation_payload: Any = raw
            if isinstance(raw, dict) and isinstance(raw.get("data"), dict):
                validation_payload = raw["data"]
            shaped = self.validate_output(validation_payload)
            return {"tool_id": self.id, "version_id": getattr(self, "id", None), "output": shaped, "raw": raw}
        except Exception as e:
            logger.exception(f"APITool 同步执行失败: {e}")
            return {"error": str(e), "tool_id": self.id}

    async def a_run(self, input: JSON, **kwargs) -> JSON:
        """异步执行"""
        try:
            _, url_path, method, query_params, body_params = self._prepare(input, **kwargs)

            base_url = self._resolve_base_url()
            trust_env = self._should_trust_env(base_url)
            headers = kwargs.get("headers") if isinstance(kwargs.get("headers"), dict) else None
            self._log_request_context(
                phase="请求前(异步)",
                method=method,
                base_url=base_url,
                url_path=url_path,
                query_params=query_params,
                body_params=body_params,
                trust_env=trust_env,
                extra={"header_keys": list(headers.keys()) if headers else []},
            )

            if kwargs.get("oauth1_signer"):
                logger.warning("当前为直连请求模式，已忽略 oauth1_signer。")

            async with httpx.AsyncClient(base_url=base_url or "", trust_env=trust_env) as session:
                resp = await session.request(
                    method=method,
                    url=url_path,
                    params=query_params,
                    json=body_params if method in ["POST", "PUT", "PATCH", "DELETE"] else None,
                    timeout=180,
                    headers=headers,
                )
            
            raw = self._process_response(resp, str(resp.url))
            self._log_request_context(
                phase="响应后(异步)",
                method=method,
                base_url=base_url,
                url_path=url_path,
                query_params=query_params,
                body_params=body_params,
                trust_env=trust_env,
                extra={
                    "status_code": raw.get("status_code"),
                    "response_url": raw.get("url"),
                    "response_preview": (str(raw.get("data"))[:500] if raw.get("data") is not None else None),
                },
            )
                
            validation_payload: Any = raw
            if isinstance(raw, dict) and isinstance(raw.get("data"), dict):
                validation_payload = raw["data"]
            shaped = self.validate_output(validation_payload)
            return {"tool_id": self.id, "version_id": getattr(self, "id", None), "output": shaped, "raw": raw}
        except httpx.ConnectError as e:
            return {"error": f"连接失败: {str(e)}", "tool_id": self.id, "status": "connection_failed"}
        except httpx.ReadTimeout as e:
            return {"error": f"读取超时: {str(e)}", "tool_id": self.id, "status": "timeout"}
        except httpx.ConnectTimeout as e:
            return {"error": f"连接超时: {str(e)}", "tool_id": self.id, "status": "timeout"}
        except httpx.ProxyError as e:
            return {"error": f"代理错误: {str(e)}", "tool_id": self.id, "status": "proxy_error"}
        except httpx.HTTPStatusError as e:
            return {"error": f"HTTP错误: {e.response.status_code} - {e.response.text}", "tool_id": self.id, "status": "http_error"}
        except httpx.RequestError as e:
            return {"error": f"请求异常: {str(e)}", "tool_id": self.id, "status": "request_error"}
        except Exception as e:
            logger.exception(f"APITool 异步执行失败: {e}")
            return {"error": str(e), "tool_id": self.id, "status": "internal_error"}

    async def a_stream(self, input: JSON, **kwargs) -> AsyncIterator[BaseEvent]:
        trace_id = str(uuid.uuid4())
        # 生成唯一的 run_id
        run_id = f"run_{self.id}_{int(time.time()*1000)}"
        executor_type = self.run_type
        executor_id = build_executor_id(self, executor_type)
        executor_path = [executor_id]
        yield RunLifecycleCreatedEvent(
            trace_id=trace_id,
            trace_path=[],
            run_id=run_id,
            run_path=[run_id],
            executor_id=executor_id,
            executor_type=executor_type,
            executor_path=executor_path,
            executor_metadata=ExecutorMetadata(
                id=self.id,
                name=self.name,
                belong_to_project_path=self.belong_to_project_path,
                name_id=self.name_id,
                description=self.description,
                avatar_uri=self.avatar_uri,
                run_type=self.run_type,
                version_id=self.version_id,
                version=self.version_tag
            ),
            user_metadata=self._resolve_user_metadata(**kwargs),
            payload=RunLifecycleCreatedPayload(input_data=input),
        )
        try:
            # 发送结果开始事件
            yield ContentAgentResultStartEvent(
                trace_id=trace_id,
                run_id=run_id,
                executor_id=executor_id,
                executor_type=executor_type,
                executor_path=executor_path,
                payload=ContentAgentResultStartPayload(mode="json")
            )

            # Reuse a_run logic but we need the result to yield
            result = await self.a_run(input, **kwargs)
            
            if "error" in result:
                 yield ContentAgentResultEndEvent(
                    trace_id=trace_id, 
                    run_id=run_id,
                    executor_id=executor_id,
                    executor_path=executor_path,
                    executor_type=executor_type,
                    payload=ContentAgentResultEndPayload(status="error", full_result=output)
                )
                 raise RuntimeError(result["error"])
                 
            output = result.get("output")
            
            yield ContentAgentResultEndEvent(
                trace_id=trace_id, 
                run_id=run_id,
                executor_id=executor_id,
                executor_path=executor_path,
                executor_type=executor_type,
                payload=ContentAgentResultEndPayload(status="success", full_result=output)
            )
            yield RunLifecycleCompletedEvent(
                trace_id=trace_id,
                trace_path=[],
                run_id=run_id,
                run_path=[run_id],
                executor_id=executor_id,
                executor_path=executor_path,
                executor_type=executor_type,
                payload=RunLifecycleCompletedPayload(output=None, usage={"total_tokens": 0}),
            )
        except Exception as e:
            yield RunLifecycleFailedEvent(
                trace_id=trace_id,
                trace_path=[],
                run_id=run_id,
                run_path=[run_id],
                executor_id=executor_id,
                executor_path=executor_path,
                executor_type=executor_type,
                payload=RunLifecycleFailedPayload(error={"code": "RuntimeError", "message": str(e)}),
            )

    def stream(self, input: JSON, **kwargs) -> Iterator[BaseEvent]:
        """同步流式（模拟）"""
        trace_id = str(uuid.uuid4())
        run_id = f"run_{self.id}_{int(time.time()*1000)}"
        executor_type = self.run_type
        executor_id = build_executor_id(self, executor_type)
        executor_path = [executor_id]
        yield RunLifecycleCreatedEvent(
            trace_id=trace_id,
            trace_path=[],
            run_id=run_id,
            run_path=[run_id],
            executor_id=executor_id,
            executor_path=executor_path,
            executor_type=executor_type,
            executor_metadata=ExecutorMetadata(
                id=self.id,
                name=self.name,
                belong_to_project_path=self.belong_to_project_path,
                name_id=self.name_id,
                description=self.description,
                avatar_uri=self.avatar_uri,
                run_type=self.run_type,
                version_id=self.version_id,
                version=self.version_tag
            ),
            user_metadata=self._resolve_user_metadata(**kwargs),
            payload=RunLifecycleCreatedPayload(input_data=input),
        )
        try:
            yield ContentAgentResultStartEvent(
                trace_id=trace_id,
                run_id=run_id,
                executor_id=executor_id,
                executor_path=executor_path,
                executor_type=executor_type,
                payload=ContentAgentResultStartPayload(mode="json")
            )

            result = self.run(input, **kwargs)
            
            if "error" in result:
                 yield ContentAgentResultEndEvent(
                    trace_id=trace_id, 
                    run_id=run_id,
                    executor_id=executor_id,
                    executor_path=executor_path,
                    executor_type=executor_type,
                    payload=ContentAgentResultEndPayload(status="error", full_result=output)
                )
                 raise RuntimeError(result["error"])
                 
            output = result.get("output")
            
            yield ContentAgentResultEndEvent(
                trace_id=trace_id, 
                run_id=run_id,
                executor_id=executor_id,
                executor_path=executor_path,
                executor_type=executor_type,
                payload=ContentAgentResultEndPayload(status="success", full_result=output)
            )
            yield RunLifecycleCompletedEvent(
                trace_id=trace_id,
                trace_path=[],
                run_id=run_id,
                run_path=[run_id],
                executor_id=executor_id,
                executor_path=executor_path,
                executor_type=executor_type,
                payload=RunLifecycleCompletedPayload(output=None, usage={"total_tokens": 0}),
            )
        except Exception as e:
            yield RunLifecycleFailedEvent(
                trace_id=trace_id,
                trace_path=[],
                run_id=run_id,
                run_path=[run_id],
                executor_id=executor_id,
                executor_path=executor_path,
                executor_type=executor_type,
                payload=RunLifecycleFailedPayload(error={"code": "RuntimeError", "message": str(e)}),
            )

    def _process_response(self, response: Union[httpx.Response, requests.Response], url: str) -> Dict[str, Any]:
        try:
            data = response.json()
        except Exception:
            data = response.text
            
        return {
            "status_code": response.status_code,
            "data": data,
            "url": url,
        }
