#!/usr/bin/env python3
# -*- coding: utf-8 -*-
#
# Copyright (c) 2024 Qindel Formación y Servicios S.L.
# All rights reserved.
#
# Redistribution and use in source and binary forms, with or without modification,
# are permitted provided that the following conditions are met:
#
#    * Redistributions of source code must retain the above copyright notice,
#      this list of conditions and the following disclaimer.
#    * Redistributions in binary form must reproduce the above copyright notice,
#      this list of conditions and the following disclaimer in the documentation
#      and/or other materials provided with the distribution.
#    * Neither the name of Virtual Cable S.L. nor the names of its contributors
#      may be used to endorse or promote products derived from this software
#      without specific prior written permission.
#
# THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS"
# AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE
# IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE
# DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT HOLDER OR CONTRIBUTORS BE LIABLE
# FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL
# DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR
# SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER
# CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY,
# OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE
# OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
"""
@author: Natalia Serrano, nserrano at qindel dot com
"""

import base64
import os
from pathlib import Path

from opengnsys.log import logger
from opengnsys.workers import ogLiveWorker

class CloningEngineWorker (ogLiveWorker):
    name = 'CloningEngine'  # Module name
    REST = None             # REST object

    def onActivation (self):
        super().onActivation (run_monitoring_thread=False)
        logger.info ('onActivation ok')

    def onDeactivation (self):
        logger.debug ('onDeactivation')

    def InventariandoSoftware (self, dsk, par, nfn):
        sft_src = f'/tmp/CSft-{self.IPlocal}-{par}'
        try:
            self.interfaceAdmin (nfn, [dsk, par, sft_src])
            herror = 0
        except:
            herror = 1

        if herror:
            logger.warning ('Error al ejecutar el comando')
            b64 = ''
            self.muestraMensaje (20)
        else:
            if not os.path.exists (sft_src):
                raise Exception (f'interfaceAdmin({nfn}) returned success but did not create file ({sft_src})')
            sft_src_contents = Path (sft_src).read_bytes()

            b64 = base64.b64encode (sft_src_contents).decode ('utf-8')
            self.muestraMensaje (19)

        cmd = {
            'nfn': 'RESPUESTA_InventarioSoftware',
            'dsk': dsk,         ## not in the original C code, around ogAdmClient.c:1944
            'par': par,
            'contents': b64,
        }
        return self.respuestaEjecucionComando (cmd, herror, 0)

    def do_CrearImagen (self, post_params):
        for k in ['dsk', 'par', 'cpt', 'idi', 'nci', 'ipr', 'nfn', 'ids']:
            if k not in post_params:
                logger.error (f'required parameter ({k}) not in POST params')
                return {}

        dsk = post_params['dsk'] ## Disco
        par = post_params['par'] ## Número de partición
        cpt = post_params['cpt'] ## Código de la partición
        idi = post_params['idi'] ## Identificador de la imagen
        nci = post_params['nci'] ## Nombre canónico de la imagen
        ipr = post_params['ipr'] ## Ip del repositorio
        nfn = post_params['nfn']
        ids = post_params['ids']

        self.muestraMensaje (7)

        try:
            res = self.InventariandoSoftware (dsk, par, 'InventarioSoftware')   ## Crea inventario Software previamente
        except:
            logger.warning ('Error al ejecutar el comando')
            return {}

        if res['contents']:
            self.muestraMensaje (2)
            inv_sft = res['contents']
            try:
                self.interfaceAdmin (nfn, [dsk, par, nci, ipr])
                self.muestraMensaje (9)
                herror = 0
            except:
                logger.warning ('Error al ejecutar el comando')
                self.muestraMensaje (10)
                herror = 1
        else:
            logger.warning ('Error al ejecutar el comando')
            inv_sft = ''

        self.muestraMenu()

        cmd = {
            'nfn':     'RESPUESTA_CrearImagen',
            'idi':     idi,    ## Identificador de la imagen
            'dsk':     dsk,    ## Número de disco
            'par':     par,    ## Número de partición de donde se creó
            'cpt':     cpt,    ## Tipo o código de partición
            'ipr':     ipr,    ## Ip del repositorio donde se alojó
            'inv_sft': inv_sft,
        }
        return self.respuestaEjecucionComando (cmd, herror, ids)

    def do_RestaurarImagen (self, post_params):
        for k in ['dsk', 'par', 'idi', 'ipr', 'nci', 'ifs', 'ptc', 'nfn', 'ids']:
            if k not in post_params:
                logger.error (f'required parameter ({k}) not in POST params')
                return {}

        dsk = post_params['dsk']
        par = post_params['par']
        idi = post_params['idi']
        ipr = post_params['ipr']
        nci = post_params['nci']
        ifs = post_params['ifs']
        ptc = post_params['ptc']    ## Protocolo de clonación: Unicast, Multicast, Torrent
        nfn = post_params['nfn']
        ids = post_params['ids']

        self.muestraMensaje (3)

        try:
            ## the ptc.split() is useless right now, since interfaceAdmin() does ' '.join(params) in order to spawn a shell
            ## however we're going to need it in the future (when everything gets translated into python), plus it's harmless now. So let's do it
            #self.interfaceAdmin (nfn, [dsk, par, nci, ipr, ptc])
            self.interfaceAdmin (nfn, [dsk, par, nci, ipr] + ptc.split())
            self.muestraMensaje (11)
            herror = 0
        except:
            logger.warning ('Error al ejecutar el comando')
            self.muestraMensaje (12)
            herror = 1

        cfg = self.LeeConfiguracion()
        if not cfg:
            logger.warning ('No se ha podido recuperar la configuración de las particiones del disco')

        self.muestraMenu()

        cmd = {
            'nfn': 'RESPUESTA_RestaurarImagen',
            'idi': idi,                  ## Identificador de la imagen
            'dsk': dsk,                  ## Número de disco
            'par': par,                  ## Número de partición
            'ifs': ifs,                  ## Identificador del perfil software
            'cfg': self.cfg2obj(cfg),    ## Configuración de discos
        }
        return self.respuestaEjecucionComando (cmd, herror, ids)

    def process_status (self, path, get_params, post_params, server):
        thr_status = {}
        for k in self.thread_list:
            thr_status[k] = {
                'running': self.thread_list[k]['running'],
                'result': self.thread_list[k]['result'],
            }
        return thr_status

    def do_Configurar (self, post_params):
        for k in ['nfn', 'dsk', 'cfg', 'ids']:
            if k not in post_params:
                logger.error (f'required parameter ({k}) not in POST params')
                return {}

        nfn = post_params['nfn']
        dsk = post_params['dsk']
        cfg = post_params['cfg']
        ids = post_params['ids']

        self.muestraMensaje (4)

        params = []
        disk_info = cfg.pop (0)
        logger.debug (f'disk_info ({disk_info})')
        for k in ['dis', 'che', 'tch']:
            params.append (f'{k}={disk_info[k]}')
        disk_info_str = '*'.join (params)

        partitions = []
        for entry in cfg:
            logger.debug (f'entry ({entry})')
            params = []
            for k in ['par', 'cpt', 'sfi', 'tam', 'ope']:
                params.append (f'{k}={entry[k]}')
            partitions.append ('*'.join (params))
        part_info_str = '%'.join (partitions)

        cfg_str = f'{disk_info_str}!{part_info_str}%'

        try:
            self.interfaceAdmin (nfn, ['ignored', cfg_str])
            self.muestraMensaje (14)
            herror = 0
        except:
            logger.warning ('Error al ejecutar el comando')
            self.muestraMensaje (13)
            herror = 1

        cfg = self.LeeConfiguracion()
        if not cfg:
            logger.warning ('No se ha podido recuperar la configuración de las particiones del disco')
            return {}

        cmd = {
            'nfn': 'RESPUESTA_Configurar',
            'cfg': self.cfg2obj (cfg),
        }
        self.muestraMenu()
        return self.respuestaEjecucionComando (cmd, herror, ids)

    def do_InventarioHardware (self, post_params):
        for k in ['nfn', 'ids']:
            if k not in post_params:
                logger.error (f'required parameter ({k}) not in POST params')
                return {}

        nfn = post_params['nfn']
        ids = post_params['ids']

        self.muestraMensaje (6)

        hrdsrc = f'/tmp/Chrd-{self.IPlocal}'      ## Nombre que tendra el archivo de inventario
        hrddst = f'/tmp/Shrd-{self.IPlocal}'      ## Nombre que tendra el archivo en el Servidor
        try:
            self.interfaceAdmin (nfn, [hrdsrc])
            hrdsrc_contents = Path (hrdsrc).read_bytes()
            logger.debug (f'hrdsrc_contents 1 ({hrdsrc_contents})')
            herror = 0
        except:
            logger.warning ('Error al ejecutar el comando')
            self.muestraMensaje (18)
            herror = 1

        if herror:
            hrddst = ''
        else:
            logger.debug (f'hrdsrc_contents 2 ({hrdsrc_contents})')
            ## Envía fichero de inventario al servidor
            res = self.enviaMensajeServidor ('recibeArchivo', { 'nfl': hrddst, 'contents': base64.b64encode (hrdsrc_contents).decode ('utf-8') })
            logger.debug (res)
            if not res:
                logger.error ('Ha ocurrido algún problema al enviar un archivo por la red')
                herror = 12     ## Error de envío de fichero por la red
            self.muestraMensaje (17)

        ## Envia respuesta de ejecución de la función de interface
        cmd = {
            'nfn': 'RESPUESTA_InventarioHardware',
            'hrd': hrddst,
        }
        self.muestraMenu()
        return self.respuestaEjecucionComando (cmd, herror, ids)

    def do_InventarioSoftware (self, post_params):
        for k in ['nfn', 'dsk', 'par', 'ids']:
            if k not in post_params:
                logger.error (f'required parameter ({k}) not in POST params')
                return {}

        nfn = post_params['nfn']
        dsk = post_params['dsk']
        par = post_params['par']
        ids = post_params['ids']

        self.muestraMensaje (7)

        try:
            cmd = self.InventariandoSoftware (dsk, par, 'InventarioSoftware')
            herror = 0
        except:
            logger.warning ('Error al ejecutar el comando')
            cmd = { 'nfn': 'RESPUESTA_InventarioSoftware' }
            herror = 1

        self.muestraMenu()
        return self.respuestaEjecucionComando (cmd, herror, ids)

    def process_CrearImagen (self, path, get_params, post_params, server):
        logger.debug ('in process_CrearImagen, path "{}" get_params "{}" post_params "{}" server "{}"'.format (path, get_params, post_params, server))
        logger.debug ('type(post_params) "{}"'.format (type (post_params)))
        return self._long_running_job ('CrearImagen', self.do_CrearImagen, args=(post_params,))

    def process_CrearImagenBasica (self, path, get_params, post_params, server):
        logger.debug ('in process_CrearImagenBasica, path "{}" get_params "{}" post_params "{}" server "{}"'.format (path, get_params, post_params, server))
        logger.warning ('this method has been removed')
        raise Exception ({ '_httpcode': 404, '_msg': 'This method has been removed' })

    def process_CrearSoftIncremental (self, path, get_params, post_params, server):
        logger.debug ('in process_CrearSoftIncremental, path "{}" get_params "{}" post_params "{}" server "{}"'.format (path, get_params, post_params, server))
        logger.warning ('this method has been removed')
        raise Exception ({ '_httpcode': 404, '_msg': 'This method has been removed' })

    def process_RestaurarImagen (self, path, get_params, post_params, server):
        logger.debug ('in process_RestaurarImagen, path "{}" get_params "{}" post_params "{}" server "{}"'.format (path, get_params, post_params, server))
        logger.debug ('type(post_params) "{}"'.format (type (post_params)))
        return self._long_running_job ('RestaurarImagen', self.do_RestaurarImagen, args=(post_params,))

    def process_RestaurarImagenBasica (self, path, get_params, post_params, server):
        logger.debug ('in process_RestaurarImagenBasica, path "{}" get_params "{}" post_params "{}" server "{}"'.format (path, get_params, post_params, server))
        logger.warning ('this method has been removed')
        raise Exception ({ '_httpcode': 404, '_msg': 'This method has been removed' })

    def process_RestaurarSoftIncremental (self, path, get_params, post_params, server):
        logger.warning ('in process_RestaurarSoftIncremental')
        logger.debug ('in process_RestaurarSoftIncremental, path "{}" get_params "{}" post_params "{}" server "{}"'.format (path, get_params, post_params, server))
        logger.warning ('this method has been removed')
        raise Exception ({ '_httpcode': 404, '_msg': 'This method has been removed' })

    def process_Configurar (self, path, get_params, post_params, server):
        logger.debug ('in process_Configurar, path "{}" get_params "{}" post_params "{}" server "{}"'.format (path, get_params, post_params, server))
        return self._long_running_job ('Configurar', self.do_Configurar, args=(post_params,))

    def process_InventarioHardware (self, path, get_params, post_params, server):
        logger.debug ('in process_InventarioHardware, path "{}" get_params "{}" post_params "{}" server "{}"'.format (path, get_params, post_params, server))
        return self._long_running_job ('InventarioHardware', self.do_InventarioHardware, args=(post_params,))

    def process_InventarioSoftware (self, path, get_params, post_params, server):
        logger.debug ('in process_InventarioSoftware, path "{}" get_params "{}" post_params "{}" server "{}"'.format (path, get_params, post_params, server))
        return self._long_running_job ('InventarioSoftware', self.do_InventarioSoftware, args=(post_params,))

    ## curl --insecure -X POST --data '{"job_id":"foo"}' https://192.168.2.199:8000/CloningEngine/KillJob
    def process_KillJob (self, path, get_params, post_params, server):
        logger.debug ('in process_KillJob, path "{}" get_params "{}" post_params "{}" server "{}"'.format (path, get_params, post_params, server))
        jid = post_params['job_id']
        r = self.killer (jid)
        logger.debug (f'r bef ({r})')
        r.update ({ 'nfn':'RESPUESTA_KillJob', 'job':jid })
        logger.debug (f'r aft ({r})')
        return r
