from dataclasses import dataclass, field
import os
from typing import Any, NewType, Optional, TypedDict, Union

from jinja2.runtime import Macro

RENDER_DEPTH_MAX = int(os.getenv("RENDER_DEPTH_MAX", "15"))

# A unique-to-the-text deterministic identifier for a given package - functions as a package id
PackageAlias = NewType("PackageAlias", str)
# A uuid for a given package release - a given package can have n releases
PackageReleaseId = NewType("PackageReleaseId", str)
# The name of a macro
MacroName = NewType("MacroName", str)
# A macro namespace is a collection of macros.
MacroNamespace = dict[MacroName, Macro]


@dataclass
class PackageSpec:
    """The specification of a package for rendering"""

    release_id: PackageReleaseId
    alias: PackageAlias
    version: str
    macros: str
    dependencies: dict[PackageAlias, PackageReleaseId]


class Reference(TypedDict):
    """References are defined in a particular dict-based format"""

    location_name: str
    node_name: str


@dataclass
class RefRenderingResult:
    """A dataclass representing all the different ref usages that are rendered and their various categorizations"""

    exported_refs: list[Reference]
    invalid_exported_refs: list[Reference]
    ref: list[Reference]
    ref_no_link: list[Reference]
    ref_link: list[Reference]
    invalid_ref: list[Reference]
    invalid_ref_no_link: list[Reference]
    invalid_ref_link: list[Reference]


class RefUsageDetails(TypedDict):
    """The items returned in 'ref_usage', represents more detailed ref usage information"""

    ref: list[Reference]
    ref_no_link: list[Reference]
    ref_link: list[Reference]
    invalid_ref: list[Reference]
    invalid_ref_no_link: list[Reference]
    invalid_ref_link: list[Reference]


class RenderStats(TypedDict):
    """The stats returned in 'render_stats' - the depth of the search as a string and the duration in milliseconds"""

    depth: str
    duration_ms: int


class RenderingResultRunDetails(TypedDict):
    """The runDetails part of the result"""

    compiledSQL: str
    exported_refs: list[Reference]
    invalid_exported_refs: list[Reference]
    ref_usage: RefUsageDetails


class RenderingResultSuccess(TypedDict):
    """The full render result returned by the rendering process"""

    runDetails: RenderingResultRunDetails
    render_stats: RenderStats


class RenderingResultErrorData(TypedDict):
    """A rendering result error"""

    errorString: str
    errorDetail: str


class RenderingResultFailure(TypedDict):
    """Rendering failures have an error, and might have an empty runDetails"""

    error: RenderingResultErrorData
    # These are optional but can only ever be None. they're ignored if an error is present.
    runDetails: Optional[None]


RenderingResult = Union[RenderingResultSuccess, RenderingResultFailure]


PackageDependenciesDict = dict[PackageAlias, Union["PackageDefinitionDict", PackageReleaseId]]


class PackageDefinitionDict(TypedDict):
    """A package definition, as a dict"""

    # this is not necessarily related to any PackageName in dependencies. However, we should keep any restrictions the
    # same for both this and the values of "dependencies".
    releaseID: PackageReleaseId
    alias: PackageAlias
    version: str
    # the macros as a string
    macros: str
    dependencies: PackageDependenciesDict


class RenderRequestDict(TypedDict):
    """A render request, as a dict"""

    templateString: str
    dependencies: Optional[PackageDependenciesDict]
    userContext: Optional[dict[str, Any]]
    templateVars: Optional[dict[str, Any]]
    globalMacros: Optional[str]


@dataclass
class PackageDefinition:
    """A package definition, as a python object"""

    # this is not necessarily related to any PackageName in dependencies. However, we should keep any restrictions the
    # same for both this and the values of "dependencies".
    release_id: PackageReleaseId
    alias: PackageAlias
    version: str
    # the macros as a string
    macros: str
    dependencies: dict[PackageAlias, Union["PackageDefinition", PackageReleaseId]]


@dataclass
class RenderRequest:
    """A render request, as a python object"""

    template: str
    request_id: Optional[str] = None  # this is really only optional because we don't care about it on the Python side.
    dependencies: dict[PackageAlias, Union["PackageDefinition", PackageReleaseId]] = field(default_factory=dict)
    user_context: dict[str, Any] = field(default_factory=dict)
    template_vars: dict[str, Any] = field(default_factory=dict)
    global_macros: str = field(default="")
    render_depth: int = field(default=RENDER_DEPTH_MAX)
    render_platform_kind: str = field(default="snowflake")
