# -*- coding: utf-8 -*-
"""
Feature: dataflow-field-completion, Property 3: TABLE_MAP 覆盖完整性

**Validates: Requirements 7.2, 8.2**

对于任意在 TABLE_MAP 中注册的 DWD 表，该表的所有非 SCD2 列要么在 FACT_MAPPINGS
中有显式映射，要么在对应 ODS 表中存在同名列（自动映射）。

本测试聚焦以下可静态验证的属性：
1. TABLE_MAP 所有条目的 ODS 源表非空
2. C 类表在 TABLE_MAP 中注册
3. C 类表在 FACT_MAPPINGS 中有条目
4. C 类表映射字段数量与期望一致
5. TABLE_MAP 与 FACT_MAPPINGS 交叉一致性
6. hypothesis 属性测试：随机 TABLE_MAP 条目结构验证
"""
from __future__ import annotations

import sys
from pathlib import Path

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

# ── 将 ETL 模块加入 sys.path ──
_ETL_ROOT = Path(__file__).resolve().parent.parent / "apps" / "etl" / "connectors" / "feiqiu"
if str(_ETL_ROOT) not in sys.path:
    sys.path.insert(0, str(_ETL_ROOT))

from tasks.dwd.dwd_load_task import DwdLoadTask

# ── C 类表定义 ──
C_CLASS_TABLES = [
    "dwd.dwd_goods_stock_summary",
    "dwd.dwd_goods_stock_movement",
]

# ── C 类表期望映射字段 ──

# goods_stock_summary → dwd_goods_stock_summary（14 个字段）
_GOODS_STOCK_SUMMARY_EXPECTED_COLS = {
    "site_goods_id", "goods_name", "goods_unit", "goods_category_id",
    "goods_category_second_id", "category_name", "range_start_stock",
    "range_end_stock", "range_in", "range_out", "range_sale",
    "range_sale_money", "range_inventory", "current_stock",
}

# goods_stock_movements → dwd_goods_stock_movement（19 个字段）
_GOODS_STOCK_MOVEMENT_EXPECTED_COLS = {
    "site_goods_stock_id", "tenant_id", "site_id", "site_goods_id",
    "goods_name", "goods_category_id", "goods_second_category_id",
    "unit", "price", "stock_type", "change_num", "start_num", "end_num",
    "change_num_a", "start_num_a", "end_num_a", "remark", "operator_name",
    "create_time",
}

# ── 收集所有 TABLE_MAP 条目 ──
_ALL_TABLE_MAP_ENTRIES = list(DwdLoadTask.TABLE_MAP.items())


# ══════════════════════════════════════════════════════════════════
# Property 3.1: TABLE_MAP 所有条目的 ODS 源表非空
# ══════════════════════════════════════════════════════════════════

def test_all_table_map_entries_have_ods_source():
    """TABLE_MAP 中每个 DWD 表都有非空的 ODS 源表名。"""
    for dwd_table, ods_table in DwdLoadTask.TABLE_MAP.items():
        assert ods_table and isinstance(ods_table, str) and ods_table.strip(), \
            f"{dwd_table}: TABLE_MAP 中的 ODS 源表为空或无效"


# ══════════════════════════════════════════════════════════════════
# Property 3.2: C 类表在 TABLE_MAP 中注册
# ══════════════════════════════════════════════════════════════════

def test_c_class_tables_registered_in_table_map():
    """
    **Validates: Requirements 7.2, 8.2**

    C 类表（dwd_goods_stock_summary、dwd_goods_stock_movement）
    必须在 TABLE_MAP 中注册。
    """
    for table in C_CLASS_TABLES:
        assert table in DwdLoadTask.TABLE_MAP, \
            f"C 类表 {table} 未在 TABLE_MAP 中注册"
        ods_table = DwdLoadTask.TABLE_MAP[table]
        assert ods_table and isinstance(ods_table, str), \
            f"C 类表 {table}: TABLE_MAP 中的 ODS 源表无效"


# ══════════════════════════════════════════════════════════════════
# Property 3.3: C 类表在 FACT_MAPPINGS 中有条目
# ══════════════════════════════════════════════════════════════════

def test_c_class_tables_have_fact_mappings():
    """
    **Validates: Requirements 7.2, 8.2**

    C 类表必须在 FACT_MAPPINGS 中有至少一个映射条目。
    """
    for table in C_CLASS_TABLES:
        entries = DwdLoadTask.FACT_MAPPINGS.get(table, [])
        assert len(entries) > 0, \
            f"C 类表 {table} 在 FACT_MAPPINGS 中无条目"


# ══════════════════════════════════════════════════════════════════
# Property 3.4: goods_stock_summary 14 个字段全覆盖
# ══════════════════════════════════════════════════════════════════

def test_goods_stock_summary_mapping_coverage():
    """
    **Validates: Requirements 7.2**

    dwd.dwd_goods_stock_summary 的 FACT_MAPPINGS 应覆盖全部 14 个期望字段。
    """
    table = "dwd.dwd_goods_stock_summary"
    entries = DwdLoadTask.FACT_MAPPINGS.get(table, [])
    actual_cols = {e[0].lower() for e in entries}

    # 验证数量
    assert len(entries) == 14, \
        f"{table}: 期望 14 个映射条目，实际 {len(entries)} 个"

    # 验证字段覆盖
    missing = _GOODS_STOCK_SUMMARY_EXPECTED_COLS - actual_cols
    assert not missing, \
        f"{table}: 缺少映射字段 {missing}"

    extra = actual_cols - _GOODS_STOCK_SUMMARY_EXPECTED_COLS
    assert not extra, \
        f"{table}: 存在多余映射字段 {extra}"


# ══════════════════════════════════════════════════════════════════
# Property 3.5: goods_stock_movement 19 个字段全覆盖
# ══════════════════════════════════════════════════════════════════

def test_goods_stock_movement_mapping_coverage():
    """
    **Validates: Requirements 8.2**

    dwd.dwd_goods_stock_movement 的 FACT_MAPPINGS 应覆盖全部 19 个期望字段。
    """
    table = "dwd.dwd_goods_stock_movement"
    entries = DwdLoadTask.FACT_MAPPINGS.get(table, [])
    actual_cols = {e[0].lower() for e in entries}

    # 验证数量
    assert len(entries) == 19, \
        f"{table}: 期望 19 个映射条目，实际 {len(entries)} 个"

    # 验证字段覆盖
    missing = _GOODS_STOCK_MOVEMENT_EXPECTED_COLS - actual_cols
    assert not missing, \
        f"{table}: 缺少映射字段 {missing}"

    extra = actual_cols - _GOODS_STOCK_MOVEMENT_EXPECTED_COLS
    assert not extra, \
        f"{table}: 存在多余映射字段 {extra}"


# ══════════════════════════════════════════════════════════════════
# Property 3.6: FACT_MAPPINGS 是 TABLE_MAP 的子集
# ══════════════════════════════════════════════════════════════════

def test_fact_mappings_subset_of_table_map():
    """FACT_MAPPINGS 中的所有 DWD 表都必须在 TABLE_MAP 中注册。"""
    for table in DwdLoadTask.FACT_MAPPINGS:
        assert table in DwdLoadTask.TABLE_MAP, \
            f"FACT_MAPPINGS 中的 {table} 未在 TABLE_MAP 中注册"


# ══════════════════════════════════════════════════════════════════
# Hypothesis 属性测试：随机 TABLE_MAP 条目结构验证
# ══════════════════════════════════════════════════════════════════

_table_map_entry_strategy = st.sampled_from(_ALL_TABLE_MAP_ENTRIES)


@given(entry=_table_map_entry_strategy)
@settings(max_examples=200, suppress_health_check=[HealthCheck.function_scoped_fixture])
def test_random_table_map_entry_valid(entry):
    """
    **Validates: Requirements 7.2, 8.2**

    对于任意随机选取的 TABLE_MAP 条目，验证：
    - DWD 表名为 "dwd." 前缀的非空字符串
    - ODS 源表名为 "ods." 前缀的非空字符串
    - 如果该表在 FACT_MAPPINGS 中有条目，每个条目都是合法的三元组
    """
    dwd_table, ods_table = entry

    # DWD 表名格式验证
    assert isinstance(dwd_table, str) and dwd_table.startswith("dwd."), \
        f"TABLE_MAP key {dwd_table!r} 不以 'dwd.' 开头"

    # ODS 源表名格式验证
    assert isinstance(ods_table, str) and ods_table.startswith("ods."), \
        f"TABLE_MAP[{dwd_table}] = {ods_table!r} 不以 'ods.' 开头"

    # 如果有 FACT_MAPPINGS 条目，验证结构
    entries = DwdLoadTask.FACT_MAPPINGS.get(dwd_table, [])
    for i, e in enumerate(entries):
        assert isinstance(e, (tuple, list)) and len(e) == 3, \
            f"{dwd_table}[{i}]: FACT_MAPPINGS 条目应为三元组"
        dwd_col, ods_expr, cast_type = e
        assert isinstance(dwd_col, str) and dwd_col.strip(), \
            f"{dwd_table}[{i}]: dwd_col 为空"
        assert isinstance(ods_expr, str) and ods_expr.strip(), \
            f"{dwd_table}[{i}]: ods_expr 为空"
        assert cast_type is None or isinstance(cast_type, str), \
            f"{dwd_table}[{i}].{dwd_col}: cast_type 类型无效"
