# coding: utf-8
import os
import shutil
import tempfile
import zipfile
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, Messages, API_SUB_PATH, 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.security_api import get_token_ApiKeyAuth, checkTokenCapabilities
from server.utils.files import buildArchive, deleteFile
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(
    "/ipk",
    responses={
        200: {"model": List[File], "description": "Gets all ipk files."},
        400: {"model": Error, "description": "Bad request."},
        401: {"model": Error, "description": "Unauthorized."},
    },
    tags=["ipk"],
    response_model_by_alias=True,
)
async def ipk_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 ipk files."""
    # ------------------------------------------- Check token capabilities -------------------------------------------

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

    files = await listdir(Paths.WEBUI_IPK_FILES_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="ipk",
            filePaths=[Paths.WEBUI_IPK_FILES_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.delete(
    "/ipk/{ipkfile}",
    responses={
        200: {"model": File, "description": "Deleted the ipk package."},
        400: {"model": Error, "description": "Bad request."},
        401: {"model": Error, "description": "Unauthorized."},
        404: {"model": Error, "description": "File not found."},
    },
    tags=["ipk"],
    response_model_by_alias=True,
)
async def ipk_ipkfile_delete(
    ipkfile: str = Path(description="The name of the ipk package."),
    token_ApiKeyAuth: TokenModel = Security(get_token_ApiKeyAuth),
) -> File:
    """Delete a specific ipk package."""
    # ------------------------------------------- Check token capabilities -------------------------------------------

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

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

    file = await deleteFile(path=Paths.WEBUI_IPK_FILES_DIR.value, deleteFileName=ipkfile)

    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(
    "/ipk/{ipkfile}",
    responses={
        200: {"model": File, "description": "Gets the ipk package."},
        400: {"model": Error, "description": "Bad request."},
        401: {"model": Error, "description": "Unauthorized."},
        404: {"model": Error, "description": "File not found."},
    },
    tags=["ipk"],
    response_model_by_alias=True,
)
async def ipk_ipkfile_get(
    background_tasks: BackgroundTasks,
    ipkfile: str = Path(description="The name of the ipk package."),
    token_ApiKeyAuth: TokenModel = Security(get_token_ApiKeyAuth),
) -> FileResponse:
    """Get a specific ipk package."""
    # ------------------------------------------- Check token capabilities -------------------------------------------

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

    if not ipkfile in await listdir(Paths.WEBUI_IPK_FILES_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=ipkfile.split(".")[0],
        filePaths=[Paths.WEBUI_IPK_FILES_DIR.value],
        files=[ipkfile],
    )

    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(
    "/ipk",
    responses={
        201: {"model": File, "description": "Shows the created File."},
        400: {"model": Error, "description": "Bad request."},
        401: {"model": Error, "description": "Unauthorized."},
    },
    tags=["ipk"],
    response_model_by_alias=True,
)
async def ipk_post(
    file: Annotated[UploadFile, File()] = None, token_ApiKeyAuth: TokenModel = Security(get_token_ApiKeyAuth)
) -> List:
    """Save a ipk package."""
    # ------------------------------------------- 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.DEFAULT_WEBUI_WORK_DIR.value, Paths.WEBUI_WORK_SUBDIR.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)

        # Extract all files into definitive dir
        if zipfile.is_zipfile(uploadedFile):
            # Extract the contents of the zip archive to the upload directory
            with zipfile.ZipFile(uploadedFile, "r") as zipRef:
                zipRef.extractall(Paths.WEBUI_IPK_FILES_DIR.value)

            # Remove zip file
            await aiofiles.os.unlink(uploadedFile)

    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}]
