from logging import getLogger
from typing import Any, Union
import json

from .apptypes import (
    PackageDefinition,
    PackageDefinitionDict,
    PackageDependenciesDict,
    PackageAlias,
    PackageReleaseId,
    RenderRequest,
    RENDER_DEPTH_MAX,
)
from .errors import RendererError, MissingTemplateString, WrongTypeError

LOGGER = getLogger(__name__)


def read_dependencies_definition(
    dct: PackageDependenciesDict,
) -> dict[PackageAlias, Union[PackageDefinition, PackageReleaseId]]:
    """Handle reading the 'dependencies' part of a package definition"""
    return {k: v if isinstance(v, str) else read_package_definition(v) for k, v in dct.items()}


def read_package_definition(dct: PackageDefinitionDict) -> PackageDefinition:
    """Read a package definition into a real object"""
    try:
        deps = read_dependencies_definition(dct["dependencies"])
        return PackageDefinition(
            release_id=dct["releaseID"],
            alias=dct["alias"],
            version=dct["version"],
            macros=dct["macros"],
            dependencies=deps,
        )
    except KeyError as exc:
        if exc.args[0] == "templateString":
            raise MissingTemplateString() from None
        raise RendererError(f"invalid inputs, missing key {exc}", json.dumps(dct)) from None


def read_renderer_request(dct: dict[str, Any]) -> RenderRequest:
    """Read a renderer request into a real object"""
    allowed_keys = {
        "id",
        "templateString",
        "dependencies",
        "userContext",
        "globalMacros",
        "templateVars",
        "renderDepth",
        "renderPlatformKind",
    }
    try:
        request_id = dct.get("id", "<no request id>")
        template = dct["templateString"]
        if not isinstance(template, str):
            raise WrongTypeError("templateString", "str", str(type(template)))
        # use `.get(x) or y` instead of `.get(x, y)` to support explicilty setting `None` for optionals, which is
        # allowed by the type spec
        dependencies = read_dependencies_definition(dct.get("dependencies") or {})
        user_context = dct.get("userContext") or {}
        render_platform_kind = dct.get("renderPlatformKind") or "snowflake"
        global_macros = dct.get("globalMacros") or ""
        if not isinstance(global_macros, str):
            raise WrongTypeError("glboalMacros", "str", str(type(global_macros)))
        template_vars = dct.get("templateVars") or {}
        render_depth: int = dct.get("renderDepth", RENDER_DEPTH_MAX)
        if not isinstance(render_depth, int):
            raise WrongTypeError("renderDepth", "int", str(type(render_depth)))

        extra_keys = {k: v for k, v in dct.items() if k not in allowed_keys}
        if extra_keys:
            # I'm not sure what to do here.
            LOGGER.warning(f"got extra data: {extra_keys!r}")
        return RenderRequest(
            request_id=request_id,
            template=template,
            dependencies=dependencies,
            user_context=user_context,
            template_vars=template_vars,
            global_macros=global_macros,
            render_depth=render_depth,
            render_platform_kind=render_platform_kind,
        )
    except KeyError as exc:
        if exc.args[0] == "templateString":
            raise MissingTemplateString() from None
        raise RendererError(f"invalid inputs, missing key {exc}", json.dumps(dct)) from None
