import base64
import os
from enum import StrEnum
from http import HTTPStatus

import pydantic
import tempfile

from fastapi import UploadFile
from google.cloud import storage
from talentro.constants import ErrorCode, DisplayMessage
from talentro.exceptions import APIException

from ..util.singleton import SingletonMeta
from ..util.string import render_template_path


class BucketEnum(StrEnum):
    PUBLIC = "talentro-static"
    BILLING = "talentro-billing"
    CANDIDATES = "talentro-candidates"


class FilePathEnum(StrEnum):
    IAM_ORGANIZATION_LOGO = "iam/organizations/{organization_id}/details/logo.{extension}"
    IAM_PROFILE_PICTURE = "iam/users/{user_id}/profile/profile_picture.{extension}"
    CANDIDATES_CANDIDATE_CV = "organizations/{organization_id}/cv/{candidate_id}/logo.{extension}"


class FileData(pydantic.BaseModel):
    file_name: str
    data: str
    content_type: str


class GoogleStorage(metaclass=SingletonMeta):

    def __init__(self):
        self._client = storage.Client()

    def upload_base64_to_gcs(self, file: FileData, bucket: BucketEnum, destination: FilePathEnum, path_definitions: dict):
        file_bytes = base64.b64decode(file.data)

        _, ext = os.path.splitext(file.file_name)
        tmp_fd, tmp_path = tempfile.mkstemp(suffix=ext)
        os.close(tmp_fd)

        try:
            filled_destination_path = render_template_path(destination, path_definitions)

        except ValueError as e:
            raise APIException(
                status_code=HTTPStatus.INTERNAL_SERVER_ERROR,
                display_message=DisplayMessage.UNKNOWN_ERROR,
                message=str(e),
                error_code=ErrorCode.UNKNOWN_ERROR
            )

        try:
            with open(tmp_path, "wb") as f:
                f.write(file_bytes)

            bucket = self._client.get_bucket(bucket)

            if not bucket.exists():
                bucket = self._client.create_bucket(bucket)

            blob = bucket.blob(filled_destination_path)

            blob.upload_from_filename(
                tmp_path,
                content_type=file.content_type  # handig voor GCS metadata
            )

        finally:
            if os.path.exists(tmp_path):
                os.remove(tmp_path)

    def upload_file(self, file: UploadFile, bucket: BucketEnum, destination: FilePathEnum, path_definitions: dict) -> str:
        """
        Uploads a file to Google Storage

        :param file: The file you want to upload.
        :param bucket: The bucket you want to upload to.
        :param destination: The file destination you want to upload to.
        :param path_definitions: Key-value pair to declare the dynamic parts of the destination path.
        :return: The public URL of the uploaded file.
        :rtype: str
        """
        # Get the bucket or create it if it doesn't exist.
        bucket = self._client.get_bucket(bucket)

        if not bucket.exists():
            bucket = self._client.create_bucket(bucket)

        if not bucket.exists():
            raise APIException(
                status_code=HTTPStatus.INTERNAL_SERVER_ERROR,
                display_message=DisplayMessage.UNKNOWN_ERROR,
                message="Storage not found",
                error_code=ErrorCode.UNKNOWN_ERROR
            )

        try:
            filled_destination_path = render_template_path(destination, path_definitions)

        except ValueError as e:
            raise APIException(
                status_code=HTTPStatus.INTERNAL_SERVER_ERROR,
                display_message=DisplayMessage.UNKNOWN_ERROR,
                message=str(e),
                error_code=ErrorCode.UNKNOWN_ERROR
            )

        blob = bucket.blob(filled_destination_path)

        blob.upload_from_file(file.file)

        return blob.public_url
