from __future__ import annotations

import ast
import unittest
from pathlib import Path


ROOT = Path(__file__).resolve().parents[1]
FEATURES = ROOT / "backend" / "features"
RETIRED_ROOT_MODULES = {
    "advanced_strategies",
    "alert_service",
    "app_config",
    "assistant_agent",
    "chart_data_provider",
    "heaven_agent",
    "heaven_engine",
    "ifind_client",
    "llm_strategy",
    "llm_stream",
    "market_insights",
    "mentor_agent",
    "realtime_aggregator",
    "screener",
    "security",
    "sentiment_engine",
    "strategy_tracking",
    "trade_journal",
    "tushare_client",
}


class FeatureBoundaryTests(unittest.TestCase):
    def test_feature_services_do_not_import_http_or_provider_adapters(self) -> None:
        forbidden = {
            "server",
            "tushare_client",
            "ifind_client",
            "chart_data_provider",
            "realtime_aggregator",
        }
        violations = []
        for path in FEATURES.rglob("*.py"):
            tree = ast.parse(path.read_text(encoding="utf-8"), filename=str(path))
            for node in ast.walk(tree):
                names = []
                if isinstance(node, ast.Import):
                    names = [alias.name for alias in node.names]
                elif isinstance(node, ast.ImportFrom) and node.module:
                    names = [node.module]
                for name in names:
                    if name.split(".")[0] in forbidden:
                        violations.append(f"{path.relative_to(ROOT)} -> {name}")
        self.assertEqual(violations, [])

    def test_backend_does_not_import_retired_root_modules(self) -> None:
        violations = []
        for path in (ROOT / "backend").rglob("*.py"):
            relative = path.relative_to(ROOT).as_posix()
            tree = ast.parse(path.read_text(encoding="utf-8"), filename=str(path))
            for node in ast.walk(tree):
                names = []
                if isinstance(node, ast.Import):
                    names = [alias.name for alias in node.names]
                elif isinstance(node, ast.ImportFrom) and node.module:
                    names = [node.module]
                for name in names:
                    root_name = name.split(".")[0]
                    if root_name in RETIRED_ROOT_MODULES:
                        violations.append(f"{relative} -> {name}")
        self.assertEqual(violations, [])

    def test_retired_root_modules_are_absent(self) -> None:
        present = sorted(
            name for name in RETIRED_ROOT_MODULES if (ROOT / f"{name}.py").exists()
        )
        self.assertEqual(present, [])

    def test_each_migrated_feature_owns_one_application_service(self) -> None:
        expected = {
            "alerts/service.py": "AlertService",
            "mentor/service.py": "MentorServiceMixin",
            "review/trade_journal.py": "TradeJournalService",
            "screener/tracking.py": "StrategyTrackingService",
        }
        for relative, class_name in expected.items():
            tree = ast.parse((FEATURES / relative).read_text(encoding="utf-8"))
            self.assertIn(class_name, {node.name for node in tree.body if isinstance(node, ast.ClassDef)})


if __name__ == "__main__":
    unittest.main()
