import socket
import sys
import time
import threading
import json
import logging
import hashlib
import hmac
import binascii

from PyQt5.QtCore import pyqtSignal, QObject
from common.utils import get_message, send_message
from common.errors import ServerError, IncorrectDataRecivedError
from common.variables import ACTION, PRESENCE, TIME, USER, ACCOUNT_NAME, PUBLIC_KEY, RESPONSE, ERROR, DATA, \
    RESPONSE_511, MESSAGE, SENDER, DESTINATION, MESSAGE_TEXT, GET_CONTACTS, LIST_INFO, PUBLIC_KEY_REQUEST, \
    ADD_CONTACT, REMOVE_CONTACT, EXIT, USERS_REQUEST

sys.path.append('../')

# Инициализация логгера клиента
LOGGER = logging.getLogger('client')
# Объект блокировки сокета и работы с базой данных
sock_lock = threading.Lock()


class ClientTransport(threading.Thread, QObject):
    """
    Класс - Транспорт, отвечает за взаимодействие с сервером.
    Реализует транспортную подсистему клиентского модуля.
    """
    # Сигналы новое сообщение и потеря соединения
    new_message = pyqtSignal(str)
    message_205 = pyqtSignal()
    connection_lost = pyqtSignal()

    def __init__(self, port, ip_address, database, username, passwd, keys):
        threading.Thread.__init__(self)
        QObject.__init__(self)

        self.database = database
        # Имя пользователя
        self.username = username
        # Сокет для работы с сервером
        self.transport = None
        # Пароль
        self.password = passwd
        # Набор ключей для шифрования
        self.keys = keys
        # Устанавливаем соединение:
        self.connection_init(port, ip_address)
        # Обновление таблиц известных пользователей и контактов
        try:
            self.user_list_request()
            self.contacts_list_request()
        except OSError as e:
            if e.errno:
                LOGGER.critical(f'Соединение с сервером потеряно')
                raise ServerError(f'Соедниение с сервером потеряно')
            LOGGER.error('Таймаут соединения при обновлении списков пользователей')
        except json.JSONDecodeError:
            LOGGER.critical(f'Соединение с сервером потеряно')
            raise ServerError(f'Соединение с сервером потеряно')
        self.running = True

    def connection_init(self, port, ip):
        """Функция инициализации соединения с сервером."""
        # Инициализация сокета и сообщение серверу о нашем появлении
        self.transport = socket.socket(socket.AF_INET, socket.SOCK_STREAM)
        self.transport.settimeout(5)

        # Попытка соединения
        connected = False
        for i in range(5):
            LOGGER.info(f'Попытка соединения с сервером №{i+1}')
            try:
                self.transport.connect((ip, port))
            except (OSError, ConnectionRefusedError):
                pass
            else:
                connected = True
                break
            time.sleep(1)

        # Если соединиться с сервером не удалось
        if not connected:
            LOGGER.critical('Не удалось установить соединение с сервером')
            raise ServerError('Не удалось установить соединение с сервером')

        LOGGER.debug('Запускаем авторизацию пользователя')
        # Запускаем процедуру авторизации
        # Получаем хэш пароля
        passwd_bytes = self.password.encode('utf-8')
        salt = self.username.lower().encode('utf-8')
        passwd_hash = hashlib.pbkdf2_hmac('sha512', passwd_bytes, salt, 10000)
        passwd_hash_string = binascii.hexlify(passwd_hash)

        # Получаем публичный ключ и декодируем его из байтов
        pubkey = self.keys.publickey().export_key().decode('ascii')

        # Авторизируемся на сервере
        with sock_lock:
            presense = {
                ACTION: PRESENCE,
                TIME: time.time(),
                USER: {
                    ACCOUNT_NAME: self.username,
                    PUBLIC_KEY: pubkey
                }
            }
            LOGGER.debug(f'Приветственное сообщение: {presense}')
            # Посылаем серверу приветственное сообщение и получаем ответ что всё нормально или получаем исключение.
            try:
                send_message(self.transport, presense)
                ans = get_message(self.transport)
                LOGGER.debug(f'Ответ сервера: {ans}')
                if RESPONSE in ans:
                    if ans[RESPONSE] == 400:
                        raise ServerError(ans[ERROR])
                    elif ans[RESPONSE] == 511:
                        # Если всё нормально, то продолжаем процедуру авторизации.
                        ans_data = ans[DATA]
                        hash = hmac.new(passwd_hash_string, ans_data.encode('utf-8'))
                        digest = hash.digest()
                        my_ans = RESPONSE_511
                        my_ans[DATA] = binascii.b2a_base64(
                            digest).decode('ascii')
                        send_message(self.transport, my_ans)
                        self.process_server_ans(get_message(self.transport))
            except (OSError, json.JSONDecodeError) as e:
                LOGGER.critical(f'Потеряно соединение с сервером.', exc_info=e)
                raise ServerError('Сбой соединения с сервером в процессе авторизации.')
            # Сообщение об удачной установке соединения.
            LOGGER.debug('Соединение с сервером установлено успешно')

    def create_presence(self):
        """Функция генерирует запрос о присутствии клиента."""
        out = {
            ACTION: PRESENCE,
            TIME: time.time(),
            USER: {
                ACCOUNT_NAME: self.username
            }
        }
        LOGGER.debug(f'Сформировано {PRESENCE} сообщение для пользователя {self.username}')
        return out

    def process_ans(self, message):
        """Функция разбирает ответ сервера. Генерирует исключение при ошибке"""
        LOGGER.debug(f'Разбор сообщения от сервера: {message}')
        if RESPONSE in message:
            if message[RESPONSE] == 200:
                return
            elif message[RESPONSE] == 400:
                raise ServerError(f'400 : {message[ERROR]}')
            elif message[RESPONSE] == 205:
                self.user_list_request()
                self.contacts_list_request()
                self.message_205.emit()
            else:
                LOGGER.debug('Неизвестный код подтверждения')

        # Если сообщение от пользователя, добавляем в базу
        elif ACTION in message and message[ACTION] == MESSAGE and SENDER in message and DESTINATION in message \
                and MESSAGE_TEXT in message and message[DESTINATION] == self.username:
            print(f'Получено сообщение от пользователя {message[SENDER]}: {message[MESSAGE_TEXT]}')
            LOGGER.debug(f'Получено сообщение от пользователя {message[SENDER]}: {message[MESSAGE_TEXT]}')

            self.new_message.emit(message)

    def contacts_list_request(self):
        """Функция генерирует запрос и обновление контакт листа с сервера"""
        self.database.contacts_clear()
        LOGGER.debug(f'Запрос контакт листа для пользователся {self.name}')
        req = {
            ACTION: GET_CONTACTS,
            TIME: time.time(),
            USER: self.username
        }
        LOGGER.debug(f'Сформирован запрос {req}')
        with sock_lock:
            send_message(self.transport, req)
            ans = get_message(self.transport)
        LOGGER.debug(f'Получен ответ {ans}')
        if RESPONSE in ans and ans[RESPONSE] == 202:
            for contact in ans[LIST_INFO]:
                self.database.add_contact(contact)
        else:
            LOGGER.error('Не удалось обновить список контактов')

    def user_list_request(self):
        """Функция запроса списка известных пользователей"""
        LOGGER.debug(f'Запрос списка известных пользователей {self.username}')
        req = {
            ACTION: USERS_REQUEST,
            TIME: time.time(),
            ACCOUNT_NAME: self.username
        }
        with sock_lock:
            send_message(self.transport, req)
            ans = get_message(self.transport)
        if RESPONSE in ans and ans[RESPONSE] == 202:
            self.database.add_users(ans[LIST_INFO])
        else:
            LOGGER.error('Не удалось обновить список известных пользователей')

    def key_request(self, user):
        """Функция запрашивающая с сервера публичный ключ пользователя."""
        LOGGER.debug(f'Запрос публичного ключа для {user}')
        req = {
            ACTION: PUBLIC_KEY_REQUEST,
            TIME: time.time(),
            ACCOUNT_NAME: user
        }
        with sock_lock:
            send_message(self.transport, req)
            ans = get_message(self.transport)
        if RESPONSE in ans and ans[RESPONSE] == 511:
            return ans[DATA]
        else:
            LOGGER.error(f'Не удалось получить ключ собеседника {user}.')

    def add_contact(self, contact):
        """Функция добавления пользователя в контакт лист"""
        LOGGER.debug(f'Создание контакта {contact}')
        req = {
            ACTION: ADD_CONTACT,
            TIME: time.time(),
            USER: self.username,
            ACCOUNT_NAME: contact
        }
        with sock_lock:
            send_message(self.transport, req)
            self.process_ans(get_message(self.transport))

    def remove_contact(self, contact):
        """Функция удаления пользователя из контакт листа"""
        LOGGER.debug(f'Удаление контакта {contact}')
        req = {
            ACTION: REMOVE_CONTACT,
            TIME: time.time(),
            USER: self.username,
            ACCOUNT_NAME: contact
        }
        with sock_lock:
            send_message(self.transport, req)
            self.process_ans(get_message(self.transport))

    def transport_shutdown(self):
        """Функция закрытия соединения с отправкой сообщения о выходе"""
        self.running = False
        message = {
            ACTION: EXIT,
            TIME: time.time(),
            ACCOUNT_NAME: self.username
        }
        with sock_lock:
            try:
                send_message(self.transport, message)
            except OSError:
                pass
        LOGGER.debug('Завершение работы транспорта')
        time.sleep(0.5)

    def send_message(self, to_user, message):
        """ Функция отправки сообщения на сервер """
        message_dic = {
            ACTION: MESSAGE,
            SENDER: self.username,
            DESTINATION: to_user,
            TIME: time.time(),
            MESSAGE_TEXT: message
        }
        LOGGER.debug(f'Сформировано сообщение: {message_dic}')
        # Ждем сокета для отправки сообщения
        with sock_lock:
            send_message(self.transport, message_dic)
            self.process_ans(get_message(self.transport))
            LOGGER.info(f'Сообщение отправлено клиенту {to_user}')

    def run(self):
        """Функция содержащая основной цикл работы транспортного потока."""
        LOGGER.debug('Запущен процесс приема сообщений с сервера')
        while self.running:
            time.sleep(1)
            with sock_lock:
                try:
                    self.transport.settimeout(0.5)
                    message = get_message(self.transport)
                except OSError as e:
                    if e.errno:
                        LOGGER.critical(f'Соединение с сервером потеряно')
                        self.running = False
                        self.connection_lost.emit()
                except IncorrectDataRecivedError:
                    LOGGER.error(f'Не удалось декодировать сообщение')
                except (ConnectionError, ConnectionAbortedError, ConnectionResetError, json.JSONDecodeError, TypeError):
                    LOGGER.critical(f'Соединение с сервером потеряно')
                    self.running = False
                    self.connection_lost.emit()
                finally:
                    self.transport.settimeout(1)

        # Если сообщение получено, то вызываем функцию обработчик:
        if message:
            LOGGER.debug(f'Принято сообщение с сервера: {message}')
            self.process_ans(message)
