from __future__ import annotations

import ast
import hashlib
import unittest
from pathlib import Path

import chart_data_provider
import ifind_client
import realtime_aggregator
import tushare_client
from backend.bootstrap import config as bootstrap_config
from backend.data import realtime
from backend.data.numbers import finite_number
from backend.data.providers import ifind_client as canonical_ifind
from backend.data.providers import tushare_client as canonical_tushare
from backend.features.market import charts
from tests.preservation_helpers import (
    assert_frontend_runtime_matches_audited_baseline,
    assert_moved_asset_matches,
    function_contract,
)


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

MARKET_METHODS = {
    "_tushare_client",
    "_market_insights",
    "get_dashboard",
    "_dashboard_sentiment_ready",
    "_display_compact_date",
    "_carry_dashboard",
    "_realtime_snapshot_due",
    "sync_dashboard",
    "realtime_aggregate_health",
    "_search_market_directory",
    "_search_match_score",
    "search_entities",
    "get_search_detail",
    "get_intraday_chart",
    "_ths_search_detail",
    "_index_search_detail",
    "get_stock_detail",
    "_stock_detail_bar_date",
    "_stock_detail_cache_needs_refresh",
    "_prepare_stock_detail",
    "_sanitize_stock_detail_prices",
    "_valid_realtime_stock_quote",
    "_ifind_realtime_stock_quote",
    "_merge_realtime_stock_detail",
    "get_stock_preview",
    "backfill",
    "_stock_identity",
    "_enrich_stock_detail",
    "_with_storage",
    "_record_count",
}

MARKET_REPOSITORY_METHODS = {
    "get_snapshot",
    "get_latest_real_snapshot",
    "save_snapshot",
    "get_data_snapshot",
    "get_latest_data_snapshot",
    "save_data_snapshot",
    "search_stock_master",
    "list_snapshot_payloads",
    "start_sync",
    "finish_sync",
    "status",
    "upsert_stock_master",
    "list_stock_master",
    "upsert_daily_bars",
    "daily_bars_for_date",
}


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 sha256(path: Path) -> str:
    return hashlib.sha256(path.read_bytes()).hexdigest()


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.ClassDef, ast.FunctionDef, ast.AsyncFunctionDef))
    }


class MarketSliceSourceEquivalenceTests(unittest.TestCase):
    def test_market_service_methods_are_exact_original_ast(self) -> None:
        original = class_methods(ORIGINAL_ROOT / "server.py", "DashboardService")
        migrated = class_methods(
            APP_ROOT / "backend" / "features" / "market" / "service.py",
            "MarketServiceMixin",
        )
        self.assertEqual(set(migrated), MARKET_METHODS)
        for name in sorted(MARKET_METHODS):
            self.assertEqual(migrated[name], original[name], name)

    def test_market_repository_methods_are_exact_original_ast(self) -> None:
        original = class_methods(ORIGINAL_ROOT / "database.py", "ReviewDatabase")
        migrated = class_methods(
            APP_ROOT / "backend" / "features" / "market" / "repository.py",
            "MarketRepositoryMixin",
        )
        self.assertEqual(set(migrated), MARKET_REPOSITORY_METHODS)
        for name in sorted(MARKET_REPOSITORY_METHODS):
            self.assertEqual(migrated[name], original[name], name)

    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")
        self.assertTrue(MARKET_METHODS.isdisjoint(remaining_service))
        self.assertTrue(MARKET_REPOSITORY_METHODS.isdisjoint(remaining_database))

    def test_provider_compatibility_modules_are_canonical_aliases(self) -> None:
        self.assertIs(tushare_client.TushareClient, canonical_tushare.TushareClient)
        self.assertIs(ifind_client.IfindHttpClient, canonical_ifind.IfindHttpClient)
        self.assertIs(realtime_aggregator.WebRealtimeAggregator, realtime.WebRealtimeAggregator)
        self.assertIs(chart_data_provider.MarketChartClient, charts.MarketChartClient)

    def test_provider_logic_is_the_original_implementation(self) -> None:
        exact_moves = (
            ("ifind_client.py", "backend/data/providers/ifind_client.py"),
            ("realtime_aggregator.py", "backend/data/realtime.py"),
        )
        for original, migrated in exact_moves:
            self.assertEqual(sha256(ORIGINAL_ROOT / original), sha256(APP_ROOT / migrated))
        original_tushare = top_level_definitions(ORIGINAL_ROOT / "tushare_client.py")
        original_tushare.pop("_number")
        original_tushare.pop("_display_date")
        self.assertEqual(
            original_tushare,
            top_level_definitions(APP_ROOT / "backend/data/providers/tushare_client.py"),
        )
        self.assertEqual(
            function_contract(ORIGINAL_ROOT / "tushare_client.py", "_number"),
            function_contract(APP_ROOT / "backend/data/numbers.py", "finite_number"),
        )
        self.assertIs(canonical_tushare._number, finite_number)
        self.assertEqual(
            function_contract(ORIGINAL_ROOT / "tushare_client.py", "_display_date"),
            function_contract(
                APP_ROOT / "backend/bootstrap/config.py", "display_compact_date"
            ),
        )
        self.assertIs(canonical_tushare._display_date, bootstrap_config.display_compact_date)
        original_charts = top_level_definitions(ORIGINAL_ROOT / "chart_data_provider.py")
        original_charts.pop("_stock_market_code")
        self.assertEqual(
            original_charts,
            top_level_definitions(APP_ROOT / "backend/features/market/charts.py"),
        )
        self.assertEqual(
            function_contract(
                ORIGINAL_ROOT / "chart_data_provider.py", "_stock_market_code"
            ),
            function_contract(
                APP_ROOT / "backend/bootstrap/config.py", "tushare_code"
            ),
        )
        self.assertIs(charts._stock_market_code, bootstrap_config.tushare_code)

    def test_relocated_frontend_preserves_original_market_runtime_and_styles(self) -> None:
        assert_frontend_runtime_matches_audited_baseline(self)
        for original, migrated in (
            ("styles.css", "styles/styles.css"),
            ("renovation.css", "styles/renovation.css"),
            ("redesign-v2.css", "styles/redesign-v2.css"),
            ("theme.css", "styles/theme.css"),
            ("wentian-v2.css", "pages/heaven/page.css"),
        ):
            assert_moved_asset_matches(self, original, migrated)


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