#!/usr/bin/env python3
# -*- coding: utf-8 -*-

from typing import Self, Any, Optional
from Managers.SettingsManager import SettingsManager
from Managers.I18NManager import I18NManager
from Drivers.HTTPSocketDriver import HTTPSocketDriver
from Drivers.FilesDriver import FilesDriver
from traceback import format_stack as trace_format_stack
from traceback import extract_tb as extract_traceback
from Managers.QuestionsManager import QuestionsManager
from Managers.UsersManager import UsersManager
from Modules.FormatModule import FormatModule
from Managers.ControllersManager import ControllersManager
from Managers.RoutesManager import RoutesManager
from Utils.Check import Check
from Utils.Patterns import RE
from Utils.Utils import Utils
from datetime import datetime
from re import Match as REMatch

class OpoTests:

    def __init__(self:Self, inputs:Optional[dict[str, Any|None]|list[Any|None]|tuple[Any|None]] = None) -> None:

        key:str

        self.__print_format:str = "[{type}] {yyyy}{mm}{dd} {hh}{ii}{ss} [{line}]{file}({method}): {message}"
        self.__exception_format:str = " '{file}({method})[{line}]'{lines}\n\n{exception_message}"

        self.settings:SettingsManager = SettingsManager(self, inputs)

        self.__print_format = self.settings.get("print_format", None, self.__print_format)
        self.__exception_format = self.settings.get("exception_format", None, self.__exception_format)

        self.i18n:I18NManager = I18NManager(self)
        self.files:FilesDriver = FilesDriver(self)
        self.http_server:HTTPSocketDriver = HTTPSocketDriver(self)
        self.users:UsersManager = UsersManager(self)
        self.questions:QuestionsManager = QuestionsManager(self)
        self.format:FormatModule = FormatModule(self)
        self.controllers:ControllersManager = ControllersManager(self)
        self.routes:RoutesManager = RoutesManager(self)

        for key in ("settings", "i18n", "files", "users", "questions", "format", "controllers", "routes", "http_server"):
            getattr(getattr(self, key), "start")()

    def print(self:Self, 
        _type:str, 
        string:str, 
        inputs:Optional[dict[str, Any|None]|list[Any|None]|tuple[Any|None]] = None, 
        default_message:str = None, 
        i:int = 0
    ) -> None:

        date:datetime = datetime.now()
        own:dict[str, Any|None] = {
            "raw_type" : _type,
            "type" : _type.upper()[:4],
            "i18n" : Utils.get_strings(string)[0],
            "message" : self.i18n.get(string, inputs, default_message),
            **Utils.get_action_data(i + 1),
            **Utils.get_dictionary(inputs)
        }

        while len(own["type"]) < 4:
            own["type"] = " " + own["type"] if len(own["type"]) % 2 else own["type"] + " "

        for key in ("year", "month", "day", "hour", "minute", "second"):

            k:str = key[0] if key != "minute" else "i"

            own[k] = own[key] = getattr(date, key)
            own[k + k] = ("00" + str(own[key] % 100))[-2:]

        own["yyyy"] = ("0000" + str(own["year"]))[-4:]

        if "end" in own:
            own["message"] += own["end"]

        print(Utils.string_variables(self.__print_format, own))
        
    def exception(self, 
        exception:Exception, 
        message:Optional[str|list|tuple] = None, 
        variables:Optional[dict[str, Any]|list|tuple] = None, 
        i:Optional[int] = 1
    ) -> None:

        lines:list[str]|None = extract_traceback(exception.__traceback__).format()
        line_matches:REMatch[str]|None = RE.EXCEPTION.match(lines[-1])
        key:str
        value:Any|None
        block:str|None
        data:dict[str, Any|None] = {
            **{key : value for subset in (variables if isinstance(variables, (list, tuple)) else (variables,)) for key, value in (subset if isinstance(subset, dict) else {}).items()}, 
            **Utils.get_action_data(1),
            "lines" : "",
            "exception_message" : str(exception), 
            "method" : line_matches.group(3), 
            "line" : line_matches.group(2), 
            "file" : line_matches.group(1)
        }

        for block in trace_format_stack()[:-2] + lines:
            if block:
                data["lines"] += "\n    " + RE.BREAK_LINES.split(block.strip())[0]

        data["end"] = Utils.string_variables(self.__exception_format, data)
                
        message and self.print("exception", message, data, None, i + 1)

    def load_json_data(self:Self, 
        inputs:Optional[str|dict[str, Any|None]|list[Any|None]|tuple[Any|None]], 
        only_dictionaries:bool = True
    ) -> list[dict[str, Any|None]|list[Any|None]|tuple[Any|None]]:

        json_items:list[dict[str, Any|None]|list[Any|None]|tuple[Any|None]] = []

        if Check.is_dictionary(inputs):
            json_items += [inputs]
        elif Check.is_array(inputs):
            if only_dictionaries:
                for item in inputs:
                    if Check.is_dictionary(item):
                        json_items += [item]
                    else:
                        json_items += self.load_json_data(item, only_dictionaries)
            else:
                json_items += [inputs]
        elif Check.is_string(inputs):
            json_items += self.load_json_data(Utils.json_decode(inputs) or self.files.load(inputs, True), only_dictionaries)

        return json_items