import io
import json
import re
import time
import traceback
import unittest
from concurrent.futures import ThreadPoolExecutor, as_completed
from contextlib import redirect_stdout, redirect_stderr
from datetime import datetime
from pathlib import Path
from typing import Any, Dict, List, Optional, Tuple
from orionis.container.resolver.resolver import Resolver
from orionis.foundation.config.testing.enums.drivers import PersistentDrivers
from orionis.foundation.config.testing.enums.mode import ExecutionMode
from orionis.foundation.config.testing.enums.verbosity import VerbosityMode
from orionis.foundation.contracts.application import IApplication
from orionis.services.introspection.instances.reflection import ReflectionInstance
from orionis.test.contracts.test_result import IOrionisTestResult
from orionis.test.contracts.unit_test import IUnitTest
from orionis.test.entities.result import TestResult
from orionis.test.enums import TestStatus
from orionis.test.exceptions import (
    OrionisTestFailureException,
    OrionisTestPersistenceError,
    OrionisTestValueError,
)
from orionis.test.output.printer import TestPrinter
from orionis.test.records.logs import TestLogs
from orionis.test.validators import (
    ValidExecutionMode,
    ValidFailFast,
    ValidPersistent,
    ValidPersistentDriver,
    ValidPrintResult,
    ValidThrowException,
    ValidVerbosity,
    ValidWebReport,
    ValidWorkers,
    ValidBasePath,
    ValidFolderPath,
    ValidNamePattern,
    ValidPattern,
    ValidTags,
    ValidModuleName,
)
from orionis.test.view.render import TestingResultRender

class UnitTest(IUnitTest):
    """
    Orionis UnitTest

    Advanced unit testing manager for the Orionis framework.

    This class provides mechanisms for discovering, executing, and reporting unit tests with extensive configurability. It supports sequential and parallel execution, test filtering by name or tags, and detailed result tracking including execution times, error messages, and tracebacks.

    Attributes
    ----------
    __app : Optional[IApplication]
        Application instance for dependency injection.
    __verbosity : Optional[int]
        Verbosity level for test output.
    __execution_mode : Optional[str]
        Execution mode for tests ('SEQUENTIAL' or 'PARALLEL').
    __max_workers : Optional[int]
        Maximum number of workers for parallel execution.
    __fail_fast : Optional[bool]
        Whether to stop on first failure.
    __throw_exception : Optional[bool]
        Whether to raise exceptions on test failures.
    __persistent : Optional[bool]
        Whether to persist test results.
    __persistent_driver : Optional[str]
        Persistence driver ('sqlite' or 'json').
    __web_report : Optional[bool]
        Whether to generate a web report.
    __folder_path : Optional[str]
        Folder path for test discovery.
    __base_path : Optional[str]
        Base directory for test discovery.
    __pattern : Optional[str]
        File name pattern for test discovery.
    __test_name_pattern : Optional[str]
        Pattern for filtering test names.
    __tags : Optional[List[str]]
        Tags for filtering tests.
    __module_name : Optional[str]
        Module name for test discovery.
    __loader : unittest.TestLoader
        Loader for discovering tests.
    __suite : unittest.TestSuite
        Test suite containing discovered tests.
    __discovered_tests : List
        List of discovered test metadata.
    __printer : Optional[TestPrinter]
        Utility for printing test results.
    __output_buffer : Optional[str]
        Buffer for capturing standard output.
    __error_buffer : Optional[str]
        Buffer for capturing error output.
    __result : Optional[dict]
        Result summary of the test execution.
    """

    def __init__(
        self
    ) -> None:
        """
        Initialize a UnitTest instance with default configuration and internal state.

        Sets up all internal attributes required for test discovery, execution, result reporting, and configuration management. Does not perform test discovery or execution.

        Returns
        -------
        None
        """
        # Application instance for dependency injection (set via __setApp)
        self.__app: Optional[IApplication] = None

        # Storage path for test results (set via __setApp)
        self.__storage: Optional[str] = None

        # Configuration values (set via configure)
        self.__verbosity: Optional[int] = None
        self.__execution_mode: Optional[str] = None
        self.__max_workers: Optional[int] = None
        self.__fail_fast: Optional[bool] = None
        self.__throw_exception: Optional[bool] = None
        self.__persistent: Optional[bool] = None
        self.__persistent_driver: Optional[str] = None
        self.__web_report: Optional[bool] = None

        # Test discovery parameters for folders
        self.__folder_path: Optional[str] = None
        self.__base_path: Optional[str] = None
        self.__pattern: Optional[str] = None
        self.__test_name_pattern: Optional[str] = None
        self.__tags: Optional[List[str]] = None

        # Test discovery parameter for modules
        self.__module_name: Optional[str] = None

        # Initialize the unittest loader and suite for test discovery and execution
        self.__loader = unittest.TestLoader()
        self.__suite = unittest.TestSuite()
        self.__discovered_tests: List = []

        # Printer for console output (set during configuration)
        self.__printer: TestPrinter = None

        # Buffers for capturing standard output and error during test execution
        self.__output_buffer = None
        self.__error_buffer = None

        # Stores the result summary after test execution
        self.__result = None

    def configure(
        self,
        *,
        verbosity: int | VerbosityMode,
        execution_mode: str | ExecutionMode,
        max_workers: int,
        fail_fast: bool,
        print_result: bool,
        throw_exception: bool,
        persistent: bool,
        persistent_driver: str | PersistentDrivers,
        web_report: bool
    ) -> 'UnitTest':
        """
        Configure the UnitTest instance with execution and reporting parameters.

        Parameters
        ----------
        verbosity : int or VerbosityMode
            Verbosity level for test output.
        execution_mode : str or ExecutionMode
            Execution mode ('SEQUENTIAL' or 'PARALLEL').
        max_workers : int
            Maximum number of workers for parallel execution.
        fail_fast : bool
            Whether to stop on the first failure.
        print_result : bool
            Whether to print results to the console.
        throw_exception : bool
            Whether to raise exceptions on test failures.
        persistent : bool
            Whether to enable result persistence.
        persistent_driver : str or PersistentDrivers
            Persistence driver ('sqlite' or 'json').
        web_report : bool
            Whether to enable web report generation.

        Returns
        -------
        UnitTest
            The configured UnitTest instance.

        Raises
        ------
        OrionisTestValueError
            If any parameter is invalid.
        """
        # Validate and assign parameters using specialized validators
        self.__verbosity = ValidVerbosity(verbosity)
        self.__execution_mode = ValidExecutionMode(execution_mode)
        self.__max_workers = ValidWorkers(max_workers)
        self.__fail_fast = ValidFailFast(fail_fast)
        self.__throw_exception = ValidThrowException(throw_exception)
        self.__persistent = ValidPersistent(persistent)
        self.__persistent_driver = ValidPersistentDriver(persistent_driver)
        self.__web_report = ValidWebReport(web_report)

        # Initialize the result printer with the current configuration
        self.__printer = TestPrinter(
            print_result = ValidPrintResult(print_result)
        )

        # Return the instance to allow method chaining
        return self

    def discoverTestsInFolder(
        self,
        *,
        base_path: str | Path,
        folder_path: str,
        pattern: str,
        test_name_pattern: Optional[str] = None,
        tags: Optional[List[str]] = None
    ) -> 'UnitTest':
        """
        Discover and add unit tests from a specified folder to the test suite.

        Parameters
        ----------
        base_path : str or Path
            Base directory for resolving the folder path.
        folder_path : str
            Relative path to the folder containing test files.
        pattern : str
            File name pattern to match test files.
        test_name_pattern : str, optional
            Regular expression pattern to filter test names.
        tags : list of str, optional
            Tags to filter tests.

        Returns
        -------
        UnitTest
            The current instance with discovered tests added.

        Raises
        ------
        OrionisTestValueError
            If arguments are invalid, folder does not exist, no tests are found, or import/discovery errors occur.
        """
        # Validate Parameters
        self.__base_path = ValidBasePath(base_path)
        self.__folder_path = ValidFolderPath(folder_path)
        self.__pattern = ValidPattern(pattern)
        self.__test_name_pattern = ValidNamePattern(test_name_pattern)
        self.__tags = ValidTags(tags)

        # Try to discover tests in the specified folder
        try:

            # Ensure the folder path is absolute
            full_path = Path(self.__base_path / self.__folder_path).resolve()

            # Validate the full path
            if not full_path.exists():
                raise OrionisTestValueError(
                    f"Test folder not found at the specified path: '{str(full_path)}'. "
                    "Please verify that the path is correct and the folder exists."
                )

            # Discover tests using the unittest TestLoader
            tests = self.__loader.discover(
                start_dir=str(full_path),
                pattern=self.__pattern,
                top_level_dir="."
            )

            # Check for failed test imports (unittest.loader._FailedTest)
            for test in self.__flattenTestSuite(tests):
                if test.__class__.__name__ == "_FailedTest":
                    # Extract the error message from the test's traceback
                    error_message = ""
                    if hasattr(test, "_exception"):
                        error_message = str(test._exception)
                    elif hasattr(test, "_outcome") and hasattr(test._outcome, "errors"):
                        error_message = str(test._outcome.errors)
                    else:
                        # Try to get error from test id or str(test)
                        error_message = str(test)
                    raise OrionisTestValueError(
                        f"Failed to import test module: {test.id()}.\n"
                        f"Error details: {error_message}\n"
                        "Please check for import errors or missing dependencies."
                    )

            # If name pattern is provided, filter tests by name
            if test_name_pattern:
                tests = self.__filterTestsByName(
                    suite=tests,
                    pattern=self.__test_name_pattern
                )

            # If tags are provided, filter tests by tags
            if tags:
                tests = self.__filterTestsByTags(
                    suite=tests,
                    tags=self.__tags
                )

            # If no tests are found, raise an error
            if not list(tests):
                raise OrionisTestValueError(
                    f"No tests found in '{str(full_path)}' matching file pattern '{pattern}'"
                    + (f", test name pattern '{test_name_pattern}'" if test_name_pattern else "")
                    + (f", and tags {tags}" if tags else "") +
                    ". Please check your patterns, tags, and test files."
                )

            # Add discovered tests to the suite
            self.__suite.addTests(tests)

            # Count the number of tests discovered
            # Using __flattenTestSuite to ensure we count all individual test cases
            test_count = len(list(self.__flattenTestSuite(tests)))

            # Append the discovered tests information
            self.__discovered_tests.append({
                "folder": str(full_path),
                "test_count": test_count,
            })

            # Return the current instance
            return self

        except ImportError as e:

            # Raise a specific error if the import fails
            raise OrionisTestValueError(
                f"Error importing tests from path '{str(full_path)}': {str(e)}.\n"
                "Please verify that the directory and test modules are accessible and correct."
            )

        except Exception as e:

            # Raise a general error for unexpected issues
            raise OrionisTestValueError(
                f"Unexpected error while discovering tests in '{str(full_path)}': {str(e)}.\n"
                "Ensure that the test files are valid and that there are no syntax errors or missing dependencies."
            )

    def discoverTestsInModule(
        self,
        *,
        module_name: str,
        test_name_pattern: Optional[str] = None
    ) -> 'UnitTest':
        """
        Discover and add unit tests from a specified Python module to the test suite.

        Parameters
        ----------
        module_name : str
            Fully qualified name of the module to discover tests from.
        test_name_pattern : str, optional
            Regular expression pattern to filter test names.

        Returns
        -------
        UnitTest
            The current UnitTest instance with discovered tests added.

        Raises
        ------
        OrionisTestValueError
            If module_name is invalid, test_name_pattern is not a valid regex, the module cannot be imported, or no tests are found.
        """
        # Validate input parameters
        self.__module_name = ValidModuleName(module_name)
        self.__test_name_pattern = ValidNamePattern(test_name_pattern)

        try:
            # Load all tests from the specified module
            tests = self.__loader.loadTestsFromName(
                name=self.__module_name
            )

            # If a test name pattern is provided, filter the discovered tests
            if test_name_pattern:
                tests = self.__filterTestsByName(
                    suite=tests,
                    pattern=self.__test_name_pattern
                )

            # Add the filtered (or all) tests to the suite
            self.__suite.addTests(tests)

            # Count the number of discovered tests
            test_count = len(list(self.__flattenTestSuite(tests)))

            if test_count == 0:
                raise OrionisTestValueError(
                    f"No tests found in module '{self.__module_name}'"
                    + (f" matching test name pattern '{test_name_pattern}'." if test_name_pattern else ".")
                    + " Please ensure the module contains valid test cases and the pattern is correct."
                )

            # Record discovery metadata
            self.__discovered_tests.append({
                "module": self.__module_name,
                "test_count": test_count
            })

            # Return the current instance for method chaining
            return self

        except ImportError as e:

            # Raise an error if the module cannot be imported
            raise OrionisTestValueError(
                f"Failed to import tests from module '{self.__module_name}': {str(e)}. "
                "Ensure the module exists, is importable, and contains valid test cases."
            )

        except re.error as e:

            # Raise an error if the test name pattern is not a valid regex
            raise OrionisTestValueError(
                f"Invalid regular expression for test_name_pattern: '{test_name_pattern}'. "
                f"Regex compilation error: {str(e)}. Please check the pattern syntax."
            )

        except Exception as e:

            # Raise a general error for unexpected issues
            raise OrionisTestValueError(
                f"An unexpected error occurred while discovering tests in module '{self.__module_name}': {str(e)}. "
                "Verify that the module name is correct, test methods are valid, and there are no syntax errors or missing dependencies."
            )

    def run(
        self
    ) -> Dict[str, Any]:
        """
        Execute the test suite and return a summary of the results.

        Returns
        -------
        dict
            Dictionary summarizing the test results, including statistics and execution time.

        Raises
        ------
        OrionisTestFailureException
            If the test suite execution fails and throw_exception is True.
        """
        # Record the start time in nanoseconds
        start_time = time.time_ns()

        # Print the start message with test suite details
        self.__printer.startMessage(
            length_tests=len(list(self.__flattenTestSuite(self.__suite))),
            execution_mode=self.__execution_mode,
            max_workers=self.__max_workers
        )

        # Execute the test suite and capture result, output, and error buffers
        result, output_buffer, error_buffer = self.__printer.executePanel(
            flatten_test_suite=self.__flattenTestSuite(self.__suite),
            callable=self.__runSuite
        )

        # Store the captured output and error buffers as strings
        self.__output_buffer = output_buffer.getvalue()
        self.__error_buffer = error_buffer.getvalue()

        # Calculate execution time in milliseconds
        execution_time = (time.time_ns() - start_time) / 1_000_000_000

        # Generate a summary of the test results
        summary = self.__generateSummary(result, execution_time)

        # Display the test results using the printer
        self.__printer.displayResults(summary=summary)

        # Raise an exception if tests failed and exception throwing is enabled
        if not result.wasSuccessful() and self.__throw_exception:
            raise OrionisTestFailureException(result)

        # Print the final summary message
        self.__printer.finishMessage(summary=summary)

        # Return the summary of the test results
        return summary

    def __flattenTestSuite(
        self,
        suite: unittest.TestSuite
    ) -> List[unittest.TestCase]:
        """
        Recursively flatten a unittest.TestSuite into a list of unique unittest.TestCase instances.

        Parameters
        ----------
        suite : unittest.TestSuite
            The test suite to flatten.

        Returns
        -------
        list of unittest.TestCase
            Flat list of unique unittest.TestCase instances.
        """
        tests = []
        seen_ids = set()

        def _flatten(item):
            if isinstance(item, unittest.TestSuite):
                for sub_item in item:
                    _flatten(sub_item)
            elif hasattr(item, "id"):
                test_id = item.id()
                parts = test_id.split('.')
                if len(parts) >= 2:
                    short_id = '.'.join(parts[-2:])
                else:
                    short_id = test_id
                if short_id not in seen_ids:
                    seen_ids.add(short_id)
                    tests.append(item)

        _flatten(suite)
        return tests

    def __runSuite(
        self
    ) -> Tuple[unittest.TestResult, io.StringIO, io.StringIO]:
        """
        Execute the test suite using the configured execution mode, capturing output and error streams.

        Returns
        -------
        tuple
            (result, output_buffer, error_buffer)
            result : unittest.TestResult
                Test result object with test outcomes.
            output_buffer : io.StringIO
                Captured standard output during test execution.
            error_buffer : io.StringIO
                Captured standard error during test execution.
        """
        output_buffer = io.StringIO()
        error_buffer = io.StringIO()

        if self.__execution_mode == ExecutionMode.PARALLEL.value:
            result = self.__runTestsInParallel(
                output_buffer,
                error_buffer
            )
        else:
            result = self.__runTestsSequentially(
                output_buffer,
                error_buffer
            )

        return result, output_buffer, error_buffer

    def __resolveFlattenedTestSuite(
        self
    ) -> unittest.TestSuite:
        """
        Resolve and inject dependencies for all test cases in the suite, returning a flattened TestSuite.

        Returns
        -------
        unittest.TestSuite
            New TestSuite with dependencies injected where required.

        Raises
        ------
        OrionisTestValueError
            If any test method has unresolved dependencies.
        """
        flattened_suite = unittest.TestSuite()

        for test_case in self.__flattenTestSuite(self.__suite):

            if test_case.__class__.__name__ == "_FailedTest":
                flattened_suite.addTest(test_case)
                continue

            rf_instance = ReflectionInstance(test_case)
            method_name = rf_instance.getAttribute("_testMethodName")

            if not method_name:
                flattened_suite.addTest(test_case)
                continue

            test_method = getattr(test_case.__class__, method_name, None)

            decorators = []
            if hasattr(test_method, '__wrapped__'):
                original = test_method
                while hasattr(original, '__wrapped__'):
                    if hasattr(original, '__qualname__'):
                        decorators.append(original.__qualname__)
                    elif hasattr(original, '__name__'):
                        decorators.append(original.__name__)
                    original = original.__wrapped__

            if decorators:
                flattened_suite.addTest(test_case)
                continue

            signature = rf_instance.getMethodDependencies(method_name)

            if ((not signature.resolved and not signature.unresolved) or (not signature.resolved and len(signature.unresolved) > 0)):
                flattened_suite.addTest(test_case)
                continue

            if (len(signature.unresolved) > 0):
                raise OrionisTestValueError(
                    f"Test method '{method_name}' in class '{test_case.__class__.__name__}' has unresolved dependencies: {signature.unresolved}. "
                    "Please ensure all dependencies are correctly defined and available."
                )

            test_class = ReflectionInstance(test_case).getClass()
            original_method = getattr(test_class, method_name)

            params = Resolver(self.__app).resolveSignature(signature)

            def create_test_wrapper(original_test, resolved_args: dict):
                def wrapper(self_instance):
                    return original_test(self_instance, **resolved_args)
                return wrapper

            wrapped_method = create_test_wrapper(original_method, params)
            bound_method = wrapped_method.__get__(test_case, test_case.__class__)
            setattr(test_case, method_name, bound_method)
            flattened_suite.addTest(test_case)

        return flattened_suite

    def __runTestsSequentially(
        self,
        output_buffer: io.StringIO,
        error_buffer: io.StringIO
    ) -> unittest.TestResult:
        """
        Execute the test suite sequentially, capturing output and error streams.

        Parameters
        ----------
        output_buffer : io.StringIO
            Buffer to capture standard output.
        error_buffer : io.StringIO
            Buffer to capture standard error.

        Returns
        -------
        unittest.TestResult
            Result of the test suite execution.
        """
        result = None
        for case in self.__resolveFlattenedTestSuite():

            if not isinstance(case, unittest.TestCase):
                raise OrionisTestValueError(
                    f"Invalid test case type: Expected unittest.TestCase, got {type(case).__name__}."
                )

            with redirect_stdout(output_buffer), redirect_stderr(error_buffer):
                runner = unittest.TextTestRunner(
                    stream=output_buffer,
                    verbosity=self.__verbosity,
                    failfast=self.__fail_fast,
                    resultclass=self.__customResultClass()
                )
                single_result: IOrionisTestResult = runner.run(unittest.TestSuite([case]))

            self.__printer.unittestResult(single_result.test_results[0])

            if result is None:
                result = single_result
            else:
                self.__mergeTestResults(result, single_result)

        return result

    def __runTestsInParallel(
        self,
        output_buffer: io.StringIO,
        error_buffer: io.StringIO
    ) -> unittest.TestResult:
        """
        Execute all test cases in the test suite concurrently using a thread pool, aggregating results.

        Parameters
        ----------
        output_buffer : io.StringIO
            Buffer to capture standard output.
        error_buffer : io.StringIO
            Buffer to capture standard error.

        Returns
        -------
        unittest.TestResult
            Combined result object containing all test outcomes.
        """
        test_cases = list(self.__resolveFlattenedTestSuite())
        result_class = self.__customResultClass()
        combined_result = result_class(io.StringIO(), descriptions=True, verbosity=self.__verbosity)

        def run_single_test(test):
            runner = unittest.TextTestRunner(
                stream=io.StringIO(),
                verbosity=0,
                failfast=False,
                resultclass=result_class
            )
            return runner.run(unittest.TestSuite([test]))

        with redirect_stdout(output_buffer), redirect_stderr(error_buffer):
            with ThreadPoolExecutor(max_workers=self.__max_workers) as executor:
                futures = [executor.submit(run_single_test, test) for test in test_cases]
                for future in as_completed(futures):
                    test_result = future.result()
                    self.__mergeTestResults(combined_result, test_result)
                    if self.__fail_fast and not combined_result.wasSuccessful():
                        for f in futures:
                            f.cancel()
                        break

        for test_result in combined_result.test_results:
            self.__printer.unittestResult(test_result)

        return combined_result

    def __mergeTestResults(
        self,
        combined_result: unittest.TestResult,
        individual_result: unittest.TestResult
    ) -> None:
        """
        Merge the results of two unittest.TestResult objects into a single result.

        Parameters
        ----------
        combined_result : unittest.TestResult
            The TestResult object to update.
        individual_result : unittest.TestResult
            The TestResult object to merge in.

        Returns
        -------
        None
        """
        combined_result.testsRun += individual_result.testsRun
        combined_result.failures.extend(individual_result.failures)
        combined_result.errors.extend(individual_result.errors)
        combined_result.skipped.extend(individual_result.skipped)
        combined_result.expectedFailures.extend(individual_result.expectedFailures)
        combined_result.unexpectedSuccesses.extend(individual_result.unexpectedSuccesses)
        if hasattr(individual_result, 'test_results'):
            if not hasattr(combined_result, 'test_results'):
                combined_result.test_results = []
            combined_result.test_results.extend(individual_result.test_results)

    def __customResultClass(
        self
    ) -> type:
        """
        Create a custom test result class for enhanced test tracking.

        Returns
        -------
        type
            Dynamically created OrionisTestResult class extending unittest.TextTestResult.
        """
        this = self

        class OrionisTestResult(unittest.TextTestResult):
            def __init__(self, *args, **kwargs):
                super().__init__(*args, **kwargs)
                self.test_results = []
                self._test_timings = {}
                self._current_test_start = None

            def startTest(self, test):
                self._current_test_start = time.time()
                super().startTest(test)

            def stopTest(self, test):
                elapsed = time.time() - self._current_test_start
                self._test_timings[test] = elapsed
                super().stopTest(test)

            def addSuccess(self, test):
                super().addSuccess(test)
                elapsed = self._test_timings.get(test, 0.0)
                self.test_results.append(
                    TestResult(
                        id=test.id(),
                        name=str(test),
                        status=TestStatus.PASSED,
                        execution_time=elapsed,
                        class_name=test.__class__.__name__,
                        method=ReflectionInstance(test).getAttribute("_testMethodName"),
                        module=ReflectionInstance(test).getModuleName(),
                        file_path=ReflectionInstance(test).getFile(),
                        doc_string=ReflectionInstance(test).getMethodDocstring(test._testMethodName),
                    )
                )

            def addFailure(self, test, err):
                super().addFailure(test, err)
                elapsed = self._test_timings.get(test, 0.0)
                tb_str = ''.join(traceback.format_exception(*err))
                file_path, clean_tb = this._extractErrorInfo(tb_str)
                self.test_results.append(
                    TestResult(
                        id=test.id(),
                        name=str(test),
                        status=TestStatus.FAILED,
                        execution_time=elapsed,
                        error_message=str(err[1]),
                        traceback=clean_tb,
                        class_name=test.__class__.__name__,
                        method=ReflectionInstance(test).getAttribute("_testMethodName"),
                        module=ReflectionInstance(test).getModuleName(),
                        file_path=ReflectionInstance(test).getFile(),
                        doc_string=ReflectionInstance(test).getMethodDocstring(test._testMethodName),
                    )
                )

            def addError(self, test, err):
                super().addError(test, err)
                elapsed = self._test_timings.get(test, 0.0)
                tb_str = ''.join(traceback.format_exception(*err))
                file_path, clean_tb = this._extractErrorInfo(tb_str)
                self.test_results.append(
                    TestResult(
                        id=test.id(),
                        name=str(test),
                        status=TestStatus.ERRORED,
                        execution_time=elapsed,
                        error_message=str(err[1]),
                        traceback=clean_tb,
                        class_name=test.__class__.__name__,
                        method=ReflectionInstance(test).getAttribute("_testMethodName"),
                        module=ReflectionInstance(test).getModuleName(),
                        file_path=ReflectionInstance(test).getFile(),
                        doc_string=ReflectionInstance(test).getMethodDocstring(test._testMethodName),
                    )
                )

            def addSkip(self, test, reason):
                super().addSkip(test, reason)
                elapsed = self._test_timings.get(test, 0.0)
                self.test_results.append(
                    TestResult(
                        id=test.id(),
                        name=str(test),
                        status=TestStatus.SKIPPED,
                        execution_time=elapsed,
                        error_message=reason,
                        class_name=test.__class__.__name__,
                        method=ReflectionInstance(test).getAttribute("_testMethodName"),
                        module=ReflectionInstance(test).getModuleName(),
                        file_path=ReflectionInstance(test).getFile(),
                        doc_string=ReflectionInstance(test).getMethodDocstring(test._testMethodName)
                    )
                )

        return OrionisTestResult

    def _extractErrorInfo(
        self,
        traceback_str: str
    ) -> Tuple[Optional[str], Optional[str]]:
        """
        Extract the file path and a cleaned traceback from a given traceback string.

        Parameters
        ----------
        traceback_str : str
            Full traceback string to process.

        Returns
        -------
        tuple
            file_path : str or None
                Path to the Python file where the error occurred.
            clean_tb : str or None
                Cleaned traceback string with framework internals removed.
        """
        file_matches = re.findall(r'File ["\'](.*?.py)["\']', traceback_str)
        file_path = file_matches[-1] if file_matches else None
        tb_lines = traceback_str.split('\n')
        clean_lines = []
        relevant_lines_started = False
        for line in tb_lines:
            if any(s in line for s in ['unittest/', 'lib/python', 'site-packages']):
                continue
            if file_path and file_path in line and not relevant_lines_started:
                relevant_lines_started = True
            if relevant_lines_started:
                clean_lines.append(line)
        clean_tb = str('\n').join(clean_lines) if clean_lines else traceback_str
        return file_path, clean_tb

    def __generateSummary(
        self,
        result: unittest.TestResult,
        execution_time: float
    ) -> Dict[str, Any]:
        """
        Generate a summary of the test suite execution.

        Parameters
        ----------
        result : unittest.TestResult
            Result object containing details of the test execution.
        execution_time : float
            Total execution time in milliseconds.

        Returns
        -------
        dict
            Dictionary containing test statistics and details.

        Side Effects
        ------------
        If persistence is enabled, the summary is persisted to storage.
        If web reporting is enabled, a web report is generated.
        """
        test_details = []
        for test_result in result.test_results:
            rst: TestResult = test_result
            test_details.append({
                'id': rst.id,
                'class': rst.class_name,
                'method': rst.method,
                'status': rst.status.name,
                'execution_time': float(rst.execution_time),
                'error_message': rst.error_message,
                'traceback': rst.traceback,
                'file_path': rst.file_path,
                'doc_string': rst.doc_string
            })
        passed = result.testsRun - len(result.failures) - len(result.errors) - len(result.skipped)
        success_rate = (passed / result.testsRun * 100) if result.testsRun > 0 else 100.0
        self.__result = {
            "total_tests": result.testsRun,
            "passed": passed,
            "failed": len(result.failures),
            "errors": len(result.errors),
            "skipped": len(result.skipped),
            "total_time": float(execution_time),
            "success_rate": success_rate,
            "test_details": test_details,
            "timestamp": datetime.now().isoformat()
        }
        if self.__persistent:
            self.__handlePersistResults(self.__result)
        if self.__web_report:
            self.__handleWebReport(self.__result)
        return self.__result

    def __handleWebReport(
        self,
        summary: Dict[str, Any]
    ) -> None:
        """
        Generate a web-based report for the provided test results summary.

        Parameters
        ----------
        summary : dict
            Summary of test results for web report generation.

        Returns
        -------
        None
        """
        render = TestingResultRender(
            storage_path=self.__storage,
            result=summary,
            persist=self.__persistent and self.__persistent_driver == 'sqlite'
        )
        self.__printer.linkWebReport(render.render())

    def __handlePersistResults(
        self,
        summary: Dict[str, Any]
    ) -> None:
        """
        Persist the test results summary using the configured persistence driver.

        Parameters
        ----------
        summary : dict
            Test results summary to persist.

        Returns
        -------
        None

        Raises
        ------
        OSError
            If there is an error creating directories or writing files.
        OrionisTestPersistenceError
            If database operations fail or any other error occurs during persistence.
        """
        try:
            if self.__persistent_driver == PersistentDrivers.SQLITE.value:
                history = TestLogs(self.__storage)
                history.create(summary)
            elif self.__persistent_driver == PersistentDrivers.JSON.value:
                timestamp = datetime.now().strftime("%Y%m%d_%H%M%S")
                log_path = Path(self.__storage) / f"{timestamp}_test_results.json"
                log_path.parent.mkdir(parents=True, exist_ok=True)
                with open(log_path, 'w', encoding='utf-8') as log:
                    json.dump(summary, log, indent=4)
        except OSError as e:
            raise OSError(f"Error creating directories or writing files: {str(e)}")
        except Exception as e:
            raise OrionisTestPersistenceError(f"Error persisting test results: {str(e)}")

    def __filterTestsByName(
        self,
        suite: unittest.TestSuite,
        pattern: str
    ) -> unittest.TestSuite:
        """
        Filter tests in a test suite based on a specified name pattern.

        Parameters
        ----------
        suite : unittest.TestSuite
            Test suite containing the tests to filter.
        pattern : str
            Regular expression pattern to match test names.

        Returns
        -------
        unittest.TestSuite
            New test suite containing only tests that match the pattern.

        Raises
        ------
        OrionisTestValueError
            If the provided pattern is not a valid regular expression.
        """
        filtered_suite = unittest.TestSuite()
        try:
            regex = re.compile(pattern)
        except re.error as e:
            raise OrionisTestValueError(
                f"The provided test name pattern is invalid: '{pattern}'. "
                f"Regular expression compilation error: {str(e)}. "
                "Please check the pattern syntax and try again."
            )
        for test in self.__flattenTestSuite(suite):
            if regex.search(test.id()):
                filtered_suite.addTest(test)
        return filtered_suite

    def __filterTestsByTags(
        self,
        suite: unittest.TestSuite,
        tags: List[str]
    ) -> unittest.TestSuite:
        """
        Filter tests in a unittest TestSuite by specified tags.

        Parameters
        ----------
        suite : unittest.TestSuite
            Original TestSuite containing all tests.
        tags : list of str
            Tags to filter the tests by.

        Returns
        -------
        unittest.TestSuite
            New TestSuite containing only tests with matching tags.
        """
        filtered_suite = unittest.TestSuite()
        tag_set = set(tags)
        for test in self.__flattenTestSuite(suite):
            test_method = getattr(test, test._testMethodName, None)
            if hasattr(test_method, '__tags__'):
                method_tags = set(getattr(test_method, '__tags__'))
                if tag_set.intersection(method_tags):
                    filtered_suite.addTest(test)
            elif hasattr(test, '__tags__'):
                class_tags = set(getattr(test, '__tags__'))
                if tag_set.intersection(class_tags):
                    filtered_suite.addTest(test)
        return filtered_suite

    def getTestNames(
        self
    ) -> List[str]:
        """
        Get a list of test names (unique identifiers) from the test suite.

        Returns
        -------
        list of str
            List of test names from the test suite.
        """
        return [test.id() for test in self.__flattenTestSuite(self.__suite)]

    def getTestCount(
        self
    ) -> int:
        """
        Get the total number of test cases in the test suite.

        Returns
        -------
        int
            Total number of individual test cases in the suite.
        """
        return len(list(self.__flattenTestSuite(self.__suite)))

    def clearTests(
        self
    ) -> None:
        """
        Clear all tests from the current test suite.

        Returns
        -------
        None
        """
        self.__suite = unittest.TestSuite()

    def getResult(
        self
    ) -> dict:
        """
        Get the results of the executed test suite.

        Returns
        -------
        dict
            Result of the executed test suite.
        """
        return self.__result

    def getOutputBuffer(
        self
    ) -> int:
        """
        Get the output buffer used for capturing test results.

        Returns
        -------
        int
            Output buffer containing the results of the test execution.
        """
        return self.__output_buffer

    def printOutputBuffer(
        self
    ) -> None:
        """
        Print the contents of the output buffer to the console.

        Returns
        -------
        None
        """
        self.__printer.print(self.__output_buffer)

    def getErrorBuffer(
        self
    ) -> int:
        """
        Get the error buffer used for capturing test errors.

        Returns
        -------
        int
            Error buffer containing errors encountered during test execution.
        """
        return self.__error_buffer

    def printErrorBuffer(
        self
    ) -> None:
        """
        Print the contents of the error buffer to the console.

        Returns
        -------
        None
        """
        self.__printer.print(self.__error_buffer)
