import logging
import subprocess
from datetime import datetime
from json import loads, dumps

from PyQt5.QtCore import Qt, QDate
from PyQt5.QtGui import QCloseEvent
from PyQt5.QtWidgets import QWidget, QGridLayout, QPushButton, QTableWidget, \
    QHeaderView, QCheckBox, QDateEdit, \
    QHBoxLayout, QMessageBox

from school_ringer_modules.config import config_path
from school_ringer_modules.system import run_command


def qdate_from_qdateedit_str(date_string: str) -> QDate:
    """
    Преобразование из текста в QDate
    @param date_string: str
    @return: QDate
    """
    day, month, year = map(int, date_string.split('.'))
    return QDate(year, month, day)


def is_holiday_today() -> bool:
    """
    Определяет, попадает ли сегодняшний день в каникулы
    @return: True, если попадает, иначе False
    """
    try:
        with open(f'{config_path}/holidays.json', 'r', encoding='utf-8') as file:
            holidays_from_file = loads(file.read())
        today = QDate.currentDate()
        for holiday in holidays_from_file:
            if holidays_from_file[holiday]['enabled'] is True:
                if qdate_from_qdateedit_str(holidays_from_file[holiday]['start']) <= today <= \
                        qdate_from_qdateedit_str(holidays_from_file[holiday]['end']):
                    return True
        return False

    except Exception as e:
        logging.info(f'Не удалось определить даты каникул, {e}')
        return False


class CalendarEditor(QWidget):
    def __init__(self) -> None:
        """
        Конструктор, создающий экземпляр класса
        """
        super().__init__()
        self.initUI()

    def load_data(self) -> None:
        """
        Заполнение таблицы UI данными из json-файла
        """
        try:
            with open(f'{config_path}/holidays.json', 'r', encoding='utf-8') as file:
                holidays_from_file = loads(file.read())
            for holiday in holidays_from_file:
                self.checkboxes[int(holiday)].setChecked(holidays_from_file[holiday]['enabled'])
                if self.checkboxes[int(holiday)].isChecked():
                    row = int(holiday)
                    self.calendar_table.cellWidget(row, 1).setEnabled(True)
                    self.calendar_table.cellWidget(row, 2).setEnabled(True)
                self.calendar_table.cellWidget(int(holiday), 1).setDate(
                    qdate_from_qdateedit_str(holidays_from_file[holiday]['start'])
                )
                self.calendar_table.cellWidget(int(holiday), 2).setDate(
                    qdate_from_qdateedit_str(holidays_from_file[holiday]['end'])
                )
        except Exception:
            logging.info('Файл с каникулами не найден или некорректен')

    def save_holidays(self) -> None:
        """
        Сохраняет коникулы в файл конфига с проверкой корректности заполнения
        @return: None
        """
        output = dict()
        for row in range(self.holidays_cnt):
            output[str(row)] = dict()
            output[str(row)]['enabled'] = self.checkboxes[row].isChecked()
            output[str(row)]['start'] = self.calendar_table.cellWidget(row, 1).text()
            output[str(row)]['end'] = self.calendar_table.cellWidget(row, 2).text()

            if (self.checkboxes[row].isChecked() and
                    datetime.strptime(output[str(row)]['start'], '%d.%m.%Y') >
                    datetime.strptime(output[str(row)]['end'], '%d.%m.%Y')):
                dlg = QMessageBox()
                dlg.setWindowTitle('Внимание!')
                dlg.setText(f'Невозможно сохранить каникулы: в строке {row + 1} каникулы начинаются позже, чем '
                            f'заканчиваются!')
                dlg.exec()
                logging.info(f"Каникулы не сохранены: {output[str(row)]['start']} > {output[str(row)]['end']}")
                return

        tempfile = run_command('mktemp').strip()
        with open(tempfile, 'w', encoding='utf-8') as file:
            file.write(dumps(output, indent=4, ensure_ascii=False))
        if subprocess.run(f'pkexec sh -c '
                          f'"mv {tempfile} {config_path}/holidays.json && '
                          f'chmod 644 {config_path}/holidays.json"', shell=True).returncode == 0:
            logging.info(f'Каникулы перенесены в файл {config_path}/holidays.json')
        else:
            logging.info('При сохранении каникул возникла ошибка. Проверьте пароль.')

    def cell_was_clicked(self, row: int, column: int) -> None:
        """
        Меняет доступность строки для редактирования дат каникул при смене состояния флажка в первом столбце
        @param row: номер строки
        @param column: номер столбца
        """
        if column == 0:
            self.checkboxes[row].nextCheckState()
            if self.checkboxes[row].isChecked():
                self.calendar_table.cellWidget(row, 1).setEnabled(True)
                self.calendar_table.cellWidget(row, 2).setEnabled(True)
            else:
                self.calendar_table.cellWidget(row, 1).setDisabled(True)
                self.calendar_table.cellWidget(row, 2).setDisabled(True)

    def checkbox_was_switched(self) -> None:
        """
        Меняет доступность строки для редактирования дат каникул при смене состояния флажка в первом столбце
        """
        row = self.checkboxes.index(self.sender())
        if self.checkboxes[row].isChecked():
            self.calendar_table.cellWidget(row, 1).setEnabled(True)
            self.calendar_table.cellWidget(row, 2).setEnabled(True)
        else:
            self.calendar_table.cellWidget(row, 1).setDisabled(True)
            self.calendar_table.cellWidget(row, 2).setDisabled(True)

    def closeEvent(self, a0: QCloseEvent) -> None:
        """
        Сигнал закрытия окна для обновления каникул
        @param a0: событие, которое вызвало закрытие окна
        """
        output = dict()
        fail_saving = False
        for row in range(self.holidays_cnt):
            output[str(row)] = dict()
            output[str(row)]['enabled'] = self.checkboxes[row].isChecked()
            output[str(row)]['start'] = self.calendar_table.cellWidget(row, 1).text()
            output[str(row)]['end'] = self.calendar_table.cellWidget(row, 2).text()
            if (self.checkboxes[row].isChecked() and
                    datetime.strptime(output[str(row)]['start'], '%d.%m.%Y') >
                    datetime.strptime(output[str(row)]['end'], '%d.%m.%Y')):
                if not fail_saving:
                    error_row = row
                fail_saving = True
        tempfile = run_command('mktemp').strip()
        with open(tempfile, 'w', encoding='utf-8') as file:
            file.write(dumps(output, indent=4, ensure_ascii=False))
        diffs = run_command(f'diff {tempfile} {config_path}/holidays.json').strip()
        if diffs == '':
            self.close()
        else:
            ret = QMessageBox.question(self, 'Внимание!', f"Сохранить изменения?",
                                       QMessageBox.Yes | QMessageBox.No | QMessageBox.Cancel)

            if ret == QMessageBox.Cancel:
                a0.ignore()
            elif ret == QMessageBox.No:
                self.close()
                self.parent.holidays_editing_window = None
            elif ret == QMessageBox.Yes:
                if fail_saving:
                    dlg = QMessageBox()
                    dlg.setWindowTitle('Внимание!')
                    dlg.setText(f'Невозможно сохранить каникулы: в строке {error_row + 1} каникулы начинаются позже, '
                                f'чем заканчиваются!')
                    dlg.exec()
                    logging.info(f"Каникулы не сохранены: "
                                 f"{output[str(error_row)]['start']} > {output[str(error_row)]['end']}")
                    a0.ignore()
                else:
                    logging.info(
                        f'Сохранение расписаний в файл, обновление списка активных расписаний, '
                        'закрытие окна редактирования расписаний...')
                    self.save_holidays()
                    self.close()

    def initUI(self) -> None:
        """
        Инициализация графического интерфейса
        """
        grid = QGridLayout()
        self.setLayout(grid)

        self.calendar_table = QTableWidget()
        self.holidays_cnt = 4
        self.calendar_table.setColumnCount(3)
        self.calendar_table.setRowCount(self.holidays_cnt)
        calendar_table_headers = ['Включить', 'Дата начала', 'Дата окончания']
        self.calendar_table.setHorizontalHeaderLabels(calendar_table_headers)
        self.calendar_table.horizontalHeader().setSectionResizeMode(QHeaderView.Stretch)
        self.calendar_table.setColumnWidth(0, 238)
        self.calendar_table.cellClicked.connect(self.cell_was_clicked)

        self.checkboxes = []

        for row in range(self.holidays_cnt):
            cell_widget = QWidget()
            cell_checkbox = QCheckBox()

            cell_checkbox.clicked.connect(self.checkbox_was_switched)
            self.checkboxes.append(cell_checkbox)
            cell_layout = QHBoxLayout(cell_widget)
            cell_layout.addWidget(cell_checkbox)
            cell_layout.setAlignment(Qt.AlignCenter)
            cell_layout.setContentsMargins(0, 0, 0, 0)
            cell_widget.setLayout(cell_layout)

            self.calendar_table.setCellWidget(row, 0, cell_widget)
            start_holidays = QDateEdit(calendarPopup=True)
            end_holidays = QDateEdit(calendarPopup=True)
            start_holidays.setDate(QDate.currentDate())
            end_holidays.setDate(QDate.currentDate())
            self.calendar_table.setCellWidget(row, 1, start_holidays)
            self.calendar_table.setCellWidget(row, 2, end_holidays)
            self.calendar_table.cellWidget(row, 1).setDisabled(True)
            self.calendar_table.cellWidget(row, 2).setDisabled(True)

        self.load_data()

        grid.addWidget(self.calendar_table, 0, 0, 1, 2)
        self.save_button = QPushButton('Сохранить')
        self.save_button.clicked.connect(self.save_holidays)
        grid.addWidget(self.save_button, 1, 0)

        self.cancel_button = QPushButton('Отмена')
        self.cancel_button.clicked.connect(lambda: self.close())
        grid.addWidget(self.cancel_button, 1, 1)

        self.setGeometry(300, 200, 600, 200)
        self.setWindowTitle('Редактирование каникул')
        self.show()
