from datetime import datetime as dt
from uuid import getnode as get_mac
from subprocess import Popen, PIPE, CREATE_NEW_PROCESS_GROUP
from threading import Thread
import signal
import ctypes
import socket
import time
import os

from RobotsOrcAPI import RobotsOrchestratorAPI
from TaskOrcAPI import TaskOrchestratorAPI

class RobotsOrchestratorFramework():
    def __init__(self, RobotGUID, host, headers, settings_path, config_path, path_scripts, should_logoff=True):
        self.robots_api = RobotsOrchestratorAPI(RobotGUID, host, headers)
        self.orc_api = TaskOrchestratorAPI(RobotGUID, host, headers)

        self.settings_path = settings_path
        self.path_scripts = path_scripts
        self.config_path = config_path
        self.should_logoff = should_logoff
        self.RobotGUID = RobotGUID

        self.path_logs = f"{config_path}\PythonFrameworkLog.txt"

        self.req_param = {
            'IP': socket.gethostbyname(socket.getfqdn()),
            'MAC': get_mac(), 'Status': 2,
            'JobGUID': "", 'JobName': "",
            'JobType': 1, 'RobotGUID': self.RobotGUID,
            'Machine': socket.gethostname()
        }

        self.status_decoding = {
            'created': 0, 'pending': 1, 'aborting': 2, 'aborted': 3, 
            'success': 4, 'failed': 5, 'stopping': 6, 'stopped': 7, 
            'InProgress': 8
        }

        self.req_heartbeat_thread_stop = False
        self.req_job_thread_stop = False
        self.python_robot_work = True
        self.status_work = False
    
    # Писать логи
    def logs(self, message):
        if not os.path.exists(self.path_logs):
            with open(self.path_logs, "w", encoding="utf-8") as file: file.write(message)
            return

        with open(self.path_logs, "a", encoding="utf-8") as file: file.write(f"\n{message}")
 
    # Действия коде ошибки 10
    def HeartbeatErrorCode10(self, JobGUID, script_name):
        self.python_robot_work = False
        self.logs(f"Обнаружен запрос на прерывание, прерываю робота ({script_name})")
        
        self.robots_api.request_update(JobGUID, self.status_decoding['aborting'])
        self.current_process.kill()
        self.robots_api.request_update(JobGUID, self.status_decoding['aborted'])
    
    # Действия при коде ошибки 20
    def HeartbeatErrorCode20(self, JobGUID, script_name):
        self.python_robot_work = False
        self.logs(f"Обнаружен запрос на остановку, останавливаю робота ({script_name})")

        self.robots_api.request_update(JobGUID, self.status_decoding['stopping'])
        while 1:
            if self.current_process.poll() is not None: break
            self.current_process.send_signal(signal.CTRL_BREAK_EVENT)
        self.robots_api.request_update(JobGUID, self.status_decoding['stopped'])

    # Обработка вывода для heartbeat
    def req_heartbeat_output_processing(self):
        script_name = self.req_param['JobName']
        JobGUID = self.req_param['JobGUID']
        ErrorCode = self.heartbeat_results['HeartbeatErrorCode']
        HasNewJob = self.heartbeat_results['HasNewJob']

        if ErrorCode == 10: self.HeartbeatErrorCode10(JobGUID, script_name)
        if ErrorCode == 20: self.HeartbeatErrorCode20(JobGUID, script_name)
        
        if HasNewJob == 0:
            if self.status_work: return
            
            self.req_param["Status"] = 0
            self.robots_api.request_heartbeat(self.req_param)
           
            if self.should_logoff:
                self.logs(f"Нет новой работы, выхожу из системы")
                ctypes.windll.user32.ExitWindowsEx(0, 1)
                return 'break'
            
            self.logs(f"Нет новой работы, выключаю фреймворк")
            self.stop_framework()
            return 'break'
        
    # Запуск heartbeat
    def req_heartbeat_thread(self):
        while not self.req_heartbeat_thread_stop:
            time.sleep(0.1)

            self.heartbeat_time_start = dt.today()
            self.heartbeat_results = self.robots_api.request_heartbeat(self.req_param)
            print(f'HeartbeatErrorCode: {self.heartbeat_results["HeartbeatErrorCode"]}, ErrorText: {self.heartbeat_results["ErrorText"]}, HasNewJob: {self.heartbeat_results["HasNewJob"]}')
            self.heartbeat_time_stop = (dt.today() - self.heartbeat_time_start)

            self.robot_status = 3
            if self.req_heartbeat_output_processing() == "break": break
            
            timeout = int(self.heartbeat_time_stop.seconds)
            if timeout < 10: time.sleep(10 - int(self.heartbeat_time_stop.seconds))

    # Проверка статуса робота
    def python_robot_check_status(self, JobGUID, script_name):
        self.logs(f"Ожидаю окончания работы робота")
        status, error = self.current_process.communicate()
        if not self.python_robot_work: return

        if self.current_process.returncode != 0:
            self.orc_api.add_new_log_entry(
                self.job_param["process_version"]['GUID'], self.job_param['JobGUID'],
                "Error", f"{script_name} error {error.decode('UTF-8')}"
            )
            self.robots_api.request_update(JobGUID, self.status_decoding['failed'])
            self.logs(f"Робот ({script_name}) закончил работу со статусом (Failed),\nОшибка кода робота: ({error.decode('UTF-8')})")
            return
        
        self.robots_api.request_update(JobGUID, self.status_decoding['success'])
        self.logs(f"Робот закончил работу со статусом (success)")
    
    # Запускаю python робота, обновляю статусы и параметры
    def python_robot(self):
        self.logs(f"Взял задачу ({self.job_param['JobName']}) ({self.job_param['JobGUID']})")

        script_name = self.job_param['JobName']
        JobGUID = self.job_param['JobGUID']
        path_script = f'{self.path_scripts}\{script_name}.py'
        
        self.req_param['JobGUID'] = self.job_param['JobGUID']
        self.req_param['JobName'] = self.job_param['JobName']
        
        if not os.path.isfile(path_script):
            self.logs(f"Робот ({script_name}) закончил работу со статусом (Failed),\nНе обнаружен путь к роботу ({path_script})")
            return self.robots_api.request_update(JobGUID, self.status_decoding['failed'])

        self.logs(f"Запускаю python робота ({script_name}.py), по пути ({path_script})")
        cmd_argv = f"{self.job_param['process_version']['GUID']}|{JobGUID}|{self.settings_path}"

        self.current_process = Popen(
            ["python", path_script, cmd_argv], creationflags=CREATE_NEW_PROCESS_GROUP,
            stdout=PIPE, stderr=PIPE
        )
        
        self.robots_api.request_update(JobGUID, self.status_decoding['InProgress'])
        self.python_robot_check_status(JobGUID, script_name)

        self.logs(f"Задача выполнена ищу следующую")

    # Работа с job
    def req_job_thread(self):    
        while not self.req_job_thread_stop:
            time.sleep(0.1)
            
            try: self.heartbeat_results['HasNewJob']
            except AttributeError: continue

            if self.heartbeat_results['HasNewJob'] == 1:
                self.status_work = True
                self.job_param = (
                    self.robots_api.request_consumeNextJob(self.req_param["Status"])
                )
                
                if type(self.job_param) != str: self.python_robot()
            
            self.status_work = False
            self.req_param["Status"] = 2

    def stop_framework(self):
        self.req_heartbeat_thread_stop = True
        self.req_job_thread_stop = True
    
    def start_thread(self):
        self.logs("Запускаю потоки робота")

        self.THREAD_REQ_HEARTBEAT = Thread(target = self.req_heartbeat_thread)
        self.THREAD_REQ_JOB = Thread(target = self.req_job_thread)

        self.THREAD_REQ_HEARTBEAT.start()
        self.THREAD_REQ_JOB.start()

        self.logs("Запустил потоки робота")

        self.THREAD_REQ_HEARTBEAT.join()
        self.THREAD_REQ_JOB.join()

        self.logs("Потоки робота отработали")

