#!/usr/bin/python3
"""
Email-Reminder Editor - edit special occasion reminders

Copyright (C) 2023-2025 by Francois Marier

This program is free software: you can redistribute it and/or modify
it under the terms of the GNU Affero General Public License as published by
the Free Software Foundation, either version 3 of the License, or
(at your option) any later version.

This program is distributed in the hope that it will be useful,
but WITHOUT ANY WARRANTY; without even the implied warranty of
MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
GNU Affero General Public License for more details.

You should have received a copy of the GNU Affero General Public License
along with this program.  If not, see <https://www.gnu.org/licenses/>.
"""

import argparse
import os
import pwd
import subprocess
import sys
from pathlib import Path

from PySide2.QtCore import QModelIndex, Qt
from PySide2.QtGui import QIcon, QKeySequence
from PySide2.QtWidgets import (QAbstractItemView, QApplication, QCheckBox,
                               QDialog, QHBoxLayout, QLabel, QLineEdit,
                               QMainWindow, QMenuBar, QMessageBox, QPushButton,
                               QSpinBox, QTableView, QTabWidget, QToolBar,
                               QVBoxLayout)

from EmailReminder.EventList import EventList

APP = QApplication(sys.argv)

CONFIG_FILE = Path.home() / '.config' / 'email-reminder' / 'reminders.xml'
LEGACY_CONFIG_FILE = Path.home() / '.email-reminders'
SEND_REMINDERS = '/usr/bin/send-reminders'
VERSION = '1.0.1'  # source of truth for the version number everywhere


def get_user_config_file_path(verbose):
    if CONFIG_FILE.exists():
        return CONFIG_FILE
    if LEGACY_CONFIG_FILE.exists():
        return LEGACY_CONFIG_FILE

    if verbose:
        print("No events configuration found in legacy or preferred location.")

    try:
        CONFIG_FILE.parent.mkdir(parents=True, exist_ok=True)
    except (OSError, PermissionError) as e:
        print(f"Failed to create config directory: {e}. "
              "Using legacy config file.")
        return LEGACY_CONFIG_FILE
    return CONFIG_FILE


class EventTab:
    def __init__(self, model):
        self.model = model
        self.table = QTableView()
        self.table.setModel(model)
        self.table.setShowGrid(False)
        self.table.verticalHeader().setVisible(False)
        self.table.horizontalHeader().setStretchLastSection(True)
        self.table.setCornerButtonEnabled(False)
        self.table.setSelectionBehavior(QTableView.SelectRows)
        self.table.setSelectionMode(QAbstractItemView.SingleSelection)
        self.table.setSortingEnabled(False)  # TODO: enable this (bug 724121)

    def getTable(self):
        return self.table

    def addEvent(self):
        if self.model.append():
            last = self.model.rowCount() - 1
            index = self.model.index(last, 0)
            self.table.setCurrentIndex(index)
            self.table.scrollToBottom()

    def deleteSelectedEvent(self):
        indexes = self.table.selectionModel().selectedIndexes()
        if indexes and indexes[0].isValid():
            if self.model.delete(indexes[0].row()):
                self.table.setCurrentIndex(QModelIndex())

    def getSelectedEventReminders(self):
        indexes = self.table.selectionModel().selectedIndexes()
        if indexes and indexes[0].isValid():
            return self.model.getReminders(indexes[0].row())
        return None

    def setSelectedEventReminders(self, values):
        indexes = self.table.selectionModel().selectedIndexes()
        if indexes and indexes[0].isValid():
            self.model.setReminders(indexes[0].row(), values)


class TabWidget(QTabWidget):
    def __init__(self, *args, **kwargs):
        super().__init__(*args, **kwargs)

        self.eventTabs = []

    def append(self, model):
        event = EventTab(model)
        self.eventTabs.append(event)
        self.addTab(event.getTable(), model.LABEL)

    def addEvent(self):
        self.eventTabs[self.currentIndex()].addEvent()

    def deleteEvent(self):
        self.eventTabs[self.currentIndex()].deleteSelectedEvent()

    def getEventReminders(self):
        return self.eventTabs[self.currentIndex()].getSelectedEventReminders()

    def setEventReminders(self, values):
        self.eventTabs[self.currentIndex()].setSelectedEventReminders(values)


class EditRemindersDialog(QDialog):

    def __init__(self, tabs, *args, **kwargs):
        super().__init__(*args, **kwargs)

        self.tabs = tabs

        self.setWindowTitle("Edit reminders")

        self.event_name_label = QLabel("Current reminders for '':")

        self.same_day_checkbox = QCheckBox("&Same day")
        self.same_day_checkbox.setChecked(True)

        days_in_advance_layout = QHBoxLayout()
        self.days_in_advance_checkbox = QCheckBox("&Days in advance:")
        self.days_in_advance_spinbox = QSpinBox()
        self.days_in_advance_spinbox.setRange(1, 364)
        self.days_in_advance_spinbox.setSingleStep(1)
        self.days_in_advance_spinbox.setValue(1)
        self.days_in_advance_spinbox.setDisabled(True)
        self.days_in_advance_checkbox.stateChanged.connect(self.toggleSpinBox)
        days_in_advance_layout.addWidget(self.days_in_advance_checkbox)
        days_in_advance_layout.addWidget(self.days_in_advance_spinbox)

        close_layout = QHBoxLayout()
        close_button = QPushButton("Close")
        close_button.clicked.connect(self.accept)
        close_button.setDefault(True)
        close_layout.addStretch()
        close_layout.addWidget(close_button)

        layout = QVBoxLayout()
        layout.addWidget(self.event_name_label)
        layout.addWidget(self.same_day_checkbox)
        layout.addLayout(days_in_advance_layout)
        layout.addLayout(close_layout)
        self.setLayout(layout)

        self.accepted.connect(self.save)

    def toggleSpinBox(self):
        self.days_in_advance_spinbox.setDisabled(
            not self.days_in_advance_checkbox.isChecked())

    def load(self):
        reminders = self.tabs.getEventReminders()
        if reminders:
            self.event_name_label.setText(
                f"Current reminders for '{reminders['name']}':")
            self.same_day_checkbox.setChecked(reminders['sameday'])
            self.days_in_advance_checkbox.setChecked(
                reminders['daysinadvance'] > 0)
            self.days_in_advance_spinbox.setValue(reminders['daysinadvance'])
        return reminders is not None

    def save(self):
        values = {'sameday': self.same_day_checkbox.isChecked(),
                  'daysinadvance': 0}
        if self.days_in_advance_checkbox.isChecked():
            values['daysinadvance'] = self.days_in_advance_spinbox.value()
        self.tabs.setEventReminders(values)


class PreferencesDialog(QDialog):

    def __init__(self, events, *args, **kwargs):
        super().__init__(*args, **kwargs)

        self.events = events

        self.setWindowTitle("Preferences")

        name_layout = QHBoxLayout()
        self.first_name_edit = QLineEdit()
        self.last_name_edit = QLineEdit()
        name_layout.addWidget(QLabel("Name:"))
        name_layout.addWidget(self.first_name_edit)
        name_layout.addWidget(self.last_name_edit)

        email_layout = QHBoxLayout()
        self.email_edit = QLineEdit()
        email_layout.addWidget(QLabel("Email:"))
        email_layout.addWidget(self.email_edit)

        close_layout = QHBoxLayout()
        close_button = QPushButton("Close")
        close_button.clicked.connect(self.accept)
        close_button.setDefault(True)
        close_layout.addStretch()
        close_layout.addWidget(close_button)

        layout = QVBoxLayout()
        layout.addWidget(
            QLabel("Set the default recipient for the reminder emails:"))
        layout.addLayout(name_layout)
        layout.addLayout(email_layout)
        layout.addLayout(close_layout)
        self.setLayout(layout)

        self.accepted.connect(self.save)

    def load(self):
        user = self.events.getUser()
        if not user['first_name'] and not user['last_name']:
            # Default to the full name of the OS account.
            unixName = pwd.getpwuid(os.getuid())[4].split(',')[0]
            user['first_name'] = unixName.split(' ')[0]
            user['last_name'] = unixName.split(' ')[1]
            self.events.setUser({
                'first_name': user['first_name'],
                'last_name': user['last_name'],
            })

        if not user['email']:
            user['email'] = ''
            if 'EMAIL' in os.environ:
                user['email'] = os.environ['EMAIL']
            self.events.setUser({
                'email': user['email'],
            })

        self.first_name_edit.setText(user['first_name'])
        self.last_name_edit.setText(user['last_name'])
        self.email_edit.setText(user['email'])

    def save(self):
        self.events.setUser({
            'first_name': self.first_name_edit.text(),
            'last_name': self.last_name_edit.text(),
            'email': self.email_edit.text(),
        })


class MainWindow(QMainWindow):

    def __init__(self, events, *args, **kwargs):
        super().__init__(*args, **kwargs)

        self.events = events

        self.setWindowTitle("Email-Reminder Editor")

        self.tabs = TabWidget()
        for eventType in EventList.SUPPORTED_TYPES:
            self.tabs.append(events.model(eventType))
        self.tabs.setDocumentMode(True)
        self.setCentralWidget(self.tabs)

        self.addToolBar(MainToolbar(self, self.tabs))
        self.setMenuBar(MainMenu(self, self.events, self.tabs))

        self.preferences_dialog = PreferencesDialog(self.events)
        self.edit_reminders_dialog = EditRemindersDialog(self.tabs)

    def show_preferences(self):
        self.preferences_dialog.load()
        self.preferences_dialog.exec_()

    def show_edit_reminders(self):
        if self.edit_reminders_dialog.load():
            self.edit_reminders_dialog.exec_()

    def closeEvent(self, event):
        user = self.events.getUser()
        if user['email']:
            event.accept()
            return

        # Warn the user about a missing email address.
        confirmation_box = QMessageBox()
        confirmation_box.setText("You will not receive any reminders since "
                                 "you have not set your email address.")
        confirmation_box.setInformativeText("Would you like to set your email "
                                            "address in the preferences now "
                                            "or quit?")
        confirmation_box.setIcon(QMessageBox.Question)
        confirmation_box.addButton("Open Preferences", QMessageBox.YesRole)
        confirmation_box.addButton("Quit", QMessageBox.NoRole)

        if confirmation_box.exec():
            event.accept()
        else:
            event.ignore()
            self.show_preferences()


class MainToolbar(QToolBar):

    def __init__(self, window, tabs, *args, **kwargs):
        super().__init__(*args, **kwargs)

        self.setMovable(False)
        self.setFloatable(False)
        self.setContextMenuPolicy(Qt.PreventContextMenu)

        # Maintain the original look-and-feel.
        self.setToolButtonStyle(Qt.ToolButtonTextBesideIcon)

        new_event_action = self.addAction(QIcon.fromTheme('document-new'),
                                          'New')
        new_event_action.triggered.connect(tabs.addEvent)
        delete_event_action = self.addAction(QIcon.fromTheme('edit-delete'),
                                             'Delete')
        delete_event_action.triggered.connect(tabs.deleteEvent)
        self.addSeparator()
        edit_event_action = self.addAction('Edit reminders')
        edit_event_action.triggered.connect(window.show_edit_reminders)


class MainMenu(QMenuBar):

    def __init__(self, window, events, tabs, *args, **kwargs):
        super().__init__(*args, **kwargs)

        self.events = events

        self.setNativeMenuBar(True)

        file_menu = self.addMenu('File')
        test_config_action = file_menu.addAction("Test configuration")
        test_config_action.triggered.connect(self.runTest)

        file_menu.addSeparator()

        quit_action = file_menu.addAction("Quit")
        quit_action.setShortcut(QKeySequence.Quit)
        quit_action.triggered.connect(window.close)

        edit_menu = self.addMenu('Edit')
        new_event_action = edit_menu.addAction("New event")
        new_event_action.setShortcut(QKeySequence.New)
        new_event_action.triggered.connect(tabs.addEvent)
        delete_event_action = edit_menu.addAction("Delete event")
        delete_event_action.setShortcut(QKeySequence.Delete)
        delete_event_action.triggered.connect(tabs.deleteEvent)
        edit_event_action = edit_menu.addAction("Edit reminders...")
        edit_event_action.setShortcut(QKeySequence("Ctrl+e"))
        edit_event_action.triggered.connect(window.show_edit_reminders)
        edit_menu.addSeparator()
        preferences_event_action = edit_menu.addAction("Preferences...")
        preferences_event_action.setShortcut(QKeySequence.Preferences)
        preferences_event_action.triggered.connect(window.show_preferences)

    def runTest(self):
        warning_box = QMessageBox()
        warning_box.setIcon(QMessageBox.Warning)
        if not os.access(SEND_REMINDERS, os.X_OK):
            warning_box.setText(f"Cannot run '{SEND_REMINDERS}'.")
            warning_box.setInformativeText("Check your installation.")
            warning_box.exec()
            return

        self.events.save()

        result = subprocess.run([SEND_REMINDERS], stdout=subprocess.PIPE,
                                stderr=subprocess.STDOUT, check=False)
        if result.stdout:
            warning_box.setText(f"Error while running '{SEND_REMINDERS}':")
            warning_box.setInformativeText(result.stdout.decode('utf-8'))
            warning_box.exec()


def main():
    parser = argparse.ArgumentParser(description="Simple editor for modifying "
                                     "special occasion email reminders")
    parser.add_argument('-s', '--simulate', dest='simulate',
                        action='store_true', help="don't save any changes")
    parser.add_argument('-v', '--verbose', dest='verbose',
                        action='store_true', help="print more information")
    parser.add_argument('-V', '--version', action='version',
                        version=f'Email-Reminder Editor {VERSION}')
    args = parser.parse_args()

    events = EventList(args.simulate, args.verbose)
    if not events.load(get_user_config_file_path(args.verbose)):
        return 1

    QIcon.setThemeName('gnome')  # TODO: remove (icon+name toolbar buttons)
    window = MainWindow(events)
    window.show()
    status = APP.exec_()
    events.save()
    return status


sys.exit(main())
