"""
This modules specifies actions to restore files from the inventory in order to prepare everything
for the device to run on it's configured role (ChargeboxControl, HmiControl, etc.)
"""

import logging
import shutil
from pathlib import Path
import subprocess

from .utils import (
    OTA_DATA_FOLDER,
    EVTEC_ECP_DATA_FOLDER,
    DeviceType,
    DeviceRole,
    MsiServices,
    OTA_ECP3_LOG,
    OTA_RESCUE_LOG,
    OTA_ECP4_LOG,
)

logger = logging.getLogger(OTA_ECP4_LOG)


DeviceTypeToDisplayServices: dict[DeviceType, MsiServices] = {
    DeviceType.ESPRESSO: MsiServices.ANDCHARGE_DISPLAY,
    DeviceType.UFC: MsiServices.ANDCHARGE_DISPLAY,
    DeviceType.UFC_500: MsiServices.ANDCHARGE_DISPLAY,
    DeviceType.SHOP: MsiServices.ANDCHARGE_DISPLAY,
    DeviceType.POWER_MANAGER: MsiServices.ANDCHARGE_DISPLAY,
    DeviceType.SOSPESO: MsiServices.ANDCHARGE_DISPLAY,
    DeviceType.CAPPUCCINO: MsiServices.ANDCHARGE_DISPLAY,
    DeviceType.RISTRETTO: MsiServices.ANDCHARGE_DISPLAY,
    DeviceType.MOKA: MsiServices.ANDCHARGE_DISPLAY,
    DeviceType.CORRETTO: MsiServices.ANDCHARGE_DISPLAY,
    DeviceType.CREMA: MsiServices.ANDCHARGE_DISPLAY,
    DeviceType.BRICCO: MsiServices.ANDCHARGE_DISPLAY,
    DeviceType.CHARGE_MASTER: MsiServices.CM_DISPLAY,
    DeviceType.PRE300: MsiServices.CM_DISPLAY,  # TODO check if needs different service
    DeviceType.PRE360AIO: MsiServices.CM_DISPLAY,
}


def getConfiguredServices(config: dict) -> set[MsiServices]:
    """
    Parses the configuration file and returns a set of services that should be active
    """
    logger.info("Parsing inventory...")
    expectedServices = set()
    deviceRole = config.get("Device").get("Role")
    deviceType = config.get("Device").get("Type")

    logger.info(f"deviceType: {deviceType}, deviceRole: {deviceRole}")

    if deviceRole == DeviceRole.CONTROLLER_REMOTE_DISPLAY:
        expectedServices.add(MsiServices.ECP)
    elif deviceRole == DeviceRole.CONTROLLER_LOCAL_HMI:
        expectedServices.add(MsiServices.ECP)
        expectedServices.add(MsiServices.ANDCHARGE_DISPLAY)
    elif deviceRole in [DeviceRole.REMOTE_HMI_1, DeviceRole.REMOTE_HMI_2]:
        expectedServices.add(MsiServices.HMI_CONTROL)
        # display service depends on device type
        expectedServices.add(DeviceTypeToDisplayServices[deviceType])
    elif deviceRole == DeviceRole.REMOTE_DISPLAY_1:
        expectedServices.add(MsiServices.WATCHDOG_SERVER)
        # display service depends on device type
        expectedServices.add(DeviceTypeToDisplayServices[deviceType])
    elif deviceRole == DeviceRole.POWER_SINK_CONTROL:
        expectedServices.add(MsiServices.POWER_SINK_CONTROL)

    # MeterControl
    if config.get("Device").get("MeterControl") == "true":
        expectedServices.add(MsiServices.METER_CONTROL)

    # DCMS
    if config.get("Device").get("BaristaMaster") in [
        "MasterLocal",
        "Master3",
        "Master10",
        "MasterFull",
    ]:
        expectedServices.add(MsiServices.DCMS_CONTROL)

    logger.info(
        f"Expected services running for {deviceRole}: {[str(service) for service in expectedServices]}"
    )

    return expectedServices


def checkExpectedServices(config):
    # check running services
    expectedServices = getConfiguredServices(config)
    expectedServicesRunning = True
    for service in expectedServices:
        if checkServiceActive(service):
            logger.info(f"Service {service} active")
        else:
            logger.warning(f"Service {service} not active but should be!")
            expectedServicesRunning = False
    if expectedServicesRunning:
        logger.info(f"All expected services running")
        return True
    else:
        logger.warning("Some services not running. Please check this device!")
        return False


def checkServiceActive(serviceName: str) -> bool:
    # check individual service
    proc = subprocess.run(
        f"systemctl status {serviceName} --no-pager", shell=True, capture_output=True
    )

    if proc.returncode != 0:
        logger.warning(
            f"Checking status of {serviceName}.service exited with {proc.returncode} (err: {proc.stderr})"
        )

    return proc.returncode == 0


def backupInventory() -> None:
    """
    Keep a copy of the inventory inside the evtec-ecp-data/log folder for traceability
    """
    otaBackupData = EVTEC_ECP_DATA_FOLDER / "log" / "ota"

    if otaBackupData.exists():
        shutil.rmtree(otaBackupData)

    logger.info(f"Created backup of {otaBackupData}")
    shutil.copytree(OTA_DATA_FOLDER.as_posix(), otaBackupData.as_posix())


def removeDir(targetDirectory: Path) -> None:
    """
    Deletes directory if it exists

    :param targetDirectory: The directory to delete
    :return: None
    """
    if targetDirectory.exists():
        logger.info(f"Deleting {targetDirectory} directory")
        shutil.rmtree(targetDirectory.as_posix())
    else:
        logger.warning(
            f"Try to delete <{targetDirectory}> directory, but it does not exist."
        )


def exposeStatistics() -> None:
    sourceFile = OTA_DATA_FOLDER / "log" / "Statistics.json.log"
    destinationDir = EVTEC_ECP_DATA_FOLDER / "data"
    destinationFile = destinationDir / "Statistics.json"

    if sourceFile.is_file():
        destinationDir.mkdir(parents=True, exist_ok=True)
        shutil.copy(sourceFile, destinationFile)
        logger.info(f"Copied {sourceFile.name} to {destinationFile}")
    else:
        logger.warning(f"Unable to expose {sourceFile.name}. File does not exist.")


def exposeOtaLogs() -> None:
    sourceDir = OTA_DATA_FOLDER
    destinationDir = EVTEC_ECP_DATA_FOLDER / "log"

    logFiles = [
        OTA_ECP3_LOG,
        OTA_RESCUE_LOG,
    ]

    for logFile in logFiles:
        sourceFile = sourceDir / f"{logFile}.log"
        if sourceFile.is_file():
            shutil.copy(sourceFile, destinationDir)
            logger.info(f"Exposed {logFile}.log to {destinationDir}")
        else:
            logger.warning(f"Unable to expose {logFile}.log. File does not exists.")
