"""
Universal HTTP fallback patcher.
Intercepts httpx and requests calls to known AI API hostnames.
Catches EVERYTHING — even hand-rolled API calls with no framework.
"""

import time
import functools
import logging
from typing import TYPE_CHECKING

if TYPE_CHECKING:
    from ..emitter import NotiLensEmitter
    from ..loop_detector import LoopDetector

logger = logging.getLogger("notilens.http")

_AI_HOSTS = {
    "api.openai.com",
    "api.anthropic.com",
    "generativelanguage.googleapis.com",
    "api.mistral.ai",
    "api.cohere.ai",
    "api.cohere.com",
    "api.groq.com",
    "api.together.xyz",
    "api.perplexity.ai",
    "openrouter.ai",
}

_patched_httpx = False
_patched_requests = False


def _is_ai_url(url: str) -> bool:
    try:
        from urllib.parse import urlparse
        host = urlparse(str(url)).hostname or ""
        return any(host == h or host.endswith("." + h) for h in _AI_HOSTS)
    except Exception:
        return False


def patch(emitter: "NotiLensEmitter", loop_detector: "LoopDetector", timeout: float) -> bool:
    _patch_httpx(emitter, loop_detector, timeout)
    _patch_requests(emitter, loop_detector, timeout)
    return True


def _patch_httpx(emitter, loop_detector, timeout):
    global _patched_httpx
    if _patched_httpx:
        return
    try:
        import httpx

        _orig_send = httpx.Client.send

        @functools.wraps(_orig_send)
        def _send(self, request, *args, **kwargs):
            url = str(request.url)
            if not _is_ai_url(url):
                return _orig_send(self, request, *args, **kwargs)

            start = time.time()
            loop_msg = loop_detector.record()
            if loop_msg:
                emitter.send("task.loop", loop_msg, level="warning", meta={"url": url, "source": "http"})

            emitter.send("ai.call.start", f"HTTP → {url}",
                         level="debug", meta={"url": url, "method": request.method, "source": "http"})
            try:
                response = _orig_send(self, request, *args, **kwargs)
                duration = round(time.time() - start, 3)
                if duration > timeout:
                    emitter.send("task.timeout", f"HTTP call took {duration}s",
                                 level="warning", meta={"url": url, "duration": duration, "source": "http"})
                if response.status_code >= 400:
                    emitter.send("task.error", f"HTTP {response.status_code} from {url}",
                                 level="error", meta={"url": url, "status": response.status_code, "source": "http"})
                else:
                    emitter.send("ai.call.complete", f"HTTP done in {duration}s",
                                 level="debug", meta={"url": url, "duration": duration,
                                                      "status": response.status_code, "source": "http"})
                return response
            except Exception as exc:
                emitter.send("task.error", f"HTTP error: {exc}",
                             level="error", meta={"url": url, "error": str(exc), "source": "http"})
                raise

        httpx.Client.send = _send

        _orig_async_send = httpx.AsyncClient.send

        @functools.wraps(_orig_async_send)
        async def _async_send(self, request, *args, **kwargs):
            url = str(request.url)
            if not _is_ai_url(url):
                return await _orig_async_send(self, request, *args, **kwargs)

            start = time.time()
            emitter.send("ai.call.start", f"HTTP async → {url}",
                         level="debug", meta={"url": url, "source": "http"})
            try:
                response = await _orig_async_send(self, request, *args, **kwargs)
                duration = round(time.time() - start, 3)
                if response.status_code >= 400:
                    emitter.send("task.error", f"HTTP {response.status_code} from {url}",
                                 level="error", meta={"url": url, "status": response.status_code, "source": "http"})
                else:
                    emitter.send("ai.call.complete", f"HTTP async done in {duration}s",
                                 level="debug", meta={"url": url, "duration": duration, "source": "http"})
                return response
            except Exception as exc:
                emitter.send("task.error", f"HTTP async error: {exc}",
                             level="error", meta={"url": url, "error": str(exc), "source": "http"})
                raise

        httpx.AsyncClient.send = _async_send

        _patched_httpx = True
        logger.debug("NotiLens: httpx patched")
    except ImportError:
        pass
    except Exception as e:
        logger.warning("NotiLens: failed to patch httpx — %s", e)


def _patch_requests(emitter, loop_detector, timeout):
    global _patched_requests
    if _patched_requests:
        return
    try:
        import requests.adapters

        _orig_send = requests.adapters.HTTPAdapter.send

        @functools.wraps(_orig_send)
        def _send(self, request, *args, **kwargs):
            url = str(request.url)
            if not _is_ai_url(url):
                return _orig_send(self, request, *args, **kwargs)

            start = time.time()
            loop_msg = loop_detector.record()
            if loop_msg:
                emitter.send("task.loop", loop_msg, level="warning", meta={"url": url, "source": "http"})

            emitter.send("ai.call.start", f"requests → {url}",
                         level="debug", meta={"url": url, "source": "http"})
            try:
                response = _orig_send(self, request, *args, **kwargs)
                duration = round(time.time() - start, 3)
                if duration > timeout:
                    emitter.send("task.timeout", f"requests call took {duration}s",
                                 level="warning", meta={"duration": duration, "source": "http"})
                if response.status_code >= 400:
                    emitter.send("task.error", f"HTTP {response.status_code} from {url}",
                                 level="error", meta={"url": url, "status": response.status_code, "source": "http"})
                else:
                    emitter.send("ai.call.complete", f"requests done in {duration}s",
                                 level="debug", meta={"url": url, "duration": duration, "source": "http"})
                return response
            except Exception as exc:
                emitter.send("task.error", f"requests error: {exc}",
                             level="error", meta={"url": url, "error": str(exc), "source": "http"})
                raise

        requests.adapters.HTTPAdapter.send = _send

        _patched_requests = True
        logger.debug("NotiLens: requests patched")
    except ImportError:
        pass
    except Exception as e:
        logger.warning("NotiLens: failed to patch requests — %s", e)
