"""Bifrost interface for Pinata Cloud."""

from byfrost.shared import errors
from byfrost.shared.config import provider
from ..shared.types.dataclass import file as bfile
from ..shared.types.dataclass import bridge
from typing import List, Tuple
from ..shared.errors.interface import BifrostError
from ..shared.config import option, request, url
from ..shared.errors import loga
from ..shared.request.builder import Client, new_client
import os
import requests
import json


class PinataCloud:
    def __init__(self, bc: bridge.BridgeConfig, client: Client):
        self.provider = provider.providers[bc.provider]
        self.default_bucket = bc.default_bucket
        self.pinata_jwt = bc.pinata_jwt
        self.default_timeout = bc.default_timeout
        self.client = client
        self.enable_debug = bc.enable_debug
        self.public_read = bc.public_read
        self.use_async = bc.use_async

    def upload_file(
        self, file_face: bfile.File
    ) -> Tuple[bfile.UploadedFile, BifrostError]:
        """upload_file uploads a file to Pinata Storage and returns an error if one occurs."""
        if file_face is None:
            return None, BifrostError("file is none", errors.ErrBadRequest)

        # verify that the file is derived from File
        if not isinstance(file_face, bfile.File):
            return None, BifrostError(
                "invalid file type: {}".format(type(file_face)), errors.ErrBadRequest
            )

        # verify that the file has a valid path
        if file_face.path is None or file_face.path == "":
            return None, BifrostError("no path specified", errors.ErrBadRequest)

        if not os.path.exists(file_face.path):
            raise BifrostError(
                f"file does not exist: {file_face.path}", errors.ErrBadRequest
            )

        if file_face.filename is None or file_face.filename == "":
            file_face.filename = os.path.basename(file_face.path)

        # verify that the file has a valid options
        if file_face.options is None:
            file_face.options = bfile.Options()

        # verify that the file has a valid metadata
        if file_face.options.metadata is None:
            file_face.options.metadata = {}

        # verify the connection
        if not self.is_connected():
            return None, BifrostError(
                "no active Pinata Storage client", errors.ErrClientError
            )

        param = bfile.Param(
            files=[
                bfile.ParamFile(
                    path=file_face.path,
                    key="file",
                    name=file_face.filename,
                )
            ],
            data=[],
        )

        # Configure upload options
        for k, v in file_face.options.__dataclass_fields__.items():
            if k == option.OptPinata:
                try:
                    opt = json.dumps(v)
                    param.data.append(bfile.ParamData(key=OptPinata, value=opt))
                except json.JSONDecodeError as e:
                    return None, BifrostError(
                        f"failed to parse pinata options: {str(e)}",
                        errors.ErrBadRequest,
                    )

            elif k == provider.PinataCloud + option.OptMetadata.capitalize():
                try:
                    m = json.dumps(v)
                    param.data.append(bfile.ParamData(key=k, value=m))
                except json.JSONDecodeError as e:
                    return None, BifrostError(
                        f"failed to parse pinata metadata: {str(e)}",
                        errors.ErrBadRequest,
                    )

        response, error = self.client.post_form(url.URLPinataPinFile, param)

        if error is not None:
            print(error)
            return None, BifrostError("failed to upload file", errors.ErrBadRequest)

        if "Error" in response and response["Error"] != "":
            return None, BifrostError(
                f"failed to upload file: {response['Error']}", errors.ErrBadRequest
            )

        return (
            bfile.UploadedFile(
                size=response["PinSize"],
                cid=response["IpfsHash"],
                preview=url.URLPinataGateway.format(response["IpfsHash"]),
                provider_object=response,
                name=os.path.basename(file_face.path),
                path=file_face.path,
                url=url.URLPinataGateway.format(response["IpfsHash"]),
            ),
            None,
        )

    def upload_multi_file(
        self,
        multi_face: bfile.MultiFile,
    ) -> Tuple[List[bfile.UploadedFile], BifrostError]:
        """upload multi file uploads multiple files to Pinata Storage and returns an error if one occurs."""
        if multi_face is None:
            return None, BifrostError("multi file is none", errors.ErrBadRequest)

        # verify that the multi file is derived from MultiFile
        if not isinstance(multi_face, bfile.MultiFile):
            return None, BifrostError(
                "invalid multi file type: {}".format(type(multi_face)),
                errors.ErrBadRequest,
            )

        # verify that the multi file has a valid files
        if multi_face.files is None or len(multi_face.files) == 0:
            return None, BifrostError("no files specified", errors.ErrBadRequest)

        # verify the connection
        if not self.is_connected():
            return None, BifrostError(
                "no active Pinata Cloud client", errors.ErrClientError
            )

        # upload files to Google Cloud Storage
        uploaded_files = []

        for file_face in multi_face.files:
            # verify that the multi file has a valid global options
            if multi_face.global_options is not None:
                # for each option in the global option dataclass, if it is not present in the file option dataclass, then add it to the file option dataclass
                for option in multi_face.global_options.__dataclass_fields__:
                    if not hasattr(file_face.options, option):
                        setattr(
                            file_face.options,
                            option,
                            getattr(multi_face.global_options, option),
                        )

            uploaded_file, err = self.upload_file(file_face)

            if err is not None:
                if self.enable_debug:
                    loga.error(
                        "Upload for file at path %s failed with err: %s",
                        file_face.path,
                        err.message,
                    )
                uploaded_files.append(
                    bfile.UploadedFile(
                        error=err, name=file_face.filename, path=file_face.path
                    )
                )
                continue
            uploaded_files.append(uploaded_file)
        return uploaded_files, None

    def preflight(self) -> BifrostError:
        """preflight attempts to authenticate with Pinata and returns an error if one occurs."""
        try:
            req = requests.Request(
                request.MethodGet,
                url.URLPinataAuth,
                headers=self.client.session.headers,
            ).prepare()
            res = self.client.session.send(req)
            if res.json()["message"] == "":
                return BifrostError(res.json()["error"], errors.ErrUnauthorized)
            return None
        except Exception as e:
            return BifrostError(str(e), errors.ErrClientError)

    def disconnect(self) -> BifrostError:
        """disconnect closes the Pinata Storage client connection and returns an error if one occurs."""
        if self.client is None:
            return None

    def config(self) -> bridge.BridgeConfig:
        """config returns the provider configuration."""
        return bridge.BridgeConfig(
            provider=self.provider,
            default_timeout=self.default_timeout,
            enable_debug=self.enable_debug,
            public_read=self.public_read,
            use_async=self.use_async,
        )

    def is_connected(self) -> bool:
        """is_connected returns true if there is an active connection to the provider."""
        return self.client is not None

    def upload_folder(
        self,
        fold_face: bfile.MultiFile,
    ) -> Tuple[List[bfile.UploadedFile], BifrostError]:
        """upload_folder uploads a folder to Pinata Storage and returns an error if one occurs."""
        ...
