import logging
import socket
import ssl
import sys

from .errors.common import AuthenticationError, NetlabError
from .serializer import serialize, deserialize

log = logging.getLogger(__name__)


class Connection(object):
    def __init__(self, user, token, host, port, socket_timeout=None, retry_on_timeout=False):
        self.user = user
        self.token = token
        self.host = host
        self.port = int(port)
        self.socket_timeout = socket_timeout
        self.retry_on_timeout = retry_on_timeout
        self._sock = None
        self._file = None

    def __del__(self):
        try:
            self.disconnect()
        except Exception:
            pass

    def _create_socket(self):
        self._sock = socket.socket(socket.AF_INET, socket.SOCK_STREAM)

    def connect(self):
        if self._sock:
            return
        try:
            self._create_socket()
            self._sock.settimeout(self.socket_timeout)
            self._sock.connect((self.host, self.port))
        except socket.timeout as e:
            log.debug(e)
            raise
        except socket.error as e:
            log.debug(e)
            raise

        self._file = self._sock.makefile()

        try:
            self.on_connect()
        except NetlabError:
            # clean up after any error in on_connect
            self.disconnect()
            raise

    def on_connect(self):
        auth_request = {
            'id': 1,
            'jsonrpc': '2.0',
            'method': 'user.authenticate',
            'params': {'user': self.user, 'token': self.token}
        }
        self.send(serialize(auth_request))
        response = deserialize(self.read_response())

        if 'error' in response:
            raise AuthenticationError("Login failed")

    def disconnect(self):
        self.on_disconnect()

        if self._sock is None:
            return
        try:
            if self._file is not None:
                self._file.close()
                self._file = None
            self._sock.shutdown(socket.SHUT_RDWR)
            self._sock.close()
        except socket.error:
            pass
        self._sock = None

    def on_disconnect(self):
        pass

    def send(self, request):
        if not self._sock:
            self.connect()
        try:
            serialized_data = request.encode('utf-8')  # socket needs bytestring
            # newline indicates end of RPC request
            self._sock.sendall(serialized_data + b"\n")
        except socket.timeout:
            self.disconnect()
            raise TimeoutError("Timeout writing to socket")
        except socket.error:
            e = sys.exc_info()[1]
            self.disconnect()
            if len(e.args) == 1:
                errno, errmsg = 'UNKNOWN', e.args[0]
            else:
                errno = e.args[0]
                errmsg = e.args[1]
            raise ConnectionError("Error %s while writing to socket. %s." %
                                  (errno, errmsg))
        except:
            self.disconnect()
            raise

    def read_response(self):
        try:
            response = self._file.readline()
            if response == "":
                raise ConnectionError('The remote server closed the connection')
        except:
            self.disconnect()
            raise
        return response


class SSLConnection(Connection):
    def _create_socket(self):
        # overriding this method to do SSL specific setup of the socket
        sock = socket.socket(socket.AF_INET, socket.SOCK_STREAM)
        self._sock = ssl.wrap_socket(sock)
