"""Fill in a module description here"""

# AUTOGENERATED! DO NOT EDIT! File to edit: ../../nbs/google/GAuth.ipynb.

# %% auto 0
__all__ = ['DEFAULT_SCOPE', 'GoogleAuth_NoCreds_Error', 'generate_creds_from_file', 'GAuth_Required', 'GoogleAuth_NoEnv_Error',
           'generate_creds_from_env', 'GoogleAuth']

# %% ../../nbs/google/GAuth.ipynb 3
from google.auth.transport.requests import Request

# %% ../../nbs/google/GAuth.ipynb 4
import os

from dataclasses import dataclass, field
from typing import List

import dotenv


from googleapiclient.discovery import build
from google_auth_oauthlib.flow import InstalledAppFlow

import domolibrary_extensions.utils.convert as dtcv
import domolibrary_extensions.utils.files as dtfi

# %% ../../nbs/google/GAuth.ipynb 5
from google.oauth2.credentials import Credentials
from googleapiclient.discovery import Resource

# %% ../../nbs/google/GAuth.ipynb 6
class GoogleAuth_NoCreds_Error(Exception):
    def __init__(self, message: str = None):
        super().__init__(
            message
            or "unable to generate a creds obj, must pass token_path or means of generating creds"
        )


DEFAULT_SCOPE = [
    "https://www.googleapis.com/auth/drive",
    "https://www.googleapis.com/auth/drive.file",
    "https://www.googleapis.com/auth/drive.metadata.readonly",
    "https://www.googleapis.com/auth/drive.readonly",
]

# %% ../../nbs/google/GAuth.ipynb 7
def _test_or_generate_new_creds(creds, flow, port=0):
    if not creds and not flow:
        raise GoogleAuth_NoCreds_Error()

    if creds and creds.valid:
        return creds

    if creds and creds.expired and creds.refresh_token:
        creds.refresh(Request())
        return creds

    return flow.run_local_server(port=port)

# %% ../../nbs/google/GAuth.ipynb 9
def generate_creds_from_file(
    client_path: str,
    token_path: str = "./env_token.json",
    scope: List[str] = None,
    port=0,
) -> Credentials:

    scope = scope or DEFAULT_SCOPE

    creds = None

    if token_path and os.path.exists(token_path):
        creds = Credentials.from_authorized_user_file(token_path, scope)

    if not creds and (not client_path or not os.path.exists(client_path)):
        raise FileExistsError(client_path)

    flow = InstalledAppFlow.from_client_secrets_file(client_path, scope)

    creds = _test_or_generate_new_creds(creds=creds, flow=flow, port=port)

    dtfi.upsert_file(
        file_path=token_path,
        content=creds.to_json(),
    )

    return creds

# %% ../../nbs/google/GAuth.ipynb 11
class GAuth_Required(Exception):
    def __init__(self):
        message = f"must pass valid Google Auth or Service with `service_name = docs`"
        super().__init__(message)


class GoogleAuth_NoEnv_Error(Exception):
    def __init__(self, env_token):
        super().__init__(f"{env_token} not found in ENV")


def generate_creds_from_env(
    client_env_key, env_path, token_env_key=None, scope: List[str] = None, port=0
):

    scope = scope or DEFAULT_SCOPE

    if env_path:
        assert os.path.exists(env_path)
        print(f"reloading env - {env_path}")
        dotenv.load_dotenv(env_path, override=True)

    token_obj = None
    creds = None

    if token_env_key:
        token_obj = dtcv.convert_json_to_obj(os.getenv(token_env_key))

        if token_obj:
            creds = Credentials.from_authorized_user_info(token_obj, scope)

    if not creds and not os.getenv(client_env_key):
        raise GoogleAuth_NoEnv_Error(client_env_key)

    client_obj = dtcv.convert_json_to_obj(os.getenv(client_env_key))

    flow = InstalledAppFlow.from_client_config(client_obj, scope)

    creds = _test_or_generate_new_creds(
        creds=creds,
        flow=flow,
        port=port,
    )

    dtfi.update_env(env_key=token_env_key, content=creds.to_json(), env_path=env_path)

    return creds

# %% ../../nbs/google/GAuth.ipynb 14
@dataclass
class GoogleAuth:
    creds: Credentials

    scope: List[str] = field(default_factory=lambda: DEFAULT_SCOPE)

    @classmethod
    def generate_from_env(
        cls,
        env_path,
        client_env_key,
        token_env_key=None,
        scope: List[str] = None,
    ):
        """
        credentials should be stored in the .env file as a json (NOT A STRING)
        token will be stored as a string (NOT A JSON OBJ)

        generate creds here, https://console.cloud.google.com/apis/credentials/oauthclient
        for a new implementation you'll need to
        1. set up a project at console.cloud.google.com,
        2. enable (turn on) the appropriate APIs for that project
        3. configure OAuth access (your python script will use an Oauth token to authenticate, so your project needs the OAuth screen)
        4. download the creds file and store it as env_creds.json
        """

        creds = generate_creds_from_env(
            client_env_key=client_env_key,
            token_env_key=token_env_key,
            scope=scope,
            env_path=env_path,
        )

        return cls(creds=creds)

    @classmethod
    def generate_from_file(
        cls, client_path: str, token_path: str, scope: List[str] = None
    ):
        """generate creds here, https://console.cloud.google.com/apis/credentials/oauthclient
        for a new implementation you'll need to
        1. set up a project at console.cloud.google.com,
        2. enable (turn on) the appropriate APIs for that project
        3. configure OAuth access (your python script will use an Oauth token to authenticate, so your project needs the OAuth screen)
        4. download the client json file and store it as env_creds.json
        """

        creds = generate_creds_from_file(
            client_path=client_path, token_path=token_path, scope=scope
        )

        return cls(creds=creds)

    def _generate_service(self, service, version) -> Resource:
        """Generates a service object for the given service name and version"""

        print(
            f"generating {service} - {version} service object on {self.__class__.__name__}"
        )

        return build(serviceName=service, version=version, credentials=self.creds)

    def generate_gdrive_service(self) -> Resource:
        """Generates a service object for the given service name and version"""

        return self._generate_service(service="drive", version="v3")

    def generate_gdocs_service(self):
        return self._generate_service(service="docs", version="v1")

    def generate_sheets_service(self):
        return self._generate_service(service="sheets", version="v4")
