import sys
import configparser
from threading import Lock
from PyQt5.QtWidgets import QMainWindow, QAction, qApp, QApplication, QLabel, QTableView, QDialog, QPushButton, \
    QLineEdit, QFileDialog, QMessageBox
from PyQt5.QtGui import QStandardItemModel, QStandardItem
from PyQt5.QtCore import Qt, QTimer
sys.path.append('../')
from common.decorator import LOGGER
from server_part.server_config import ServerConfig
from server_part.add_user import RegisterUser
from server_part.remove_user import DelUserDialog

# Флаг что был подключён новый пользователь, нужен чтобы не мучать БД
# постоянными запросами на обновление
new_connection = False
conflag_lock = Lock()


def gui_create_model(db_session):
    """
    GUI - Создание таблицы QModel, для отображения в окне программы.
    :param db_session: сессия БД
    :return: список
    """
    list_users = db_session.active_users_list()
    list = QStandardItemModel()
    list.setHorizontalHeaderLabels(['Имя Клиента', 'IP Адрес', 'Порт', 'Время подключения'])
    for row in list_users:
        user, ip, port, time = row
        user = QStandardItem(user)
        user.setEditable(False)
        ip = QStandardItem(ip)
        ip.setEditable(False)
        port = QStandardItem(str(port))
        port.setEditable(False)
        # Уберём милисекунды из строки времени, т.к. такая точность не требуется.
        time = QStandardItem(str(time.replace(microsecond=0)))
        time.setEditable(False)
        list.appendRow([user, ip, port, time])
    return list


def create_stat_model(db_session):
    """
    GUI - Функция реализующая заполнение таблицы историей сообщений.
    :param db_session: сессия БД
    :return: список
    """
    # Список записей из базы
    hist_list = db_session.message_history()

    # Объект модели данных:
    list = QStandardItemModel()
    list.setHorizontalHeaderLabels(
        ['Имя Клиента', 'Последний раз входил', 'Сообщений отправлено', 'Сообщений получено'])
    for row in hist_list:
        user, sent, recvd = row
        user = QStandardItem(user)
        user.setEditable(False)
        sent = QStandardItem(str(sent))
        sent.setEditable(False)
        recvd = QStandardItem(str(recvd))
        recvd.setEditable(False)
        list.appendRow([user, sent, recvd])
    return list


class MainWindow(QMainWindow):
    """
    Класс основного окна.
    """
    def __init__(self):
        super().__init__()
        self.initUI()

    def initUI(self):
        # Кнопка выхода
        exitAction = QAction('Выход', self)
        exitAction.setShortcut('Ctrl+Q')
        exitAction.triggered.connect(qApp.quit)

        # Кнопка обновить список клиентов
        self.refresh_button = QAction('Обновить список', self)

        # Кнопка настроек сервера
        self.config_btn = QAction('Настройки сервера', self)

        # Кнопка регистрации пользователя
        self.register_btn = QAction('Регистрация пользователя', self)

        # Кнопка удаления пользователя
        self.remove_btn = QAction('Удаление пользователя', self)

        # Кнопка вывести историю сообщений
        self.show_history_button = QAction('История клиентов', self)

        # Статусбар
        # dock widget
        self.statusBar()

        # Тулбар
        self.toolbar = self.addToolBar('MainBar')
        self.toolbar.addAction(exitAction)
        self.toolbar.addAction(self.refresh_button)
        self.toolbar.addAction(self.show_history_button)
        self.toolbar.addAction(self.config_btn)
        self.toolbar.addAction(self.register_btn)
        self.toolbar.addAction(self.remove_btn)

        # Настройки геометрии основного окна
        # Поскольку работать с динамическими размерами мы не умеем, и мало времени на изучение, размер окна фиксирован.
        self.setFixedSize(800, 600)
        self.setWindowTitle('Messaging Server alpha release')

        # Надпись о том, что ниже список подключённых клиентов
        self.label = QLabel('Список подключённых клиентов:', self)
        self.label.setFixedSize(240, 15)
        self.label.move(10, 25)

        # Окно со списком подключённых клиентов.
        self.active_clients_table = QTableView(self)
        self.active_clients_table.move(10, 45)
        self.active_clients_table.setFixedSize(780, 400)

        # Последним параметром отображаем окно.
        self.show()


class HistoryWindow(QDialog):
    """
    Класс окна с историей пользователей.
    """
    def __init__(self):
        super().__init__()
        self.initUI()

    def initUI(self):
        # Настройки окна:
        self.setWindowTitle('Статистика клиентов')
        self.setFixedSize(600, 700)
        self.setAttribute(Qt.WA_DeleteOnClose)

        # Кнапка закрытия окна
        self.close_button = QPushButton('Закрыть', self)
        self.close_button.move(250, 650)
        self.close_button.clicked.connect(self.close)

        # Лист с собственно историей
        self.history_table = QTableView(self)
        self.history_table.move(10, 10)
        self.history_table.setFixedSize(580, 620)

        self.show()


class ConfigWindow(QDialog):
    """
    Класс окна настроек.
    """
    def __init__(self):
        super().__init__()
        self.initUI()

    def save(self):
        return

    def initUI(self):
        # Настройки окна
        self.setFixedSize(365, 260)
        self.setWindowTitle('Настройки сервера')

        # Надпись о файле базы данных:
        self.db_path_label = QLabel('Путь до файла базы данных: ', self)
        self.db_path_label.move(10, 10)
        self.db_path_label.setFixedSize(240, 15)

        # Строка с путём базы
        self.db_path = QLineEdit(self)
        self.db_path.setFixedSize(250, 20)
        self.db_path.move(10, 30)
        self.db_path.setReadOnly(True)

        # Кнопка выбора пути.
        self.db_path_select = QPushButton('Обзор...', self)
        self.db_path_select.move(275, 28)

        # Функция обработчик открытия окна выбора папки
        def open_file_dialog():
            global dialog
            dialog = QFileDialog(self)
            path = dialog.getExistingDirectory()
            path = path.replace('/', '\\')
            self.db_path.insert(path)

        self.db_path_select.clicked.connect(open_file_dialog)

        # Метка с именем поля файла базы данных
        self.db_file_label = QLabel('Имя файла базы данных: ', self)
        self.db_file_label.move(10, 68)
        self.db_file_label.setFixedSize(180, 15)

        # Поле для ввода имени файла
        self.db_file = QLineEdit(self)
        self.db_file.move(200, 66)
        self.db_file.setFixedSize(150 , 20)

        # Метка с номером порта
        self.port_label = QLabel('Номер порта для соединений:', self)
        self.port_label.move(10, 108)
        self.port_label.setFixedSize(180, 15)

        # Поле для ввода номера порта
        self.port = QLineEdit(self)
        self.port.move(200, 108)
        self.port.setFixedSize(150, 20)

        # Метка с адресом для соединений
        self.ip_label = QLabel('С какого IP принимаем соединения:', self)
        self.ip_label.move(10, 148)
        self.ip_label.setFixedSize(180, 15)

        # Метка с напоминанием о пустом поле.
        self.ip_label_note = QLabel(' оставьте это поле пустым, чтобы\n принимать соединения с любых адресов.', self)
        self.ip_label_note.move(10, 168)
        self.ip_label_note.setFixedSize(500, 30)

        # Поле для ввода ip
        self.ip = QLineEdit(self)
        self.ip.move(200, 148)
        self.ip.setFixedSize(150, 20)

        # Кнопка сохранения настроек
        self.save_btn = QPushButton('Сохранить', self)
        self.save_btn.move(190 , 220)
        self.save_btn.clicked.connect(self.save)

        # Кнапка закрытия окна
        self.close_button = QPushButton('Закрыть', self)
        self.close_button.move(275, 220)
        self.close_button.clicked.connect(self.close)

        self.show()


def run_server_gui(server, config):
    """
    Запуск GUI сервера.
    :param server: сервер
    :param config: конфигурация
    """
    # Создаём графическое окуружение для сервера:
    server_app = QApplication(sys.argv)
    main_window = MainWindow()
    db_session = server.database.create_session()

    # Инициализируем параметры в окна
    main_window.statusBar().showMessage('Server Working')
    main_window.active_clients_table.setModel(gui_create_model(db_session))
    main_window.active_clients_table.resizeColumnsToContents()
    main_window.active_clients_table.resizeRowsToContents()

    def list_update():
        """
        Подфункция обновляющая список подключённых, проверяет флаг подключения, и
        если надо обновляет список.
        """
        global new_connection
        if new_connection:
            main_window.active_clients_table.setModel(
                gui_create_model(db_session))
            main_window.active_clients_table.resizeColumnsToContents()
            main_window.active_clients_table.resizeRowsToContents()
            with conflag_lock:
                new_connection = False

    def show_statistics():
        """
        Подфункция создающая окно со статистикой клиентов.
        """
        global stat_window
        stat_window = HistoryWindow()
        stat_window.history_table.setModel(create_stat_model(db_session))
        stat_window.history_table.resizeColumnsToContents()
        stat_window.history_table.resizeRowsToContents()
        stat_window.show()

    def server_config():
        """
        Подфункция создающяя окно с настройками сервера.
        """
        global config_window
        # Создаём окно и заносим в него текущие параметры
        config_window = ConfigWindow()
        config_window.db_path.insert(config.db_path)
        config_window.db_file.insert(config.db_file)
        config_window.port.insert(str(config.port))
        config_window.ip.insert(config.address)
        config_window.save_btn.clicked.connect(save_server_config)
        config_window.show()

    def reg_user():
        """Подфункция создающая окно регистрации пользователя."""
        global reg_window
        reg_window = RegisterUser(server)
        reg_window.show()

    def rem_user():
        """Подфункция создающая окно удаления пользователя."""
        global rem_window
        rem_window = DelUserDialog(server)
        rem_window.show()

    def save_server_config():
        """Подфункция сохранения настроек."""
        global config_window
        message = QMessageBox()
        new_config = ServerConfig()
        new_config.db_path = config_window.db_path.text()
        new_config.db_file = config_window.db_file.text()
        new_config.port = config_window.port.text()
        new_config.address = config_window.ip.text()
        try:
            new_config.validate()
            new_config.copy_to(config)
        except ValueError as e:
            message.warning(config_window, 'Ошибка', str(e))
        except Exception as e:
            message.warning(config_window, 'Ошибка', 'Системная ошибка при загрузке конфигурации')
            LOGGER.critical(str(e))
        try:
            with open('../server.ini', 'w') as file:
                ini = configparser.ConfigParser()
                config.write_to_ini(ini)
                ini.write(file)
            message.information(
                config_window, 'OK', 'Настройки успешно сохранены!')
        except Exception as e:
            message.warning(config_window, 'Ошибка', 'Системная ошибка при записи конфигурации в файл')
            LOGGER.critical(str(e))

    # Таймер, обновляющий список клиентов 1 раз в секунду
    timer = QTimer()
    timer.timeout.connect(list_update)
    timer.start(1000)

    def handle_connections_change():
        global new_connection
        with conflag_lock:
            new_connection = True
    server.on_connections_change.append(handle_connections_change)

    # Связываем кнопки с процедурами
    main_window.refresh_button.triggered.connect(list_update)
    main_window.show_history_button.triggered.connect(show_statistics)
    main_window.config_btn.triggered.connect(server_config)
    main_window.register_btn.triggered.connect(reg_user)
    main_window.remove_btn.triggered.connect(rem_user)

    # Запускаем GUI
    server_app.exec_()
