import io
import json
from typing import IO, Dict, Optional

import httpx
from aext_panels_server.api import PythonAnywhereAPI
from aext_panels_server.const import PROJECT_ROOT_PATH
from aext_panels_server.logger import custom_logger
from aext_panels_server.schemas import MetaResponse, RequestError, ServiceResponse
from aext_panels_server.utils import call_api


class ProjectIndexService:
    async def create_project_index(self):
        raise NotImplementedError

    async def list_project_index(self):
        raise NotImplementedError

    def get_project_index_file_path(self, include_root_path: bool = False) -> str:
        if include_root_path:
            return f"{PROJECT_ROOT_PATH}.projects/index.json"
        return ".projects/index.json"

    async def get_project_index(self, api: PythonAnywhereAPI) -> ServiceResponse:
        project_index_path = project_index_service.get_project_index_file_path()
        err, response = await call_api(api.download_file(project_index_path), always_return_response=True)

        if not err.status:
            return ServiceResponse(
                meta=MetaResponse(error=RequestError(status=False)),
                content=response.json(),
            )

        if not response:
            return ServiceResponse(
                meta=MetaResponse(
                    error=RequestError(
                        status=True,
                        message="Unexpected error while fetching project index",
                    )
                ),
                content={},
            )

        if err.status:
            err_resp = ServiceResponse(
                meta=MetaResponse(
                    error=RequestError(
                        status=False,
                    )
                ),
                content={},
            )
            if response.status_code == httpx.codes.NOT_FOUND:
                err_resp.set_error_message("Index file not found")
            elif response.status_code == httpx.codes.UNAUTHORIZED:
                err_resp.set_error_message("Failed to fetch index - Unauthorized")
            else:
                err_resp.set_error_message(f"Failed to fetch index - {err.message}")
            return err_resp

    async def get_project_index_or_create(self, api, project_id, project_title) -> io.BytesIO:
        """
        Tries to retrieve and load the index file `index.json`
        which is currently stored in PythonAnywhere server.

        Parameters
        ----------
        api: PythonAnywhereAPI
        project_id: Project's id
        project_title: Project's title

        Returns binary dict containing the project index
        -------
        """
        project_index_response = await self.get_project_index(api)
        project_index = project_index_response.content

        # project_index file doesn't exist then create the json
        if not project_index:
            custom_logger.info("Creating project_index json because it does not exist.")
            project_index = {project_id: {"webapp_id": None, "title": project_title}}
        else:
            # project_id doesn't exist then add the new project
            if project_id not in project_index:
                custom_logger.info("Adding new project to project_index json.")
                new_project = {project_id: {"webapp_id": None, "title": project_title}}
                project_index.update(new_project)
            else:
                # project_id exist then update the project title
                custom_logger.info("Updating project's title on project_index json.")
                project_index[project_id]["title"] = project_title

        return io.BytesIO(json.dumps(project_index).encode("utf-8"))

    async def get_webapp_id(
        self,
        project_id: str,
        project_index: Optional[Dict],
        api: Optional[PythonAnywhereAPI],
    ) -> Optional[int]:
        if not project_index and not api:
            raise Exception("If project index is not given the API is needed in order to fetch it")
        if not project_index and api:
            # Project index was not given, fetching it from PA
            project_index = await self.get_project_index(api)

        if project_index_info := project_index.content.get(project_id):
            if project_index_info:
                return project_index_info.get("webapp_id")
        return None

    async def exists(self, api: PythonAnywhereAPI, project_id: str) -> bool:
        project_index_response = await self.get_project_index(api)
        if not project_index_response.content:
            return False
        return bool(project_index_response.content.get(project_id))

    async def _create_json_bytes(self, data: Dict) -> io.BytesIO:
        return io.BytesIO(json.dumps(data).encode("utf-8"))

    async def save(self, api: PythonAnywhereAPI, project_index_content: IO) -> ServiceResponse:
        err, response = await call_api(
            api.upload_file(
                self.get_project_index_file_path(include_root_path=True),
                project_index_content,
            )
        )
        return ServiceResponse(meta=MetaResponse(error=err), content={})

    async def update_webapp_id(self, api: PythonAnywhereAPI, project_id: str, webapp_id: int) -> ServiceResponse:
        project_index_response = await self.get_project_index(api)
        project_index = project_index_response.content
        try:
            project_index[project_id]["webapp_id"] = webapp_id
        except KeyError:
            custom_logger.error(f"Project {project_id} index not found while updating entry")
            return ServiceResponse(
                meta=MetaResponse(
                    error=RequestError(
                        status=True,
                        message="Project {project_id} index not found while updating entry",
                    )
                ),
                content={},
            )
        return await self.save(api, await self._create_json_bytes(project_index))

    async def delete_project(self, api: PythonAnywhereAPI, project_id: str) -> ServiceResponse:
        project_index_response = await self.get_project_index(api)
        project_index = project_index_response.content
        try:
            del project_index[project_id]
        except KeyError:
            # The key was already removed or does not exist. Failing safe and returning success
            custom_logger.info(f"Project {project_id} index not found while deleting entry")
        return await self.save(api, await self._create_json_bytes(project_index))


project_index_service = ProjectIndexService()
