import asyncio
import sys
import logging
from typing import Optional

from .fingerprints import DEFAULT_PROFILE, available_profiles, get_profile
from .session_intel import SessionIntelligence
from .retry_engine import SmartRetryEngine, RetryAction
from .metrics import MetricsCollector
from .plugins import PluginRegistry

logger = logging.getLogger("nebulascrape.async")


class AsyncNebulaScraper:
    def __init__(
        self,
        profile: str = DEFAULT_PROFILE,
        auto_retry: bool = True,
        max_retries: int = 5,
        debug: bool = False,
        **kwargs
    ):
        self._profile_name = profile if profile in available_profiles() else DEFAULT_PROFILE
        self._profile_data = get_profile(self._profile_name)
        self.auto_retry = auto_retry
        self.max_retries = max_retries
        self.debug = debug
        self._extra = kwargs

        self._intel = SessionIntelligence()
        self._retry_engine = SmartRetryEngine(max_retries=max_retries)
        self._metrics = MetricsCollector()
        self._plugins = PluginRegistry()

        self._client = None
        self._closed = False

    def _build_client(self):
        try:
            import httpx
            headers = dict(self._profile_data.get("headers", {}))
            self._client = httpx.AsyncClient(
                http2=True,
                headers=headers,
                verify=True,
                follow_redirects=True,
            )
            return True
        except ImportError:
            logger.warning("[AsyncNebulaScraper] httpx not installed — falling back to aiohttp")
            return False

    def _build_aiohttp_client(self):
        try:
            import aiohttp
            headers = dict(self._profile_data.get("headers", {}))
            connector = aiohttp.TCPConnector(ssl=True, limit=10)
            self._aiohttp_client = aiohttp.ClientSession(
                headers=headers,
                connector=connector,
            )
            return True
        except ImportError:
            logger.warning("[AsyncNebulaScraper] aiohttp not installed either")
            return False

    def _wrap_aiohttp_response(self, resp, body: bytes, url: str):
        class _AiohttpResponseWrapper:
            def __init__(self, r, content, final_url):
                self.status_code = r.status
                self.headers = dict(r.headers)
                self._content = content
                self.url = str(final_url)
                self.history = []
                self.meta = {}
                self.is_redirect = False

            @property
            def text(self):
                return self._content.decode('utf-8', errors='replace')

            @property
            def content(self):
                return self._content

        return _AiohttpResponseWrapper(resp, body, url)

    async def _ensure_client(self):
        if self._client is None:
            if not self._build_client():
                self._build_aiohttp_client()

    async def _do_request(self, method: str, url: str, **kwargs):
        await self._ensure_client()

        if hasattr(self, '_aiohttp_client') and self._client is None:
            async with self._aiohttp_client.request(method, url, **kwargs) as resp:
                body = await resp.read()
                return self._wrap_aiohttp_response(resp, body, resp.url)

        if self._client is not None:
            import httpx
            response = await self._client.request(method, url, **kwargs)
            response.meta = {}
            return response

        raise RuntimeError("No async HTTP backend available — install httpx or aiohttp")

    async def request(self, method: str, url: str, **kwargs) -> object:
        kwargs = self._plugins.run_pre_request(self, method, url, kwargs)
        self._metrics.start(transport="async_http2")
        self._retry_engine.reset()

        response = None
        last_intel = None

        while True:
            try:
                response = await self._do_request(method, url, **kwargs)
                self._metrics.mark_connected()
            except Exception as e:
                if self.auto_retry and self._retry_engine.attempt < self.max_retries:
                    self._retry_engine._attempt += 1
                    await asyncio.sleep(2 ** self._retry_engine.attempt)
                    continue
                raise

            last_intel = self._intel.analyze(response)

            if self.auto_retry:
                decision = self._retry_engine.analyze_response(response, last_intel)
                self._plugins.run_retry(self, self._retry_engine.attempt, decision)

                if decision.action.value in ('pass', 'abort'):
                    break

                self._metrics.increment_retry()
                if decision.backoff_seconds > 0:
                    logger.debug(f"[AsyncNebulaScraper] Retrying in {decision.backoff_seconds:.2f}s — {decision.reason}")
                    await asyncio.sleep(decision.backoff_seconds)
            else:
                break

        response = self._plugins.run_post_request(self, response, kwargs)
        finished = self._metrics.finish(response)

        if not hasattr(response, 'meta'):
            response.meta = {}
        if last_intel is not None:
            response.meta.update(last_intel.to_meta())
        response.meta['metrics'] = {
            'latency_ms': finished.latency_ms,
            'tls_handshake_ms': finished.tls_handshake_ms,
            'retry_count': finished.retry_count,
            'redirect_depth': finished.redirect_depth,
            'transport_used': finished.transport_used,
        }

        return response

    async def get(self, url: str, **kwargs):
        return await self.request("GET", url, **kwargs)

    async def post(self, url: str, **kwargs):
        return await self.request("POST", url, **kwargs)

    async def put(self, url: str, **kwargs):
        return await self.request("PUT", url, **kwargs)

    async def delete(self, url: str, **kwargs):
        return await self.request("DELETE", url, **kwargs)

    def register_plugin(self, plugin):
        self._plugins.register(plugin)

    @property
    def metrics(self):
        return self._metrics.session_metrics()

    async def close(self):
        if self._client:
            await self._client.aclose()
        if hasattr(self, '_aiohttp_client'):
            await self._aiohttp_client.close()
        self._closed = True

    async def __aenter__(self):
        return self

    async def __aexit__(self, *args):
        await self.close()


AsyncClient = AsyncNebulaScraper
