import datetime
import json
import os
import shlex
import sys
import subprocess
import local_config as config

from PyQt5 import QtCore
from PyQt5 import QtWidgets
from PyQt5.QtCore import QRegExp
from PyQt5.QtGui import QIntValidator, QRegExpValidator
from PyQt5.QtWidgets import QApplication, QLabel, QLineEdit, QMainWindow, QMessageBox, QPushButton


config_template = '''# ERPNext related configs
ERPNEXT_API_KEY = '{0}'
ERPNEXT_API_SECRET = '{1}'
ERPNEXT_URL = '{2}'


# operational configs
PULL_FREQUENCY = {3} or 60 # in minutes
LOGS_DIRECTORY = 'logs' # logs of this script is stored in this directory
IMPORT_START_DATE = '{4}' or None # format: '20190501'

# Biometric device configs (all keys mandatory)
    #- device_id - must be unique, strictly alphanumerical chars only. no space allowed.
    #- ip - device IP Address
    #- punch_direction - 'IN'/'OUT'/'AUTO'/None
    #- clear_from_device_on_fetch: if set to true then attendance is deleted after fetch is successful.
    #(Caution: this feature can lead to data loss if used carelessly.)
devices = {5}

# Configs updating sync timestamp in the Shift Type DocType
shift_type_device_mapping = {6}
'''


class BiometricWindow(QMainWindow):
    def __init__(self):
        super().__init__()
        self.reg_exp_for_ip = r"((25[0-5]|2[0-4][0-9]|[01]?[0-9][0-9]?)\.){3}(25[0-5]|2[0-4][0-9]|[01]?[0-9][0-9]?)(?=\s*netmask)"
        self.init_ui()

    def closeEvent(self, event):
        can_exit = not hasattr(self, "p")
        if can_exit:
            event.accept()
        else:
            create_message_box(text="Window cannot be closed when \nservice is running!", title="Message", width=200)
            event.ignore()

    def init_ui(self):
        self.counter = 0
        self.setup_window()
        self.setup_textboxes_and_label()
        self.center()
        self.show()

    def setup_window(self):
        self.setFixedSize(470, 550)
        self.setWindowTitle('ERPNext Biometric Service')

    def setup_textboxes_and_label(self):

        self.create_label("API Secret", "api_secret", 20, 0, 200, 30)
        self.create_field("textbox_erpnext_api_secret", 20, 30, 200, 30)

        self.create_label("API Key", "api_key", 20, 60, 200, 30)
        self.create_field("textbox_erpnext_api_key", 20, 90, 200, 30)

        self.create_label("ERPNext URL", "erpnext_url", 20, 120, 200, 30)
        self.create_field("textbox_erpnext_url", 20, 150, 200, 30)

        self.create_label("Pull Frequency (in minutes)",
                          "pull_frequency", 250, 0, 200, 30)
        self.create_field("textbox_pull_frequency", 250, 30, 200, 30)

        self.create_label("Import Start Date",
                          "import_start_date", 250, 60, 200, 30)
        self.create_field("textbox_import_start_date", 250, 90, 200, 30)
        self.validate_data(r"^\d{1,2}/\d{1,2}/\d{4}$", "textbox_import_start_date")

        self.create_separator(210, 470)
        self.create_button('+', 'add', 390, 230, 35, 30, self.add_devices_fields)
        self.create_button('-', 'remove', 420, 230, 35, 30, self.remove_devices_fields)

        self.create_label("Device ID", "device_id", 20, 260, 0, 30)
        self.create_label("Device IP", "device_ip", 170, 260, 0, 30)
        self.create_label("Shift", "shift", 320, 260, 0, 0)

        # First Row for table
        self.create_field("device_id_0", 20, 290, 145, 30)
        self.create_field("device_ip_0", 165, 290, 145, 30)
        self.validate_data(self.reg_exp_for_ip, "device_ip_0")
        self.create_field("shift_0", 310, 290, 145, 30)

        # Actions buttons
        self.create_button('Set Configuration', 'set_conf', 20, 500, 130, 30, self.setup_local_config)
        self.create_button('Start Service', 'start_or_stop_service', 320, 500, 130, 30, self.integrate_biometric, enable=False)
        self.create_button('Running Status', 'running_status', 170, 500, 130, 30, self.get_running_status, enable=False)
        self.set_default_value_or_placeholder_of_field()

        # validating integer
        self.onlyInt = QIntValidator(10, 30)
        self.textbox_pull_frequency.setValidator(self.onlyInt)

    def set_default_value_or_placeholder_of_field(self):
        if os.path.exists("local_config.py"):
            import local_config as config
            self.textbox_erpnext_api_secret.setText(config.ERPNEXT_API_SECRET)
            self.textbox_erpnext_api_key.setText(config.ERPNEXT_API_KEY)
            self.textbox_erpnext_url.setText(config.ERPNEXT_URL)
            self.textbox_pull_frequency.setText(str(config.PULL_FREQUENCY))

            if len(config.devices):
                self.device_id_0.setText(config.devices[0]['device_id'])
                self.device_ip_0.setText(config.devices[0]['ip'])
                self.shift_0.setText(
                    config.shift_type_device_mapping[0]['shift_type_name'])

            if len(config.devices) > 1:
                for _ in range(self.counter, len(config.devices) - 1):
                    self.add_devices_fields()

                    device = getattr(self, 'device_id_' + str(self.counter))
                    ip = getattr(self, 'device_ip_' + str(self.counter))
                    shift = getattr(self, 'shift_' + str(self.counter))

                    device.setText(config.devices[self.counter]['device_id'])
                    ip.setText(config.devices[self.counter]['ip'])
                    shift.setText(config.shift_type_device_mapping[self.counter]['shift_type_name'])
        else:
            self.textbox_erpnext_api_secret.setPlaceholderText("c70ee57c7b3124c")
            self.textbox_erpnext_api_key.setPlaceholderText("fb37y8fd4uh8ac")
            self.textbox_erpnext_url.setPlaceholderText("example.erpnext.com")
            self.textbox_pull_frequency.setPlaceholderText("60")

        self.textbox_import_start_date.setPlaceholderText("DD/MM/YYYY")

    # Widgets Genrators
    def create_label(self, label_text, label_name, x, y, height, width):
        setattr(self,  label_name, QLabel(self))
        label = getattr(self, label_name)
        label.move(x, y)
        label.setText(label_text)
        if height and width:
            label.resize(height, width)
        label.show()

    def create_field(self, field_name, x, y, height, width):
        setattr(self,  field_name, QLineEdit(self))
        field = getattr(self, field_name)
        field.move(x, y)
        field.resize(height, width)
        field.show()

    def create_separator(self, y, width):
        setattr(self, 'separator', QLineEdit(self))
        field = getattr(self, 'separator')
        field.move(0, y)
        field.resize(width, 5)
        field.setEnabled(False)
        field.show()

    def create_button(self, button_label, button_name, x, y, height, width, callback_function, enable=True):
        setattr(self,  button_name, QPushButton(button_label, self))
        button = getattr(self, button_name)
        button.move(x, y)
        button.resize(height, width)
        button.clicked.connect(callback_function)
        button.setEnabled(enable)

    def center(self):
        frame = self.frameGeometry()
        screen = QApplication.desktop().screenNumber(QApplication.desktop().cursor().pos())
        centerPoint = QApplication.desktop().screenGeometry(screen).center()
        frame.moveCenter(centerPoint)
        self.move(frame.topLeft())

    def add_devices_fields(self):
        if self.counter < 5:
            self.counter += 1
            self.create_field("device_id_" + str(self.counter), 20, 290+(self.counter * 30), 145, 30)
            self.create_field("device_ip_" + str(self.counter), 165, 290+(self.counter * 30), 145, 30)
            self.validate_data(self.reg_exp_for_ip, "device_ip_" + str(self.counter))
            self.create_field("shift_" + str(self.counter), 310, 290+(self.counter * 30), 145, 30)

    def validate_data(self, reg_exp, field_name):
        field = getattr(self, field_name)
        reg_ex = QRegExp(reg_exp)
        input_validator = QRegExpValidator(reg_ex, field)
        field.setValidator(input_validator)

    def remove_devices_fields(self):
        if self.counter > 0:
            b = getattr(self, "shift_" + str(self.counter))
            b.deleteLater()
            b = getattr(self, "device_id_" + str(self.counter))
            b.deleteLater()
            b = getattr(self, "device_ip_" + str(self.counter))
            b.deleteLater()

            self.counter -= 1

    def integrate_biometric(self):
        button = getattr(self, "start_or_stop_service")

        if not hasattr(self, 'p'):
            print("Starting Service...")
            command = shlex.split('python -c "from erpnext_sync import infinite_loop; infinite_loop()"')
            self.p = subprocess.Popen(command, stdout=subprocess.PIPE)
            print("Process running at {}".format(self.p.pid))
            button.setText("Stop Service")
            create_message_box("Service status", "Service has been started")
            self.create_label(str(datetime.datetime.now()), "service_start_time", 20, 60, 200, 30)
            self.service_start_time.setHidden(True)
            getattr(self, 'running_status').setEnabled(True)
        else:
            print("Stopping Service...")
            self.p.kill()
            del self.p
            button.setText("Start Service")
            create_message_box("Service status", "Service has been stoped")
            getattr(self, 'running_status').setEnabled(False)

    def setup_local_config(self):
        bio_config = self.get_local_config()

        print("Setting Local Configuration...")

        if not bio_config:
            print("Local Configuration not updated...")
            return 0

        if os.path.exists("local_config.py"):
            os.remove("local_config.py")

        with open("local_config.py", 'w+') as f:
            f.write(bio_config)

        print("Local Configuration Updated.")

        create_message_box("Message", "Configuration Updated!\nClick on Start Service.")

        getattr(self, 'start_or_stop_service').setEnabled(True)

    def get_device_details(self):
        device = {}
        devices = []
        shifts = []

        for idx in range(0, self.counter+1):
            shift = getattr(self, "shift_" + str(idx)).text()
            device_id = getattr(self, "device_id_" + str(idx)).text()
            devices.append({
                'device_id': device_id,
                'ip': getattr(self, "device_ip_" + str(idx)).text(),
                'punch_direction': '',
                'clear_from_device_on_fetch': ''
            })
            if shift in device:
                device[shift].append(device_id)
            else:
                device[shift]=[device_id]
        
        for shift_type_name in device.keys():
            shifts.append({
                'shift_type_name': shift_type_name,
                'related_device_id': device[shift_type_name]
            })
        return devices, shifts

    def get_local_config(self):
        if not validate_fields(self):
            return 0
        string = self.textbox_import_start_date.text()
        formated_date = "".join([ele for ele in reversed(string.split("/"))])

        devices, shifts = self.get_device_details()
        return config_template.format(self.textbox_erpnext_api_key.text(), self.textbox_erpnext_api_secret.text(), self.textbox_erpnext_url.text(), self.textbox_pull_frequency.text(), formated_date, json.dumps(devices), json.dumps(shifts))

    def get_running_status(self):
        running_status = []
        with open('/'.join([config.LOGS_DIRECTORY])+'/logs.log', 'r') as f:
            index = 0
            for idx, line in enumerate(f,1):
                logdate = convert_into_date(line.split(',')[0], '%Y-%m-%d %H:%M:%S')
                if logdate and logdate >= convert_into_date(self.service_start_time.text().split('.')[0] , '%Y-%m-%d %H:%M:%S'):
                    index = idx
                    break
            if index:
                running_status.extend(read_file_contents('logs',index))

        with open('/'.join([config.LOGS_DIRECTORY])+'/error.log', 'r') as fread:
            error_index = 0
            for error_idx, error_line in enumerate(fread,1):
                start_date = convert_into_date(self.service_start_time.text().split('.')[0] , '%Y-%m-%d %H:%M:%S')
                if start_date and start_date.strftime('%Y-%m-%d') in error_line:
                    error_logdate = convert_into_date(error_line.split(',')[0], '%Y-%m-%d %H:%M:%S')
                    if error_logdate and error_logdate >= start_date:
                        error_index = error_idx
                        break
            if error_index:
                running_status.extend(read_file_contents('error',error_index))

        if running_status:
            create_message_box("Running status", ''.join(running_status))
        else:
            create_message_box("Running status", 'Process not yet started')

def read_file_contents(file_name, index):
    running_status = []
    with open('/'.join([config.LOGS_DIRECTORY])+f'/{file_name}.log', 'r') as file_handler:
        for idx, line in enumerate(file_handler,1):
            if idx>=index:
                running_status.append(line)
    return running_status


def validate_fields(self):
    def message(text):
        create_message_box("Missing Value", "Please Set {}".format(text), "warning")

    if not self.textbox_erpnext_api_key.text():
        return message("API Key")

    if not self.textbox_erpnext_api_secret.text():
        return message("API Secret")

    if not self.textbox_erpnext_url.text():
        return message("ERPNext URL")

    if not self.textbox_import_start_date.text():
        return message("Import Start Date")

    return validate_date(self.textbox_import_start_date.text())


def validate_date(date):
    try:
        datetime.datetime.strptime(date, '%d/%m/%Y')
        return True
    except ValueError:
        create_message_box("", "Please Enter Date in correct format", "warning", width=200)
        return False


def convert_into_date(datestring, pattern):
    try:
        return datetime.datetime.strptime(datestring, pattern)
    except:
        return None


def create_message_box(title, text, icon="information", width=150):
    msg = QMessageBox()
    msg.setWindowTitle(title)
    lineCnt = len(text.split('\n'))
    if lineCnt > 15:
        scroll = QtWidgets.QScrollArea()
        scroll.setWidgetResizable(1)
        content = QtWidgets.QWidget()
        scroll.setWidget(content)
        layout = QtWidgets.QVBoxLayout(content)
        tmpLabel = QtWidgets.QLabel(text)
        tmpLabel.setTextInteractionFlags(QtCore.Qt.TextSelectableByMouse)
        layout.addWidget(tmpLabel)
        msg.layout().addWidget(scroll, 12, 10, 1, msg.layout().columnCount())
        msg.setStyleSheet("QScrollArea{min-width:550 px; min-height: 400px}")
    else:
        msg.setText(text)
        if icon == "warning":
            msg.setIcon(QtWidgets.QMessageBox.Warning)
            msg.setStyleSheet("QMessageBox Warning{min-width: 50 px;}")
        else:
            msg.setIcon(QtWidgets.QMessageBox.Information)
            msg.setStyleSheet("QMessageBox Information{min-width: 50 px;}")
        msg.setStyleSheet("QmessageBox QLabel{min-width: "+str(width)+"px;}")
    msg.exec_()


def setup_window():
    biometric_app = QApplication(sys.argv)
    biometric_window = BiometricWindow()
    biometric_app.exec_()


if __name__ == "__main__":
    setup_window()