# Copyright (C) 2022 Panther Labs Inc
#
# Panther Enterprise is licensed under the terms of a commercial license available from
# Panther Labs Inc ("Panther Commercial License") by contacting contact@runpanther.com.
# All use, distribution, and/or modification of this software, whether commercial or non-commercial,
# falls under the Panther Commercial License to the extent it is permitted.

# coding=utf-8
# *** WARNING: generated file

import os
import json
import base64
import inspect
import datetime
import dataclasses
from typing import List, Dict, Any


@dataclasses.dataclass(frozen=True)
class ConfigNodeOrigin:
    pkg: str = "none"
    name: str = "none"

    def to_dict(self) -> Dict[str, str]:
        return dict(pkg=self.pkg, name=self.name)


def origin_factory() -> ConfigNodeOrigin:
    stack = inspect.stack()
    frame = stack[2]

    if not frame:
        return ConfigNodeOrigin()

    module = inspect.getmodule(frame[0])

    if not module:
        return ConfigNodeOrigin()

    return ConfigNodeOrigin(
        pkg=module.__package__ or "none",
        name=module.__name__ or "none",
    )


@dataclasses.dataclass(frozen=True)
class ConfigNode:
    _origin: ConfigNodeOrigin = dataclasses.field(
        default_factory=origin_factory, init=False
    )

    def __post_init__(self) -> None:
        if self._output_key():
            cache.add(self._output_key(), self)

    def _typename(self) -> str:
        return "ConfigNode"

    def _output_key(self) -> str:
        return ""

    def _fields(self) -> List[str]:
        return []


class _Cache:
    _data: Dict[str, List[ConfigNode]]
    _cache_dir: str
    _cache_file: str

    def __init__(self) -> None:
        self._data = dict()
        self._cache_dir = os.path.abspath(
            os.environ.get("PANTHER_CACHE_DIR") or os.path.join(".", ".panther")
        )
        self.prep_cache_dir()

        cache_file_name = (
            os.environ.get("PANTHER_CONFIG_CACHE_FILENAME") or "panther-config-cache"
        )

        self._cache_file = os.path.join(self._cache_dir, cache_file_name)

        self.prep_cache_file()

    def prep_cache_dir(self) -> None:
        if not os.path.exists(self._cache_dir):
            os.mkdir(self._cache_dir)

    def prep_cache_file(self) -> None:
        with open(self._cache_file, "w") as f:
            pass

    def add(self, key: str, node: ConfigNode) -> None:
        if self._cache_file is None:
            return

        with open(self._cache_file, "a") as f:
            f.write(
                json.dumps(
                    dict(
                        key=key,
                        created_at=datetime.datetime.now(
                            datetime.timezone.utc
                        ).strftime("%Y-%m-%dT%H:%M:%S.%f%z"),
                        panther_config_version="0.0.2",
                        val=to_intermediate(node),
                    )
                )
            )
            f.write("\n")


cache = _Cache()


def to_intermediate(obj: Any) -> Any:
    if isinstance(obj, ConfigNode):
        field_data = dict()

        for field_name in obj._fields():
            field_data[field_name] = to_intermediate(getattr(obj, field_name))

        return dict(
            o=obj._origin.to_dict(),
            t=obj._typename(),
            d=field_data,
        )

    if isinstance(obj, list):
        return [*map(to_intermediate, obj)]

    if callable(obj):
        return base64.b64encode(inspect.getsource(obj).encode("utf-8")).decode("utf-8")

    return obj
