import os
import subprocess
import sys
from logging import Logger, getLogger
from pathlib import Path

from aiohttp import web
from config.ConfigSimpleReadOnly import configSimpleReadOnly
from config.migration.systemdLogging import setupLogging

PORT = 8090
RESPONSE_CODE = 200
LOG_NAME = "RescueDisplay"
REMOTE_DISPLAY_1 = "RemoteDisplay1"
REMOTE_HMI_1 = "RemoteHMI1"
REMOTE_HMI_2 = "RemoteHMI2"
DISPLAY_ROLE_LOCAL_HMI = "ControllerLocalHMI"  # for crema&charge with controller board, we can directly show picture
REMOTE_HMI_ROLES = [REMOTE_HMI_1, REMOTE_HMI_2]
DISPLAY_ROLES = [REMOTE_DISPLAY_1, REMOTE_HMI_1, REMOTE_HMI_2, DISPLAY_ROLE_LOCAL_HMI]
KEY_DEVICE_ROLE = "Device/Role"
KEY_DEVICE_TYPE = "Device/Type"
KEY_DEVICE_SERIAL = "Device/Serial/Serial"
KEY_DEVICE_VIEW = "UserInterface/DeviceView"
ICON_NAME = "icon-coffeecup-shattered-"
DEVICE_VIEW_UFC_300 = "ufc300"
PRE_DEVICES = ["CM", "PRE300", "PRE360AIO"]
RESCUE_INDEX = "rescue.html"

ECP_WORK_FILES_DIR_CANDIDATES = (
    os.environ.get("ECP_WORK_FILES_DIR", ""),
    "/mnt/persistent-data/evtec-ecp-data",
)

def findConfigFile() -> Path | None:
    """Returns the first readable config file found.

    Iterates over the work dir candidates and tries to find a config file in it. When finding the first readable config
    file, it sets the environment variable 'ECP_WORK_FILES_DIR' to the corresponding work folder and returns a path
    object to this file if one has been found.

    :return: The found config file or None, if no config file could be found
    """
    CONFIG_FILE_PATH = "config/config.json"
    """Location of the config file within the workdir"""

    for workdir in ECP_WORK_FILES_DIR_CANDIDATES:
        configFile = Path(workdir) / CONFIG_FILE_PATH
        if os.access(configFile, os.R_OK):
            # The config file candidate exists and is readable. Use this one.
            os.environ["ECP_WORK_FILES_DIR"] = workdir
            return configFile
        else:
            getLogger().info("Config file '%s' is not accessible", str(configFile))
    return None


def getConfigKeys(configFile: Path) -> tuple[str, str, int, str]:
    """Read the device role from the config file given environment variable

    :param configFile: path to the config file
    """
    configKeys: dict = configSimpleReadOnly(
        (KEY_DEVICE_ROLE, KEY_DEVICE_TYPE, KEY_DEVICE_SERIAL, KEY_DEVICE_VIEW),
        {
            KEY_DEVICE_ROLE: "",
            KEY_DEVICE_TYPE: "",
            KEY_DEVICE_SERIAL: 0,
            KEY_DEVICE_VIEW: "",
        },  # The role is read out by the service supervisor, which does not assume any defaults
        configFile,
    )
    return (
        configKeys.get(KEY_DEVICE_ROLE),
        configKeys.get(KEY_DEVICE_TYPE),
        configKeys.get(KEY_DEVICE_SERIAL),
        configKeys.get(KEY_DEVICE_VIEW),
    )


def showStaticPicture(picturePath: Path) -> None:
    """Use the framebuffer directly to show the static picture.

    :param picturePath: Path to the static picture with correct resolution
    """
    showPictureProcess = subprocess.Popen(
        args=["dd", f"if={picturePath.absolute()}", "of=/dev/fb0"],
        stdout=subprocess.PIPE,
        stderr=subprocess.PIPE,
    )
    stdout, stderr = showPictureProcess.communicate()
    if stderr:
        logger: Logger = getLogger(LOG_NAME)
        logger.info(stderr.decode())

async def indexHandler(request: web.Request) -> web.Response:
    """Handles GET requests and serves the HTML index page with CORS headers.

    :param request: The incoming HTTP GET request.
    :type request: web.Request
    :return: HTTP response containing HTML content and CORS headers.
    :rtype: web.Response
    """

    htmlFile = "<html><body>500</body></html>"
    sourcePath = Path(__file__).parent / RESCUE_INDEX
    if sourcePath.is_file():
        with open(sourcePath, "r") as file:
            htmlFile = file.read()
    return web.Response(
        text=htmlFile,
        content_type="text/html",
        headers={
            "Access-Control-Allow-Origin": "*",
            "Access-Control-Allow-Methods": "GET, OPTIONS",
            "Access-Control-Allow-Headers": "*",
        }
    )

async def optionsHandler(request: web.Request) -> web.Response:
    """Handles CORS preflight (OPTIONS) requests and returns with necessary headers.

    :param request: The incoming HTTP OPTIONS request.
    :type request: web.Request
    :return: HTTP response with only CORS headers.
    :rtype: web.Response
    """

    return web.Response(headers={
        "Access-Control-Allow-Origin": "*",
        "Access-Control-Allow-Methods": "GET, OPTIONS",
        "Access-Control-Allow-Headers": "*",
    })

def setGPIO(logger: Logger, path: str, value: str):
    try:
        with open(path, "w") as gpio:
            gpio.write(value)
    except (OSError, IOError) as e:
        logger.info(f"Error setting GPIO: {e}")

def main():
    configFile: Path | None = findConfigFile()
    # Find which config file to use
    if configFile is None:
        getLogger().error("No readable config found. Exiting.")
        sys.exit(os.EX_CONFIG)

    # setupLogging needs the ECP_WORK_FILES_DIR environment variable populated, thus it must be run after findConfigFile
    setupLogging(LOG_NAME + ".log")
    logger: Logger = getLogger(LOG_NAME)

    deviceRole, deviceType, deviceSerial, deviceView = getConfigKeys(configFile)
    logger.info(
        f"Config Keys: Role:{deviceRole}, Type:{deviceType}, Serial:{deviceSerial}, DeviceView:{deviceView}"
    )
    if deviceRole in DISPLAY_ROLES:
        parentDir = Path(__file__).parent
        resolution800x400 = parentDir / (ICON_NAME + "800x400.raw")
        resolution1024x768 = parentDir / (ICON_NAME + "1024x768.raw")
        resolution1280x800 = parentDir / (ICON_NAME + "1280x800.raw")
        if deviceView == DEVICE_VIEW_UFC_300:
            showStaticPicture(resolution1024x768)
        elif deviceRole == DISPLAY_ROLE_LOCAL_HMI:
            showStaticPicture(resolution1280x800)
        elif deviceType in PRE_DEVICES:
            showStaticPicture(resolution1280x800)
        else:
            showStaticPicture(resolution800x400)
    else:
        setGPIO(logger, "/sys/class/gpio/gpio161/value", "1")
        setGPIO(logger, "/sys/class/gpio/gpio159/value", "1")
        setGPIO(logger, "/sys/class/gpio/gpio158/value", "1")
        app = web.Application()
        routes = [
            "",
            "/", 
            "/index.html", 
            "/index.htm"
        ]

        for route in routes:
            app.router.add_route("GET", route, indexHandler)
            app.router.add_route("OPTIONS", route, optionsHandler)

        web.run_app(app, host="0.0.0.0", port=PORT)

if __name__ == "__main__":
    """Main entrypoint, any unhandled exceptions will lead to non zero exit code"""
    main()
