# -*- coding: utf-8 -*-
"""属性测试：废除聚合逻辑正确性（is_trash 驱动）

纯单元测试，通过 stub 隔离外部依赖（DB/API/Config），
验证 _aggregate_by_assistant_date 中 is_trash 分流逻辑的正确性。

# Feature: assistant-abolish-cleanup, Property 1: 废除聚合逻辑正确性
# **Validates: Requirements 3.4, 9.3**
"""
from __future__ import annotations

from datetime import date
from decimal import Decimal
from typing import Any, Dict, List, Optional
from unittest.mock import MagicMock

from hypothesis import given, settings
from hypothesis import strategies as st

from tasks.dws.assistant_daily_task import AssistantDailyTask


# ── Stub 构造 ──────────────────────────────────────────────────────────

def _make_stub_task() -> AssistantDailyTask:
    """构造一个最小化的 AssistantDailyTask 实例，stub 掉所有外部依赖。"""
    # config.get 需要区分 key：timezone 返回字符串，其余返回整数
    def _config_get(key, default=None):
        if key == "app.timezone":
            return "Asia/Shanghai"
        if key == "app.tenant_id":
            return 1
        if key == "app.store_id":
            return 1
        return default

    config = MagicMock()
    config.get.side_effect = _config_get

    db = MagicMock()
    api = MagicMock()
    logger = MagicMock()

    task = AssistantDailyTask(config, db, api, logger)

    # stub SCD2 等级查询——返回固定等级
    task.get_assistant_level_asof = MagicMock(
        return_value={"level_code": 10, "level_name": "初级"}
    )

    # stub 课程类型查询——所有 skill 都返回 BASE
    from tasks.dws.base_dws_task import CourseType, ConfigCache
    task.get_course_type = MagicMock(return_value=CourseType.BASE)

    # stub load_config_cache（get_course_type 已被 mock，此处仅防止真实 DB 调用）
    task.load_config_cache = MagicMock(return_value=ConfigCache(
        performance_tiers=[],
        level_prices=[],
        bonus_rules=[],
        area_categories={},
        skill_types={},
        loaded_at=None,
    ))

    return task


# ── Hypothesis 策略 ────────────────────────────────────────────────────

# 固定的助教 ID 和服务日期，确保所有记录聚合到同一行
_FIXED_ASSISTANT_ID = 10001
_FIXED_SERVICE_DATE = date(2025, 6, 15)
_FIXED_SITE_ID = 1

# 单条服务记录策略
_service_record_st = st.fixed_dictionaries({
    "assistant_id": st.just(_FIXED_ASSISTANT_ID),
    "service_date": st.just(_FIXED_SERVICE_DATE),
    "assistant_nickname": st.just("测试助教"),
    "assistant_level": st.just(10),
    "assistant_service_id": st.integers(min_value=1, max_value=999999),
    "skill_id": st.just(100),
    "member_id": st.integers(min_value=1, max_value=9999),
    "table_id": st.integers(min_value=1, max_value=50),
    "income_seconds": st.integers(min_value=0, max_value=36000),
    "ledger_amount": st.integers(min_value=0, max_value=10000),
    "is_trash": st.integers(min_value=0, max_value=1),
})

# 服务记录列表策略（1~30 条）
_service_records_st = st.lists(_service_record_st, min_size=1, max_size=30)


# ── 属性测试 ───────────────────────────────────────────────────────────

class TestProperty1TrashAggregation:
    """Property 1: 废除聚合逻辑正确性（is_trash 驱动）

    **Validates: Requirements 3.4, 9.3**
    """

    @given(records=_service_records_st)
    @settings(max_examples=200)
    def test_trashed_seconds_equals_sum_of_trash_income(self, records: List[Dict[str, Any]]):
        """trashed_seconds 应等于所有 is_trash=1 记录的 income_seconds 之和"""
        task = _make_stub_task()
        result = task._aggregate_by_assistant_date(records, _FIXED_SITE_ID)

        # 手动计算期望值
        expected = sum(
            task.safe_int(r.get("income_seconds", 0))
            for r in records
            if bool(r.get("is_trash", 0))
        )

        assert len(result) == 1
        assert result[0]["trashed_seconds"] == expected

    @given(records=_service_records_st)
    @settings(max_examples=200)
    def test_trashed_count_equals_trash_record_count(self, records: List[Dict[str, Any]]):
        """trashed_count 应等于所有 is_trash=1 记录的数量"""
        task = _make_stub_task()
        result = task._aggregate_by_assistant_date(records, _FIXED_SITE_ID)

        expected = sum(1 for r in records if bool(r.get("is_trash", 0)))

        assert len(result) == 1
        assert result[0]["trashed_count"] == expected

    @given(records=_service_records_st)
    @settings(max_examples=200)
    def test_total_service_count_equals_non_trash_count(self, records: List[Dict[str, Any]]):
        """total_service_count 应等于所有 is_trash=0 记录的数量"""
        task = _make_stub_task()
        result = task._aggregate_by_assistant_date(records, _FIXED_SITE_ID)

        expected = sum(1 for r in records if not bool(r.get("is_trash", 0)))

        assert len(result) == 1
        assert result[0]["total_service_count"] == expected

    @given(records=_service_records_st)
    @settings(max_examples=200)
    def test_total_seconds_equals_sum_of_non_trash_income(self, records: List[Dict[str, Any]]):
        """total_seconds 应等于所有 is_trash=0 记录的 income_seconds 之和"""
        task = _make_stub_task()
        result = task._aggregate_by_assistant_date(records, _FIXED_SITE_ID)

        expected = sum(
            task.safe_int(r.get("income_seconds", 0))
            for r in records
            if not bool(r.get("is_trash", 0))
        )

        assert len(result) == 1
        assert result[0]["total_seconds"] == expected
