"""NetworkCapture — intercept and record API traffic during test execution.

Attaches to a Playwright Page and silently captures every XHR / fetch
request+response.  The captured data is available for assertions and is
included in failure reports.
"""

from __future__ import annotations

import json
import logging
from dataclasses import dataclass, field
from typing import Any

from playwright.sync_api import Page, Request, Response

log = logging.getLogger("antiqa.browser")


@dataclass
class CapturedRequest:
    method: str
    url: str
    post_data: str | None = None
    headers: dict[str, str] = field(default_factory=dict)


@dataclass
class CapturedResponse:
    status: int
    url: str
    body: Any = None
    headers: dict[str, str] = field(default_factory=dict)


@dataclass
class CapturedExchange:
    """A matched request → response pair."""
    request: CapturedRequest
    response: CapturedResponse | None = None


class NetworkCapture:
    """Capture API traffic from a Playwright page.

    Usage::

        capture = NetworkCapture(page)
        capture.start()
        # ... run actions ...
        capture.stop()

        for ex in capture.exchanges:
            print(ex.request.url, ex.response.status)
    """

    def __init__(self, page: Page, filter_pattern: str | None = "/api/") -> None:
        self.page = page
        self.filter_pattern = filter_pattern
        self.exchanges: list[CapturedExchange] = []
        self._pending: dict[str, CapturedExchange] = {}
        self._listening = False

    def start(self) -> None:
        """Begin capturing network traffic."""
        if self._listening:
            return
        self.page.on("request", self._on_request)
        self.page.on("response", self._on_response)
        self._listening = True
        log.debug("Network capture started (filter=%s)", self.filter_pattern)

    def stop(self) -> None:
        """Stop capturing and flush pending requests."""
        if not self._listening:
            return
        try:
            self.page.remove_listener("request", self._on_request)
            self.page.remove_listener("response", self._on_response)
        except Exception:
            pass
        for exchange in self._pending.values():
            self.exchanges.append(exchange)
        self._pending.clear()
        self._listening = False
        log.debug("Network capture stopped (%d exchanges)", len(self.exchanges))

    def clear(self) -> None:
        """Discard all captured data."""
        self.exchanges.clear()
        self._pending.clear()

    # ── Queries ────────────────────────────────────────

    def find(self, url_contains: str) -> list[CapturedExchange]:
        """Return exchanges whose URL contains *url_contains*."""
        return [e for e in self.exchanges if url_contains in e.request.url]

    def failed_responses(self) -> list[CapturedExchange]:
        """Return exchanges with a 4xx/5xx status."""
        return [
            e for e in self.exchanges
            if e.response and e.response.status >= 400
        ]

    def summary(self) -> list[dict[str, Any]]:
        """Return a serialisable summary of all exchanges."""
        out = []
        for ex in self.exchanges:
            entry: dict[str, Any] = {
                "method": ex.request.method,
                "url": ex.request.url,
            }
            if ex.response:
                entry["status"] = ex.response.status
            out.append(entry)
        return out

    # ── Internal handlers ──────────────────────────────

    def _matches(self, url: str) -> bool:
        if not self.filter_pattern:
            return True
        return self.filter_pattern in url

    def _on_request(self, request: Request) -> None:
        if not self._matches(request.url):
            return
        captured = CapturedRequest(
            method=request.method,
            url=request.url,
            post_data=request.post_data,
            headers=dict(request.headers),
        )
        exchange = CapturedExchange(request=captured)
        self._pending[request.url + request.method] = exchange

    def _on_response(self, response: Response) -> None:
        key = response.url + response.request.method
        exchange = self._pending.pop(key, None)
        if exchange is None:
            if not self._matches(response.url):
                return
            exchange = CapturedExchange(
                request=CapturedRequest(
                    method=response.request.method,
                    url=response.url,
                )
            )

        body = None
        try:
            body = response.json()
        except Exception:
            try:
                body = response.text()
            except Exception:
                pass

        exchange.response = CapturedResponse(
            status=response.status,
            url=response.url,
            body=body,
            headers=dict(response.headers),
        )
        self.exchanges.append(exchange)
        log.debug(
            "Captured %s %s → %s",
            exchange.request.method,
            exchange.request.url,
            exchange.response.status,
        )
