import sys
import unittest
from pathlib import Path
from unittest.mock import patch

ROOT = Path(__file__).resolve().parent
if str(ROOT) not in sys.path:
    sys.path.insert(0, str(ROOT))
PROJECT_ROOT = ROOT.parents[1]
if str(PROJECT_ROOT) not in sys.path:
    sys.path.insert(0, str(PROJECT_ROOT))

from tools.material_recall import _pick_query_strategies_for_batch
from tools.creative_creation import _is_landing_candidate
from tools.video_recall import LandingVideo


def _landing(**kwargs):
    base = dict(
        video_id=1,
        title="",
        cover_url="",
        video_url="",
        score=0.0,
        rov=0.0,
        sim=0.0,
        visit_uv=0,
        category="",
        standard_element="",
        category_name="",
        demand_content_title="",
        demand_content_topic="",
        demand_content_id="",
        demand_type="",
        point_type="",
        dimension="",
        experiment_id="",
        raw={},
    )
    base.update(kwargs)
    return LandingVideo(**base)


class HotVideoFeatureEnrichmentTest(unittest.TestCase):
    def test_hot_video_element_features_expand_into_multiple_recall_strategies(self):
        landing = _landing(
            raw={
                "element_features": [
                    {"point_type": "关键点", "standard_element": "孝道观念"},
                    {"point_type": "灵感点", "standard_element": "养老院"},
                ]
            },
        )

        strategies = _pick_query_strategies_for_batch(landing)

        self.assertEqual(
            strategies[:2],
            [
                ("孝道观念", "KEYPOINT_SUBSTANCE", "标准化元素-关键点"),
                ("养老院", "INSPIRATION_SUBSTANCE", "标准化元素-灵感点"),
            ],
        )

    def test_landing_candidate_requires_cluster_feature_point_demand_type(self):
        self.assertTrue(_is_landing_candidate(_landing(
            demand_type="聚类特征点",
            point_type="灵感点",
            standard_element="退休书",
        )))
        self.assertFalse(_is_landing_candidate(_landing(
            demand_type="tree泛化特征点",
            point_type="灵感点",
            standard_element="奇幻视觉",
        )))

    def test_hot_landing_candidate_allows_odps_enriched_element_features_without_demand_type(self):
        self.assertTrue(_is_landing_candidate(_landing(
            demand_type="",
            point_type="关键点",
            standard_element="孝道观念",
            raw={
                "source": "hot",
                "element_features": [
                    {"point_type": "关键点", "standard_element": "孝道观念"},
                ],
            },
        )))

    def test_video_feature_query_uses_latest_dt_cache_miss_substance_dimension_and_score_threshold(self):
        from tools import video_feature_query

        captured_sql = []

        class FakeClient:
            def query(self, sql):
                captured_sql.append(sql)
                return [
                    {
                        "vid": "70242543",
                        "point_type": "关键点",
                        "standard_element": "孝道观念",
                        "contribution_score": 0.91,
                        "dt": "20260701",
                    },
                    {
                        "vid": "70242543",
                        "point_type": "目的点",
                        "standard_element": "家庭矛盾",
                        "contribution_score": 0.86,
                        "dt": "20260701",
                    },
                ]

        with (
            patch.object(video_feature_query, "_get_odps_client", return_value=FakeClient()),
            patch.object(video_feature_query, "_latest_partition_dt", return_value="20260701"),
            patch.object(video_feature_query, "_ensure_cache_table"),
            patch.object(video_feature_query, "_read_cache", return_value=({}, set())),
            patch.object(video_feature_query, "_write_cache") as write_cache,
        ):
            features = video_feature_query.fetch_video_element_features([70242543])

        self.assertEqual(len(features[70242543]), 2)
        write_cache.assert_called_once_with(features, [], "20260701")
        sql = captured_sql[0]
        self.assertIn("dt = '20260701'", sql)
        self.assertIn("`元素维度` = '实质'", sql)
        self.assertIn("`贡献分` >= 0.8", sql)

    def test_video_feature_query_cache_hit_skips_odps_element_query(self):
        from tools import video_feature_query
        from tools.video_feature_query import VideoElementFeature

        class FakeClient:
            def query(self, sql):
                raise AssertionError("cache hit should not query ODPS elements")

        cached = {
            70242543: [
                VideoElementFeature(
                    video_id=70242543,
                    point_type="关键点",
                    standard_element="孝道观念",
                    contribution_score=0.91,
                    dt="20260701",
                )
            ]
        }
        with (
            patch.object(video_feature_query, "_get_odps_client", return_value=FakeClient()),
            patch.object(video_feature_query, "_latest_partition_dt", return_value="20260701"),
            patch.object(video_feature_query, "_ensure_cache_table"),
            patch.object(video_feature_query, "_read_cache", return_value=(cached, {70242543})),
            patch.object(video_feature_query, "_write_cache") as write_cache,
        ):
            features = video_feature_query.fetch_video_element_features([70242543])

        self.assertEqual(features, cached)
        write_cache.assert_not_called()

    def test_video_feature_query_writes_negative_cache_for_missing_ids(self):
        from tools import video_feature_query

        class FakeClient:
            def query(self, sql):
                return []

        with (
            patch.object(video_feature_query, "_get_odps_client", return_value=FakeClient()),
            patch.object(video_feature_query, "_latest_partition_dt", return_value="20260701"),
            patch.object(video_feature_query, "_ensure_cache_table"),
            patch.object(video_feature_query, "_read_cache", return_value=({}, set())),
            patch.object(video_feature_query, "_write_cache") as write_cache,
        ):
            features = video_feature_query.fetch_video_element_features([70242543])

        self.assertEqual(features, {})
        write_cache.assert_called_once_with({}, [70242543], "20260701")


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