# coding: utf-8
import os
import shutil
import tempfile
from typing import Dict, List, Union, Any  # noqa: F401

import aiofiles
from aiofiles.os import listdir
from fastapi import (  # noqa: F401
    APIRouter,
    Body,
    Cookie,
    Depends,
    Form,
    Header,
    Path,
    Query,
    Response,
    Security,
    status,
    HTTPException,
    UploadFile,
)
from fastapi.responses import FileResponse
from starlette.background import BackgroundTasks
from typing_extensions import Annotated

from server.data.definitions import Groups, Paths, ErrorTypes, API_SUB_PATH, Messages, WEBUI_WORK_FILES_DIR
from server.models.error import Error
from server.models.extra_models import TokenModel  # noqa: F401
from server.models.file import File
from server.models.rauc_install_path import RaucInstallPath
from server.models.rauc_operation import RaucOperation
from server.models.root_fs_info import RootFSInfo
from server.security_api import get_token_ApiKeyAuth, checkTokenCapabilities
from server.utils.files import buildArchive, deleteFile
from server.utils.raucInterface import (
    getRaucInformation,
    getRaucOperation,
    Operation,
    getRaucProgress,
    installRaucBundle,
)
from . import appData

router = APIRouter(prefix=API_SUB_PATH)

# Get from environment variable the value, otherwise it takes the default value
PATH_WEBUI_WORK_DIR = f"{os.environ.get(WEBUI_WORK_FILES_DIR, Paths.DEFAULT_WEBUI_WORK_DIR.value)}"
PATH_WEBUI_REAL_WORK_DIR = f"{PATH_WEBUI_WORK_DIR}/{Paths.WEBUI_WORK_SUBDIR.value}"

# Change the work directory for uploads
tempfile.tempdir = PATH_WEBUI_REAL_WORK_DIR


@router.get(
    "/rauc",
    responses={
        200: {"model": List[File], "description": "Gets all rauc bundles."},
        400: {"model": Error, "description": "Bad request."},
        401: {"model": Error, "description": "Unauthorized."},
    },
    tags=["rauc"],
    response_model_by_alias=True,
)
async def rauc_get(
    background_tasks: BackgroundTasks,
    onlyList: bool = Query(False, description="The boolean for activating only list mode."),
    token_ApiKeyAuth: TokenModel = Security(get_token_ApiKeyAuth),
) -> Union[List, Any]:
    """Get all the available rauc bundles."""
    isTokenGranted = await checkTokenCapabilities(token=token_ApiKeyAuth, group=Groups.DOWNLOAD_FIRMWARE.value)

    files = await listdir(Paths.WEBUI_RAUC_WORK_DIR.value)
    if onlyList:
        return [{"id": file_name} for file_name in files]
    else:

        zipPath, zipName = await buildArchive(
            serial=appData.serial,
            ecpVersion=appData.ecpVersion,
            fileName="rauc-work",
            filePaths=[Paths.WEBUI_RAUC_WORK_DIR.value],
            files=files,
        )

        background_tasks.add_task(os.unlink, zipPath)

        return FileResponse(
            zipPath,
            media_type="application/x-zip-compressed",
            headers={"Content-Disposition": f'attachment; filename="{zipName}.zip"'},
        )


@router.post(
    "/rauc",
    responses={
        201: {"model": File, "description": "Shows the created File."},
        400: {"model": Error, "description": "Bad request."},
        401: {"model": Error, "description": "Unauthorized."},
    },
    tags=["rauc"],
    response_model_by_alias=True,
)
async def rauc_post(
    file: Annotated[UploadFile, File()] = None, token_ApiKeyAuth: TokenModel = Security(get_token_ApiKeyAuth)
) -> List:
    """Save a rauc bundle."""
    # ------------------------------------------- Check token capabilities -------------------------------------------

    isTokenGranted = await checkTokenCapabilities(token=token_ApiKeyAuth, group=Groups.UPLOAD_FIRMWARE.value)

    # -------------------------------------------- Check fields aren't empty ------------------------------------------
    if file is None or file.filename == "":
        errorResponse = Error(
            status=status.HTTP_400_BAD_REQUEST,
            type=ErrorTypes.REQUEST_IS_EMPTY.value,
            message=Messages.REQUEST_IS_EMPTY.value,
        )
        raise HTTPException(status_code=status.HTTP_400_BAD_REQUEST, detail=errorResponse.dict())

    # -------------------------------------- Save the files into persistent path --------------------------------------
    # Save the uploaded file to disk
    uploadedFile = os.path.join(Paths.WEBUI_RAUC_WORK_DIR.value, file.filename)

    # Write the uploaded file into persistent work dir
    try:
        # Move spooled Upload file to persistent memory
        with open(uploadedFile, "wb") as f_destination:
            shutil.copyfileobj(file.file, f_destination)

    except (FileNotFoundError, PermissionError, IOError, UnicodeDecodeError, OSError) as error:
        errorResponse = Error(
            status=status.HTTP_500_INTERNAL_SERVER_ERROR, type=ErrorTypes.EXCEPTION.value, message=error
        )
        raise HTTPException(status_code=status.HTTP_500_INTERNAL_SERVER_ERROR, detail=errorResponse.dict())

    return [{file.filename: Messages.UPLOAD_SUCCESSFUL.value}]


@router.delete(
    "/rauc/{raucfile}",
    responses={
        200: {"model": File, "description": "Deleted the rauc bundle."},
        400: {"model": Error, "description": "Bad request."},
        401: {"model": Error, "description": "Unauthorized."},
        404: {"model": Error, "description": "File not found."},
    },
    tags=["rauc"],
    response_model_by_alias=True,
)
async def rauc_raucfile_delete(
    raucfile: str = Path(description="The name of the rauc bundle."),
    token_ApiKeyAuth: TokenModel = Security(get_token_ApiKeyAuth),
) -> File:
    """Delete a specific rauc bundle."""
    # ------------------------------------------- Check token capabilities -------------------------------------------

    isTokenGranted = await checkTokenCapabilities(token=token_ApiKeyAuth, group=Groups.EDIT_FIRMWARE.value)

    # -------------------------------------------- Delete the file ------------------------------------------

    file = await deleteFile(path=Paths.WEBUI_RAUC_WORK_DIR.value, deleteFileName=raucfile)

    if not file:
        errorResponse = Error(
            status=status.HTTP_404_NOT_FOUND,
            type=ErrorTypes.FILE_NOT_FOUND.value,
            message=Messages.FILE_NOT_FOUND.value,
        )
        raise HTTPException(status_code=status.HTTP_404_NOT_FOUND, detail=errorResponse.dict())

    return file


@router.get(
    "/rauc/{raucfile}",
    responses={
        200: {"model": File, "description": "Gets the rauc bundle."},
        400: {"model": Error, "description": "Bad request."},
        401: {"model": Error, "description": "Unauthorized."},
        404: {"model": Error, "description": "File not found."},
    },
    tags=["rauc"],
    response_model_by_alias=True,
)
async def rauc_raucfile_get(
    background_tasks: BackgroundTasks,
    raucfile: str = Path(description="The name of the rauc bundle."),
    token_ApiKeyAuth: TokenModel = Security(get_token_ApiKeyAuth),
) -> FileResponse:
    """Get a specific rauc bundle."""
    # ------------------------------------------- Check token capabilities -------------------------------------------

    isTokenGranted = await checkTokenCapabilities(token=token_ApiKeyAuth, group=Groups.DOWNLOAD_FIRMWARE.value)

    if not raucfile in await listdir(Paths.WEBUI_RAUC_WORK_DIR.value):
        errorResponse = Error(
            status=status.HTTP_404_NOT_FOUND,
            type=ErrorTypes.FILE_NOT_FOUND.value,
            message=Messages.FILE_NOT_FOUND.value,
        )
        raise HTTPException(status_code=status.HTTP_404_NOT_FOUND, detail=errorResponse.dict())

    zipPath, zipName = await buildArchive(
        serial=appData.serial,
        ecpVersion=appData.ecpVersion,
        fileName=raucfile.split(".")[0],
        filePaths=[Paths.WEBUI_RAUC_WORK_DIR.value],
        files=[raucfile],
    )

    background_tasks.add_task(os.unlink, zipPath)

    return FileResponse(
        zipPath,
        media_type="application/x-zip-compressed",
        headers={"Content-Disposition": f'attachment; filename="{zipName}.zip"'},
    )


@router.get(
    "/raucactions/info",
    responses={
        200: {"model": RootFSInfo, "description": "Device information"},
        400: {"model": Error, "description": "Bad request."},
    },
    tags=["rauc"],
    response_model_by_alias=True,
)
async def raucactions_info_get() -> RootFSInfo:
    """Get information on the rauc slots"""

    # Do not execute when running in a docker
    if os.path.exists("/.dockerenv"):
        appData.raucData.operation = Operation.IDLE.value
        return appData.raucData

    operation = await getRaucOperation()

    if not operation == Operation.INSTALLING:
        rauc_status = await getRaucInformation()
        appData.raucData = RootFSInfo.parse_obj(rauc_status)
    else:
        appData.raucData.operation = Operation.INSTALLING.value

    return appData.raucData


@router.get(
    "/raucactions/operationstatus",
    responses={
        200: {"model": RaucOperation, "description": "Operation Status"},
        400: {"model": Error, "description": "Bad request."},
    },
    tags=["rauc"],
    response_model_by_alias=True,
)
async def raucactions_operationstatus_get() -> RaucOperation:
    """Get operation status of rauc"""
    # Do not execute when running in a docker
    if os.path.exists("/.dockerenv"):
        appData.raucData.operation = Operation.IDLE.value
        return RaucOperation(operation=appData.raucData.operation)

    operation = await getRaucOperation()
    return RaucOperation(operation=str(operation))


@router.get(
    "/raucactions/progress",
    responses={
        200: {"model": Dict, "description": "Install Progress"},
        400: {"model": Error, "description": "Bad request."},
    },
    tags=["rauc"],
    response_model_by_alias=True,
)
async def raucactions_progress_get() -> Dict:
    """Get information of the installation progress"""
    progress = await getRaucProgress()
    return {"progress": progress}


@router.put(
    "/raucactions/install",
    responses={
        200: {"model": Dict, "description": "Install trigger"},
        400: {"model": Error, "description": "Bad request."},
        401: {"model": Error, "description": "Unauthorized."},
        404: {"model": Error, "description": "Action not found."},
    },
    tags=["rauc"],
    response_model_by_alias=True,
)
async def raucactions_install_put(
    rauc_install_path: RaucInstallPath = Body(None, description="Requestbody for the POST action."),
    token_ApiKeyAuth: TokenModel = Security(get_token_ApiKeyAuth),
) -> Dict:
    """Trigger the installation of a rauc bundle."""
    # ------------------------------------------- Check token capabilities -------------------------------------------

    isTokenGranted = await checkTokenCapabilities(token=token_ApiKeyAuth, group=Groups.UPLOAD_FIRMWARE.value)

    # -------------------------------------------- Check fields aren't empty ------------------------------------------

    if rauc_install_path is None:
        errorResponse = Error(
            status=status.HTTP_400_BAD_REQUEST,
            type=ErrorTypes.REQUEST_IS_EMPTY.value,
            message=Messages.REQUEST_IS_EMPTY.value,
        )
        raise HTTPException(status_code=status.HTTP_400_BAD_REQUEST, detail=errorResponse.dict())

    if rauc_install_path.url:
        await installRaucBundle(rauc_install_path.url)
        return {"status": "ok"}
    elif rauc_install_path.filename in await listdir(Paths.WEBUI_RAUC_WORK_DIR.value):
        await installRaucBundle(os.path.join(Paths.WEBUI_RAUC_WORK_DIR.value, rauc_install_path.filename))
        return {"status": "ok"}
    else:
        errorResponse = Error(
            status=status.HTTP_404_NOT_FOUND,
            type=ErrorTypes.FILE_NOT_FOUND.value,
            message=Messages.FILE_NOT_FOUND.value,
        )
        raise HTTPException(status_code=status.HTTP_404_NOT_FOUND, detail=errorResponse.dict())
