import asyncio
import socket
from pathlib import Path
from typing import Any

import structlog
from aiohttp import ClientError, TCPConnector
from aiohttp.http_exceptions import HttpProcessingError
from aiohttp.web_exceptions import HTTPException
from csirtmu.core.json_serde import dec_hook
from gql import Client, gql
from gql.transport.aiohttp import AIOHTTPTransport
from gql.transport.exceptions import TransportError, TransportProtocolError, TransportQueryError, TransportServerError
from graphql import DocumentNode
from msgspec import convert

from csirtmu.beta_temporal.config import NetboxConfig
from csirtmu.beta_temporal.external_services.netbox.structs import NetboxIPDTO, NetboxPrefixDTO, NetboxSubnetInfoDTO

HTTP_ERR = (
    ClientError,
    HttpProcessingError,
    TransportQueryError,
    TransportProtocolError,
    TransportServerError,
    HTTPException,
    TransportError,
)


class NetboxClient:
    def __init__(self, netbox_config: NetboxConfig) -> None:
        self.netbox_config = netbox_config
        self.logger = structlog.get_logger()
        with (Path(__file__).parent / "queries.graphql").open() as f:
            self._queries = gql(f.read())

    async def get_subnet_info_on_ip(self, ip: str) -> NetboxSubnetInfoDTO | None:
        operation = "CheckIPWithinPrefixes"

        res = await self._send_graphql_req(operation, {"ip": ip})
        if res is None:
            return None

        return convert(
            {
                "ip_addresses": [
                    convert(address, NetboxIPDTO, dec_hook=dec_hook) for address in res["ip_address_list"]
                ],
                "prefixes": [convert(prefix, NetboxPrefixDTO, dec_hook=dec_hook) for prefix in res["prefix_list"]],
            },
            NetboxSubnetInfoDTO,
        )

    async def _send_graphql_req(
        self, operation: str, variables: dict[str, Any], query: DocumentNode | None = None
    ) -> dict[str, Any] | None:
        if query is None:
            query = self._queries
        client_session_args: dict[str, Any] = {}
        if self.netbox_config.force_ipv4_only:
            client_session_args["connector"] = TCPConnector(loop=asyncio.get_event_loop(), family=socket.AF_INET)
        transport = AIOHTTPTransport(
            url=self.netbox_config.graphql_url,
            headers=self.netbox_config.auth_headers,
            client_session_args=client_session_args,
        )
        try:
            async with Client(
                transport=transport,
                fetch_schema_from_transport=False,
                execute_timeout=self.netbox_config.timeout,
            ) as client:
                try:
                    return await client.execute(  # type: ignore
                        query,
                        operation_name=operation,
                        variable_values=variables,
                        parse_result=False,
                    )
                except TimeoutError:
                    self.logger.exception("Netbox connection timeout")
                    return None

        except HTTP_ERR as e:
            self.logger.exception("Netbox connection error")
            self.logger.exception(e)
            return None
