# Copyright (c) 2021, VRAI Labs and/or its affiliates. All rights reserved.
#
# This software is licensed under the Apache License, Version 2.0 (the
# "License") as published by the Apache Software Foundation.
#
# You may not use this file except in compliance with the License. You may
# obtain a copy of the License at http://www.apache.org/licenses/LICENSE-2.0
#
# Unless required by applicable law or agreed to in writing, software
# distributed under the License is distributed on an "AS IS" BASIS, WITHOUT
# WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the
# License for the specific language governing permissions and limitations
# under the License.

from typing import List, Optional
from typing_extensions import Literal
from supertokens_python.recipe_module import RecipeModule
from supertokens_python.types import RecipeUserId
from .plugins import LoadPluginsResponse
from .supertokens import (
    AppInfo,
    InputAppInfo,
    RecipeInit,
    Supertokens,
    SupertokensConfig,
    SupertokensExperimentalConfig,
    SupertokensInputConfig,
    SupertokensPublicConfig,
    get_request_from_user_context,
    is_recipe_initialized,
)

# Some Pydantic models need a rebuild to resolve ForwardRefs
# Referencing imports here to prevent lint errors.
# Caveat: These will be available for import from this module directly.
RecipeModule  # type: ignore

# LoadPluginsResponse -> SupertokensPublicConfig
LoadPluginsResponse.model_rebuild()
# SupertokensInputConfig -> RecipeModule
SupertokensInputConfig.model_rebuild()

def init(
    app_info: InputAppInfo,
    framework: Literal["fastapi", "flask", "django"],
    supertokens_config: SupertokensConfig,
    recipe_list: List[RecipeInit],
    mode: Optional[Literal["asgi", "wsgi"]] = None,
    telemetry: Optional[bool] = None,
    debug: Optional[bool] = None,
    experimental: Optional[SupertokensExperimentalConfig] = None,
):
    return Supertokens.init(
        app_info,
        framework,
        supertokens_config,
        recipe_list,
        mode,
        telemetry,
        debug,
        experimental=experimental,
    )

def get_all_cors_headers() -> List[str]:
    return Supertokens.get_instance().get_all_cors_headers()

def convert_to_recipe_user_id(user_id: str) -> RecipeUserId:
    return RecipeUserId(user_id)

__all__ = [
    "AppInfo",
    "InputAppInfo",
    "RecipeInit",
    "RecipeUserId",
    "Supertokens",
    "SupertokensConfig",
    "SupertokensExperimentalConfig",
    "SupertokensPublicConfig",
    "convert_to_recipe_user_id",
    "get_all_cors_headers",
    "get_request_from_user_context",
    "init",
    "is_recipe_initialized",
]

class AppInfo:
    def __init__(self,
        app_name: str,
        api_domain: str,
        website_domain: Optional[str],
        framework: Literal["fastapi", "flask", "django"],
        api_gateway_path: str,
        api_base_path: str,
        website_base_path: str,
        mode: Union[Literal["asgi", "wsgi"], None],
        origin: Optional[Union[str, Callable[[Optional[BaseRequest], Dict[str, Any]], str]]],
    ):
        self.app_name = app_name
        self.api_gateway_path = NormalisedURLPath(api_gateway_path)
        self.api_domain = NormalisedURLDomain(api_domain)
        self.top_level_api_domain = get_top_level_domain_for_same_site_resolution(
            self.api_domain.get_as_string_dangerous()
        )
        if website_domain is None and origin is None:
            raise_general_exception(
                "Please provide at least one of website_domain or origin"
            )
        self.__origin = origin
        self.__website_domain = website_domain
        self.api_base_path = self.api_gateway_path.append(
            NormalisedURLPath(api_base_path)
        )
        self.website_base_path = NormalisedURLPath(website_base_path)
        if mode is not None:
            self.mode = mode
        elif framework == "fastapi":
            mode = "asgi"
        else:
            mode = "wsgi"
        self.framework = framework
        self.mode = mode

def get_top_level_website_domain(
        self, request: Optional[BaseRequest], user_context: UserContext
    ) -> str:
        return get_top_level_domain_for_same_site_resolution(
            self.get_origin(request, user_context).get_as_string_dangerous()
        )

def get_origin(self, request: Optional[BaseRequest], user_context: UserContext):
        origin = self.__origin
        if origin is None:
            origin = self.__website_domain

# This should not be possible because we check for either origin or websiteDomain above
        if origin is None:
            raise_general_exception("should never come here")

if callable(origin):
            origin = origin(request, user_context)

return NormalisedURLDomain(origin)

def toJSON(self):
        def defaultImpl(o: Any):
            if isinstance(o, (NormalisedURLDomain, NormalisedURLPath)):
                return o.get_as_string_dangerous()
            return o.__dict__

return json.dumps(self, default=defaultImpl, sort_keys=True, indent=4)

class InputAppInfo:
    def __init__(self,
        app_name: str,
        api_domain: str,
        api_gateway_path: str = "",
        api_base_path: str = "/auth",
        website_base_path: str = "/auth",
        website_domain: Optional[str] = None,
        origin: Optional[Union[str, Callable[[Optional[BaseRequest], Dict[str, Any]], str]]] = None,
    ):
        self.app_name = app_name
        self.api_gateway_path = api_gateway_path
        self.api_domain = api_domain
        self.website_domain = website_domain
        self.origin = origin
        self.api_base_path = api_base_path
        self.website_base_path = website_base_path

class RecipeInit(Protocol):
    def __call__(self, app_info: AppInfo, plugins: List[OverrideMap]) -> RecipeModule: ...

class RecipeUserId:
    def __init__(self, recipe_user_id: str):
        self.recipe_user_id = recipe_user_id

def get_as_string(self) -> str:
        return self.recipe_user_id

def __eq__(self, other: Any) -> bool:
        if isinstance(other, RecipeUserId):
            return self.recipe_user_id == other.recipe_user_id
        return False

class Supertokens:
    __instance: Optional[Supertokens] = None

recipe_modules: List[RecipeModule]

app_info: AppInfo

supertokens_config: SupertokensConfig

_telemetry_status: str

telemetry: bool

def __init__(
        self,
        app_info: InputAppInfo,
        framework: Literal["fastapi", "flask", "django"],
        supertokens_config: SupertokensConfig,
        recipe_list: List[RecipeInit],
        mode: Optional[Literal["asgi", "wsgi"]],
        telemetry: Optional[bool],
        debug: Optional[bool],
        experimental: Optional[SupertokensExperimentalConfig] = None,
    ):
        if not isinstance(app_info, InputAppInfo):  # type: ignore
            raise ValueError("app_info must be an instance of InputAppInfo")

self.app_info = AppInfo(
            app_info.app_name,
            app_info.api_domain,
            app_info.website_domain,
            framework,
            app_info.api_gateway_path,
            app_info.api_base_path,
            app_info.website_base_path,
            mode,
            app_info.origin,
        )

input_config = SupertokensInputConfig(
            app_info=app_info,
            framework=framework,
            supertokens_config=supertokens_config,
            recipe_list=recipe_list,
            mode=mode,
            telemetry=telemetry,
            debug=debug,
            experimental=experimental,
        )

self.recipe_modules: List[RecipeModule] = list(map(make_recipe, recipe_list))

if not account_linking_found:
            from supertokens_python.recipe.accountlinking.recipe import (
                AccountLinkingRecipe,
            )

self.recipe_modules.append(
                AccountLinkingRecipe.init()(self.app_info, override_maps)
            )

if not jwt_found:
            from supertokens_python.recipe.jwt.recipe import JWTRecipe

self.recipe_modules.append(JWTRecipe.init()(self.app_info, override_maps))

if not openid_found:
            from supertokens_python.recipe.openid.recipe import OpenIdRecipe

self.recipe_modules.append(
                OpenIdRecipe.init()(self.app_info, override_maps)
            )

if not multitenancy_found:
            from supertokens_python.recipe.multitenancy.recipe import MultitenancyRecipe

self.recipe_modules.append(
                MultitenancyRecipe.init()(self.app_info, override_maps)
            )

if not user_metadata_found:
            from supertokens_python.recipe.usermetadata.recipe import UserMetadataRecipe

self.recipe_modules.append(
                UserMetadataRecipe.init()(self.app_info, override_maps)
            )

if not oauth2_found:
            from supertokens_python.recipe.oauth2provider.recipe import (
                OAuth2ProviderRecipe,
            )

self.recipe_modules.append(
                OAuth2ProviderRecipe.init()(self.app_info, override_maps)
            )

class SupertokensConfig:
    def __init__(self,
        connection_uri: str,
        api_key: Union[str, None] = None,
        network_interceptor: Optional[
            Callable[
                [
                    str,
                    str,
                    Dict[str, Any],
                    Optional[Dict[str, Any]],
                    Optional[Dict[str, Any]],
                    Optional[Dict[str, Any]],
                ],
                Tuple[
                    str,
                    str,
                    Dict[str, Any],
                    Optional[Dict[str, Any]],
                ],
            ]
        ] = None,
        disable_core_call_cache: bool = False,
    ):  
        self.connection_uri = connection_uri
        self.api_key = api_key
        self.network_interceptor = network_interceptor
        self.disable_core_call_cache = disable_core_call_cache

class SupertokensExperimentalConfig(CamelCaseBaseModel):
    plugins: Optional[List["SuperTokensPlugin"]] = None

class SupertokensPublicConfig(_BaseSupertokensPublicConfig):
    """
    Public properties received as input to the `Supertokens.init` function.
    """
    pass
