from __future__ import annotations

import ast
import hashlib
import unittest
from pathlib import Path

import heaven_agent
import heaven_engine
from backend.features.heaven import agent as canonical_agent
from backend.features.heaven import engine as canonical_engine


APP_ROOT = Path(__file__).resolve().parents[1]
ORIGINAL_ROOT = APP_ROOT.parent

HEAVEN_SERVICE_METHODS = {
    "_heaven_manual_schema",
    "_validate_heaven_manual_data",
    "_apply_heaven_manual_data",
    "_heaven_line_checks",
    "_resolve_heaven_stock_code",
    "heaven_setup",
    "_heaven_stock_context",
    "_heaven_market_mode",
    "_heaven_trend_sources",
    "_heaven_trend_quality_issues",
    "heaven_personal",
    "heaven_hexagram",
    "heaven_readings",
    "_heaven_reading_identity",
    "heaven_interpret",
    "_legacy_truncated_heaven_reading",
    "_call_heaven_agent",
    "_heaven_index_context",
    "_aggregate_index_context",
    "_heaven_sector_context",
}

HEAVEN_REPOSITORY_METHODS = {
    "list_sector_phase_overrides",
    "save_sector_phase_override",
    "delete_sector_phase_override",
    "_heaven_reading_dict",
    "save_heaven_reading",
    "list_heaven_readings",
    "latest_heaven_reading",
    "delete_heaven_reading",
}

HEAVEN_HTTP_METHODS = {"heaven_hexagram", "heaven_personal", "heaven_interpret"}


def sha256(path: Path) -> str:
    return hashlib.sha256(path.read_bytes()).hexdigest()


def class_methods(path: Path, class_name: str) -> dict[str, str]:
    tree = ast.parse(path.read_text(encoding="utf-8"), filename=str(path))
    owner = next(
        node
        for node in tree.body
        if isinstance(node, ast.ClassDef) and node.name == class_name
    )
    return {
        node.name: ast.dump(node, include_attributes=False)
        for node in owner.body
        if isinstance(node, (ast.FunctionDef, ast.AsyncFunctionDef))
    }


def top_level_definitions(path: Path) -> dict[str, str]:
    tree = ast.parse(path.read_text(encoding="utf-8"), filename=str(path))
    return {
        node.name: ast.dump(node, include_attributes=False)
        for node in tree.body
        if isinstance(node, (ast.FunctionDef, ast.AsyncFunctionDef, ast.ClassDef))
    }


class HeavenSliceSourceEquivalenceTests(unittest.TestCase):
    def assert_methods_equal(
        self,
        original_path: Path,
        original_class: str,
        migrated_path: Path,
        migrated_class: str,
        expected: set[str],
        adapted: set[str] | None = None,
    ) -> None:
        original = class_methods(original_path, original_class)
        migrated = class_methods(migrated_path, migrated_class)
        self.assertEqual(set(migrated), expected)
        for name in sorted(expected - (adapted or set())):
            self.assertEqual(migrated[name], original[name], name)

    def test_heaven_agent_uses_shared_transport(self) -> None:
        source = (
            APP_ROOT / "backend" / "features" / "heaven" / "agent.py"
        ).read_text(encoding="utf-8")
        self.assertIn("llm_transport.chat_completion", source)
        self.assertNotIn("urllib.request", source)

    def test_heaven_engine_definitions_are_exact_original_ast(self) -> None:
        self.assertEqual(
            top_level_definitions(ORIGINAL_ROOT / "heaven_engine.py"),
            top_level_definitions(
                APP_ROOT / "backend" / "features" / "heaven" / "engine.py"
            ),
        )

    def test_compatibility_modules_are_canonical_module_objects(self) -> None:
        self.assertIs(heaven_agent, canonical_agent)
        self.assertIs(heaven_engine, canonical_engine)

    def test_heaven_service_methods_are_exact_original_ast(self) -> None:
        self.assert_methods_equal(
            ORIGINAL_ROOT / "server.py",
            "DashboardService",
            APP_ROOT / "backend" / "features" / "heaven" / "service.py",
            "HeavenServiceMixin",
            HEAVEN_SERVICE_METHODS,
            {"_heaven_reading_identity"},
        )
        source = (
            APP_ROOT / "backend" / "features" / "heaven" / "service.py"
        ).read_text(encoding="utf-8")
        self.assertIn(
            "MarketServiceMixin._display_compact_date(context_date)", source
        )

    def test_heaven_repository_methods_are_exact_original_ast(self) -> None:
        self.assert_methods_equal(
            ORIGINAL_ROOT / "database.py",
            "ReviewDatabase",
            APP_ROOT / "backend" / "features" / "heaven" / "repository.py",
            "HeavenRepositoryMixin",
            HEAVEN_REPOSITORY_METHODS,
        )

    def test_original_classes_no_longer_duplicate_moved_methods(self) -> None:
        remaining_service = class_methods(
            APP_ROOT / "backend" / "application.py", "DashboardService"
        )
        remaining_database = class_methods(APP_ROOT / "database.py", "ReviewDatabase")
        remaining_http = class_methods(
            APP_ROOT / "backend" / "application.py", "RequestHandler"
        )
        self.assertTrue(HEAVEN_SERVICE_METHODS.isdisjoint(remaining_service))
        self.assertTrue(HEAVEN_REPOSITORY_METHODS.isdisjoint(remaining_database))
        self.assertTrue(HEAVEN_HTTP_METHODS.isdisjoint(remaining_http))

    def test_http_mixin_preserves_all_heaven_endpoints(self) -> None:
        methods = class_methods(
            APP_ROOT / "backend" / "features" / "heaven" / "http.py",
            "HeavenHttpMixin",
        )
        self.assertEqual(set(methods), HEAVEN_HTTP_METHODS)
        source = (
            APP_ROOT / "backend" / "features" / "heaven" / "http.py"
        ).read_text(encoding="utf-8")
        self.assertNotIn("SERVICE.", source)
        self.assertEqual(source.count("self.application_service.heaven_"), 3)


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