import asyncio
from pathlib import Path
from ssl import SSLContext

from dataclasses import dataclass, field
import socket
import sys

from aionetworking.compatibility_os import get_client_kwargs
from aionetworking.networking.protocol_factories import DatagramClientProtocolFactory
from aionetworking.types.networking import ConnectionType
from aionetworking.networking.ssl import ClientSideSSL
from .base import BaseClient, BaseNetworkClient

from typing import Union, Optional


@dataclass
class TCPClient(BaseNetworkClient):
    name = "TCP Client"
    peer_prefix = 'tcp'
    transport: asyncio.Transport = field(init=False, compare=False, default=None)
    server_hostname: str = None
    happy_eyeballs_delay: float = None
    interleave: int = None
    ssl: ClientSideSSL = None
    ssl_handshake_timeout: int = None

    def __post_init__(self) -> None:
        super().__post_init__()
        if self.ssl:
            self.close_tasks.append(self.ssl.close)
            self.ssl.set_logger(self.logger)

    @property
    def ssl_context(self) -> Optional[SSLContext]:
        if self.ssl:
            return self.ssl.context

    async def _open_connection(self) -> ConnectionType:
        extra_kwargs = get_client_kwargs(self.happy_eyeballs_delay, self.interleave)
        self.transport, self.conn = await self.loop.create_connection(
            self.protocol_factory, host=self.host, port=self.port, ssl=self.ssl_context, local_addr=self.local_addr,
            server_hostname=self.server_hostname, ssl_handshake_timeout=self.ssl_handshake_timeout, **extra_kwargs)
        return self.conn


@dataclass
class UnixSocketClient(BaseClient):
    name = "Unix Socket Client"
    peer_prefix = 'unix'
    path: Union[str, Path] = None
    transport: asyncio.Transport = field(init=False, compare=False, default=None)
    server_hostname: Optional[str] = None

    ssl: Optional[ClientSideSSL] = None
    ssl_handshake_timeout: Optional[int] = None

    def __post_init__(self) -> None:
        super().__post_init__()
        if self.ssl:
            self.close_tasks.append(self.ssl.close)
            self.ssl.set_logger(self.logger)

    @property
    def ssl_context(self) -> Optional[SSLContext]:
        if self.ssl:
            return self.ssl.context

    @property
    def src(self) -> str:
        if self.transport:
            _socket = self.transport.get_extra_info('socket')
            return str(_socket.fd)
        return ''

    @property
    def dst(self) -> str:
        return str(self.path)

    async def _open_connection(self) -> ConnectionType:
        extra_kwargs = get_client_kwargs()
        self.transport, self.conn = await self.loop.create_unix_connection(
            self.protocol_factory, path=str(self.path), ssl=self.ssl_context, server_hostname=self.server_hostname,
            ssl_handshake_timeout=self.ssl_handshake_timeout, **extra_kwargs)
        return self.conn



@dataclass
class UDPClient(BaseNetworkClient):
    name = "UDP Client"
    peer_prefix = 'udp'
    transport: asyncio.DatagramTransport = field(init=False, compare=False, default=None)
    reuse_port: Optional[bool] = None
    allow_broadcast: Optional[bool] = None

    async def _open_connection(self) -> ConnectionType:
        protocol_factory: DatagramClientProtocolFactory
        self.transport, protocol_factory = await self.loop.create_datagram_endpoint(
            self.protocol_factory, remote_addr=(self.host, self.port), local_addr=self.local_addr,
            reuse_port=self.reuse_port, allow_broadcast=self.allow_broadcast)
        self.conn = protocol_factory.new_peer()
        await self.conn.wait_connected()
        return self.conn
