"""
WiP.

Soon.
"""

# region [Imports]

import os
import re
import sys
import json
import queue
import math
import base64
import pickle
import random
import shelve
import dataclasses
import shutil
import asyncio
import logging
import sqlite3
import platform
import importlib
import subprocess
import inspect

from time import sleep, process_time, process_time_ns, perf_counter, perf_counter_ns
from io import BytesIO, StringIO
from abc import ABC, ABCMeta, abstractmethod
from copy import copy, deepcopy
from enum import Enum, Flag, auto, unique
from time import time, sleep
from pprint import pprint, pformat
from pathlib import Path
from string import Formatter, digits, printable, whitespace, punctuation, ascii_letters, ascii_lowercase, ascii_uppercase
from timeit import Timer
from typing import TYPE_CHECKING, Union, Callable, Iterable, Optional, Mapping, Any, IO, TextIO, BinaryIO, Hashable, Generator, Literal, TypeVar, TypedDict, AnyStr
from zipfile import ZipFile, ZIP_LZMA
from datetime import datetime, timezone, timedelta
from tempfile import TemporaryDirectory
from textwrap import TextWrapper, fill, wrap, dedent, indent, shorten
from functools import wraps, partial, lru_cache, singledispatch, total_ordering, cached_property
from importlib import import_module, invalidate_caches
from contextlib import contextmanager, asynccontextmanager, nullcontext, closing, ExitStack, suppress
from statistics import mean, mode, stdev, median, variance, pvariance, harmonic_mean, median_grouped
from collections import Counter, ChainMap, deque, namedtuple, defaultdict
from urllib.parse import urlparse
from importlib.util import find_spec, module_from_spec, spec_from_file_location
from concurrent.futures import ThreadPoolExecutor, ProcessPoolExecutor
from importlib.machinery import SourceFileLoader
import logging
from gidapptools.general_helper.timing import time_func
from tzlocal import get_localzone
from dateutil.tz import UTC
from gidapptools.general_helper.string_helper import StringCaseConverter, StringCase
from gidapptools.gid_logger.enums import LoggingLevel, LoggingSectionAlignment
if TYPE_CHECKING:
    from gidapptools.gid_logger.records import LOG_RECORD_TYPES
# endregion[Imports]

# region [TODO]


# endregion [TODO]

# region [Logging]


# endregion[Logging]

# region [Constants]

THIS_FILE_DIR = Path(__file__).parent.absolute()

# endregion[Constants]


def get_all_func_names(file: Path, recursive: True):
    import ast

    def _get_names(source_file: Path) -> list[str]:
        root = ast.parse(source_file.read_text(encoding='utf-8', errors='ignore'))
        return sorted({node.name for node in ast.walk(root) if isinstance(node, ast.FunctionDef)})

    def _process_item(_item: Path):
        if _item.is_file() and _item.suffix == '.py':
            yield _get_names(_item)
        elif _item.is_dir():
            for _sub_item in _item.iterdir():
                yield from _process_item(_sub_item)
    if recursive is False:
        names = _get_names(file)
    else:
        names = []
        for _names in _process_item(file.parent):
            names += _names
    return sorted(set(names), key=len)


def get_all_module_names(file: Path) -> list[str]:

    def _get_module_name(_item: Path) -> str:
        module_name = _item.stem
        return module_name

    def _process_item(_item: Path):
        if _item.is_file() and _item.suffix == '.py':
            yield _get_module_name(_item)
        elif _item.is_dir():
            for _sub_item in _item.iterdir():
                yield from _process_item(_sub_item)

    if file.is_file():
        file = file.parent
    return sorted({i for i in _process_item(file)}, key=len)


class AbstractLoggingStyleSection:
    default_alignment: LoggingSectionAlignment = LoggingSectionAlignment.LEFT
    default_width: int = 0
    default_text: str = str(None)

    def __init__(self,
                 position: int = None,
                 width: int = None,
                 alignment: Union[LoggingSectionAlignment, str] = None) -> None:
        self.position = position
        self.width = width or self.default_width
        if alignment is None:
            self.alignment = self.default_alignment
        elif isinstance(alignment, str):
            self.alignment = LoggingSectionAlignment(alignment)
        else:
            self.alignment = alignment

    @abstractmethod
    def get_formated_value(self, record: "LOG_RECORD_TYPES") -> str:
        ...

    def align(self, text: str) -> str:
        width = self.width() if callable(self.width) else self.width
        if len(text) > width:
            text = '...' + text[-(width - 3):]
        return self.alignment.align(text, width)

    def format(self, record: "LOG_RECORD_TYPES") -> str:
        try:
            text = str(self.get_formated_value(record=record))
        except AttributeError:
            text = self.default_text
        return self.align(text)

    @classmethod
    def ___section_name___(cls) -> str:
        return StringCaseConverter.convert_to(cls.__name__.removesuffix("Section"), StringCase.SNAKE)

    def __repr__(self) -> str:
        return f"{self.__class__.__name__}(position={self.position!r})"


class TimeSection(AbstractLoggingStyleSection):

    local_timezone: timezone = get_localzone()
    default_time_format = '%Y-%m-%d %H:%M:%S'
    default_msec_format = '.{msec:03.0f}'
    default_alignment: LoggingSectionAlignment = LoggingSectionAlignment.LEFT

    def __init__(self,
                 position: int = None,
                 time_zone: timezone = None,
                 time_format: str = None,
                 msec_format: str = None,
                 width: int = None,
                 alignment: Union[LoggingSectionAlignment, str] = None) -> None:
        super().__init__(position=position, alignment=alignment)

        self.time_zone = time_zone
        self.time_format = time_format or self.default_time_format
        self.msec_format = msec_format or self.default_msec_format
        self.width = width or (len(self.time_format) + len(self.msec_format.format(msec=0)) + 2)

    def get_formated_value(self, record: "LOG_RECORD_TYPES") -> str:
        time_value = datetime.fromtimestamp(record.created, tz=self.local_timezone)
        msec_value = record.msecs
        if msec_value == 1000:
            msec_value = 0
            time_value = time_value + timedelta(seconds=1)
        if self.time_zone is not None:
            time_value = time_value.replace(tzinfo=self.time_zone)
        if self.time_format.casefold() == "isoformat":
            return time_value.isoformat(timespec='seconds') + self.msec_format.format(msec=record.msecs)
        return time_value.strftime(self.time_format) + self.msec_format.format(msec=record.msecs)


class LevelSection(AbstractLoggingStyleSection):
    default_case: StringCase = StringCase.UPPER
    default_alignment: LoggingSectionAlignment = LoggingSectionAlignment.CENTER
    default_width: int = max(len(i) for i in LoggingLevel._member_names_)

    def __init__(self,
                 position: int = None,
                 case: StringCase = None,
                 width: int = None,
                 alignment: Union[LoggingSectionAlignment, str] = None) -> None:
        super().__init__(position=position, alignment=alignment, width=width)
        if case is None:
            self.case = self.default_case
        elif isinstance(case, str):
            self.case = StringCase(case)
        else:
            self.case = case

    def get_formated_value(self, record: "LOG_RECORD_TYPES") -> str:
        return StringCaseConverter.convert_to(record.levelname, self.case)


class ThreadSection(AbstractLoggingStyleSection):
    default_alignment: LoggingSectionAlignment = LoggingSectionAlignment.CENTER
    default_width: int = 20

    def get_formated_value(self, record: "LOG_RECORD_TYPES") -> str:
        return record.threadName


class LineNumberSection(AbstractLoggingStyleSection):
    default_alignment: LoggingSectionAlignment = LoggingSectionAlignment.CENTER
    default_width: int = 5

    def get_formated_value(self, record: "LOG_RECORD_TYPES") -> str:
        return record.lineno


class PathSection(AbstractLoggingStyleSection):
    default_alignment: LoggingSectionAlignment = LoggingSectionAlignment.LEFT

    def __init__(self, position: int = None,
                 depth: int = 2,
                 with_extension: bool = False,
                 width: int = None,
                 alignment: Union[LoggingSectionAlignment, str] = None) -> None:
        super().__init__(position=position, width=self.width_from_env, alignment=alignment)
        self.with_extension = with_extension
        self.depth = depth
        self._width_cache: int = None

    def width_from_env(self) -> int:
        if self._width_cache is None:
            self._width_cache = int(os.getenv("MAX_MODULE_NAME_LEN", "20"))
        return self._width_cache

    def get_formated_value(self, record: "LOG_RECORD_TYPES") -> str:
        path = Path(record.pathname)
        if self.with_extension is False:
            path = path.with_suffix('')
        return '.'.join(part for part in path.parts[-self.depth:])


class FunctionNameSection(AbstractLoggingStyleSection):
    default_alignment: LoggingSectionAlignment = LoggingSectionAlignment.LEFT
    default_width: int = int(os.getenv("MAX_FUNC_NAME_LEN", "10"))
    default_text: str = "-"

    def __init__(self) -> None:
        super().__init__(width=self.width_from_env)
        self._width_cache: int = None

    def width_from_env(self) -> int:
        if self._width_cache is None:
            self._width_cache = int(os.getenv("MAX_FUNC_NAME_LEN", "10"))
        return self._width_cache

    def get_formated_value(self, record: "LOG_RECORD_TYPES") -> str:
        if record.funcName == "<module>":
            return self.default_text
        return record.funcName


class AbstractSectionLoggingStyle(ABC):
    default_separator: str = " | "
    default_message_start_indicator: str = ' ||--> '

    def __init__(self, sections: Iterable[Union[str, AbstractLoggingStyleSection]], separator: str = None, message_start_indicator: str = None, message_section: AbstractLoggingStyleSection = None):
        self.sections = sections
        self.sorted_sections: tuple[AbstractLoggingStyleSection] = self.sort_sections()
        self.separator = separator or self.default_separator
        self.message_start_indicator = message_start_indicator or self.default_message_start_indicator
        self.message_section = message_section

    def sort_sections(self) -> tuple[AbstractLoggingStyleSection]:
        temp_sorted = [i[0] for i in sorted([(v, idx) for idx, v in enumerate(self.sections)], key=lambda x: (x[0].position, x[1]))]
        for idx, sect in enumerate(temp_sorted):
            if sect.position is None:
                sect.position = idx
        return tuple(temp_sorted)

    def usesTime(self) -> bool:
        return False

    @abstractmethod
    def validate(self) -> None:
        ...

    @abstractmethod
    def _format(self, record: "LOG_RECORD_TYPES") -> str:
        ...

    def format(self, record: "LOG_RECORD_TYPES"):
        return self._format(record)


class GidSectionLoggingStyle(AbstractSectionLoggingStyle):

    def validate(self) -> None:
        if any(isinstance(i, AbstractLoggingStyleSection) is False for i in self.sections):
            raise ValueError("invalid format")

    def _format(self, record: "LOG_RECORD_TYPES") -> str:

        message_part = record.getMessage() if self.message_section is None else self.message_section.format(record)

        return self.separator.join(section.format(record) for section in self.sorted_sections) + self.message_start_indicator + message_part


class GidLoggingFormatter(logging.Formatter):
    default_fmt = (TimeSection(), LineNumberSection(), LevelSection(), ThreadSection(), PathSection(), FunctionNameSection())

    def __init__(self,
                 fmt: Union[str, Iterable[Union[str, AbstractLoggingStyleSection]]] = None,
                 style: Literal["section", "$", "{", "$"] = "section",
                 validate: bool = False,
                 **kwargs) -> None:
        if style == "section":
            if fmt is None:
                fmt = self.default_fmt
            self._style = GidSectionLoggingStyle(sections=fmt, separator=kwargs.pop("separator", None), message_start_indicator=kwargs.pop("message_start_indicator", None), message_section=kwargs.pop("message_section", None))
        else:
            self._style = logging._STYLES[style][0](fmt)
            if validate:
                self._style.validate()

            self._fmt = self._style._fmt
            self.datefmt = kwargs.pop("datefmt", None)

    def set_time(self, record: "LOG_RECORD_TYPES") -> "LOG_RECORD_TYPES":
        return record

    def format_message(self, record: "LOG_RECORD_TYPES") -> str:
        return self.formatMessage(record=record)

    def format_stack(self, stack_info):
        return self.formatStack(stack_info=stack_info)

    def format_exception(self, record: "LOG_RECORD_TYPES") -> "LOG_RECORD_TYPES":
        if record.exc_info:
            if not record.exc_text:
                record.exc_text = self.formatException(ei=record.exc_info)
        return record

    def format(self, record: "LOG_RECORD_TYPES") -> str:
        record = self.set_time(record=record)
        text = self.format_message(record=record)
        record = self.format_exception(record=record)
        if record.exc_text:
            if text[-1:] != "\n":
                text = text + "\n"
            text = text + record.exc_text
        if record.stack_info:
            if text[-1:] != "\n":
                text = text + "\n"
            text = text + self.format_stack(record.stack_info)
        return text

# region[Main_Exec]


if __name__ == '__main__':
    pprint(get_all_module_names(Path(r"D:\Dropbox\hobby\Modding\Programs\Github\My_Repos\GidAppTools\gidapptools\__main__.py")))
# endregion[Main_Exec]
