#!/usr/bin/env python3
"""
tests/run_tests.py
==================
Single entry point to run all VSS tests without pytest.

Usage
-----
    # From vss/ directory — run everything
    python tests/run_tests.py

    # Verbose (shows each test method)
    python tests/run_tests.py -v

    # Single module
    python tests/run_tests.py --module routing
    python tests/run_tests.py --module sql
    python tests/run_tests.py --module llm
    python tests/run_tests.py --module api

    # Single test class
    python tests/run_tests.py --class TestSanitizeSql -v

Logs
----
    tests/logs/vss_tests.log — every test run appended with a session banner,
                               class/module separators, PASS/FAIL per test,
                               and state-trace blocks where available.
"""

from __future__ import annotations

import argparse
import logging
import sys
import unittest
from datetime import datetime
from pathlib import Path
from typing import Optional

# ── sys.path ─────────────────────────────────────────────────────────────────
_TESTS_DIR = Path(__file__).resolve().parent
_VSS_ROOT  = _TESTS_DIR.parent

for _p in (str(_TESTS_DIR), str(_VSS_ROOT)):
    if _p not in sys.path:
        sys.path.insert(0, _p)

import shared  # noqa: E402  — sets up logging + helpers

logger = logging.getLogger("vss.tests.runner")

_W = 80   # visual width

# ── Module registry ───────────────────────────────────────────────────────────
_MODULES = {
    "routing": "test_agents.test_routing",
    "sql":     "test_agents.test_sql_agent",
    "llm":     "test_agents.test_llm_init",
    "api":     "test_e2e.test_api",
}


# ─────────────────────────────────────────────────────────────────────────────
# Custom test result — adds class separators and per-test logging
# ─────────────────────────────────────────────────────────────────────────────

class _VSSTestResult(unittest.TextTestResult):
    """
    Extends TextTestResult to write structured blocks to the log file.

    Each test class gets a separator header; each test result (PASS/FAIL/ERROR)
    is written as a timestamped line with the icon ✓ / ✗ / !.
    """

    def __init__(self, stream, descriptions, verbosity):
        super().__init__(stream, descriptions, verbosity)
        self._current_class: Optional[str] = None

    def _ts(self) -> str:
        return datetime.utcnow().strftime("%H:%M:%S")

    def _class_header(self, test) -> None:
        cls = f"{test.__class__.__module__}.{test.__class__.__name__}"
        if cls == self._current_class:
            return
        self._current_class = cls
        sep = "━" * _W
        logger.info("")
        logger.info(sep)
        logger.info("  CLASS: %s", cls)
        logger.info(sep)

    def startTest(self, test) -> None:
        self._class_header(test)
        super().startTest(test)

    def addSuccess(self, test) -> None:
        super().addSuccess(test)
        logger.info("  ✓  %s  %s", self._ts(), test._testMethodName)

    def addFailure(self, test, err) -> None:
        super().addFailure(test, err)
        logger.error("  ✗  %s  %s  FAIL", self._ts(), test._testMethodName)
        logger.error("     %s", self._exc_info_to_string(err, test).strip())

    def addError(self, test, err) -> None:
        super().addError(test, err)
        logger.error("  !  %s  %s  ERROR", self._ts(), test._testMethodName)
        logger.error("     %s", self._exc_info_to_string(err, test).strip())

    def addSkip(self, test, reason) -> None:
        super().addSkip(test, reason)
        logger.warning("  -  %s  %s  SKIP  (%s)", self._ts(), test._testMethodName, reason)


class _VSSTestRunner(unittest.TextTestRunner):
    """Runner that uses _VSSTestResult."""

    def _makeResult(self):
        return _VSSTestResult(self.stream, self.descriptions, self.verbosity)


# ─────────────────────────────────────────────────────────────────────────────
# Suite builder
# ─────────────────────────────────────────────────────────────────────────────

def _build_suite(module_filter: Optional[str], class_filter: Optional[str]) -> unittest.TestSuite:
    loader = unittest.TestLoader()
    suite  = unittest.TestSuite()

    modules = (
        {module_filter: _MODULES[module_filter]}
        if module_filter and module_filter in _MODULES
        else _MODULES
    )

    for label, dotted_name in modules.items():
        try:
            target = f"{dotted_name}.{class_filter}" if class_filter else dotted_name
            tests  = loader.loadTestsFromName(target)
            suite.addTests(tests)
            logger.debug("Loaded: %s (%s)", label, dotted_name)
        except Exception as exc:
            logger.error("Failed to load %s: %s", dotted_name, exc)
            print(f"[LOAD ERROR] {dotted_name}: {exc}", file=sys.stderr)

    return suite


# ─────────────────────────────────────────────────────────────────────────────
# Runner
# ─────────────────────────────────────────────────────────────────────────────

def _run(verbosity: int, module_filter: Optional[str], class_filter: Optional[str]) -> int:
    suite = _build_suite(module_filter, class_filter)
    total = suite.countTestCases()

    # ── Pre-run banner in log ─────────────────────────────────────────────────
    logger.info("")
    logger.info("─" * _W)
    logger.info(
        "  RUN  %d test(s)%s%s",
        total,
        f"  module={module_filter}" if module_filter else "",
        f"  class={class_filter}"  if class_filter  else "",
    )
    logger.info("─" * _W)

    runner = _VSSTestRunner(
        verbosity=verbosity,
        stream=sys.stdout,
        failfast=False,
    )
    result = runner.run(suite)

    # ── Post-run summary in log ───────────────────────────────────────────────
    passed  = total - len(result.failures) - len(result.errors) - len(result.skipped)
    logger.info("")
    logger.info("═" * _W)
    logger.info(
        "  RESULT  passed=%-3d  failed=%-3d  errors=%-3d  skipped=%-3d  total=%d",
        passed, len(result.failures), len(result.errors), len(result.skipped), total,
    )
    logger.info("  Logs → %s", shared.LOG_FILE)
    logger.info("═" * _W)

    if result.failures or result.errors:
        logger.error("")
        logger.error("  FAILURES / ERRORS")
        logger.error("─" * _W)
        for test, tb in result.failures:
            logger.error("  ✗ FAIL   %s\n%s", test, tb)
        for test, tb in result.errors:
            logger.error("  ! ERROR  %s\n%s", test, tb)

    print(f"\nLogs → {shared.LOG_FILE}")
    return 0 if result.wasSuccessful() else 1


# ─────────────────────────────────────────────────────────────────────────────
# CLI
# ─────────────────────────────────────────────────────────────────────────────

def main() -> None:
    parser = argparse.ArgumentParser(
        description="Run VSS unit tests without pytest.",
        formatter_class=argparse.RawTextHelpFormatter,
    )
    parser.add_argument("-v", "--verbose", action="store_true",
                        help="Show each test method name")
    parser.add_argument("--module", choices=list(_MODULES),
                        metavar="MODULE",
                        help=f"Run one module. Choices: {', '.join(_MODULES)}")
    parser.add_argument("--class", dest="test_class", metavar="CLASS",
                        help="Run a single TestCase class (e.g. TestSanitizeSql)")
    args = parser.parse_args()

    sys.exit(_run(2 if args.verbose else 1, args.module, args.test_class))


if __name__ == "__main__":
    main()
