# -*- coding: utf-8 -*-
"""
数据一致性检查器单元测试

测试 quality/consistency_checker.py 的核心纯函数逻辑，
不依赖数据库连接。
"""
import json
import pytest
from pathlib import Path

from quality.consistency_checker import (
    FieldCheckResult,
    TableCheckResult,
    ConsistencyReport,
    check_api_vs_ods_fields,
    check_ods_vs_dwd_mappings,
    extract_api_fields_from_json,
    generate_markdown_report,
    _validate_ods_expression,
    _extract_records,
)


# ---------------------------------------------------------------------------
# extract_api_fields_from_json
# ---------------------------------------------------------------------------

class TestExtractApiFields:
    """API JSON 字段提取"""

    def test_flat_list(self, tmp_path: Path):
        """直接列表格式"""
        data = [{"id": 1, "name": "test", "amount": 100}]
        p = tmp_path / "test.json"
        p.write_text(json.dumps(data), encoding="utf-8")
        fields = extract_api_fields_from_json(p)
        assert fields == {"id", "name", "amount"}

    def test_nested_data_key(self, tmp_path: Path):
        """{"data": [...]} 格式"""
        data = {"data": [{"id": 1, "foo": "bar"}]}
        p = tmp_path / "test.json"
        p.write_text(json.dumps(data), encoding="utf-8")
        fields = extract_api_fields_from_json(p)
        assert fields == {"id", "foo"}

    def test_nested_data_with_list_key(self, tmp_path: Path):
        """{"data": {"settleList": [...]}} 格式"""
        data = {"data": {"settleList": [{"id": 1, "amount": 50}]}}
        p = tmp_path / "test.json"
        p.write_text(json.dumps(data), encoding="utf-8")
        fields = extract_api_fields_from_json(p)
        assert fields == {"id", "amount"}

    def test_nonexistent_file(self, tmp_path: Path):
        """文件不存在返回 None"""
        p = tmp_path / "nonexistent.json"
        assert extract_api_fields_from_json(p) is None

    def test_invalid_json(self, tmp_path: Path):
        """无效 JSON 返回 None"""
        p = tmp_path / "bad.json"
        p.write_text("not json", encoding="utf-8")
        assert extract_api_fields_from_json(p) is None

    def test_empty_list(self, tmp_path: Path):
        """空列表返回 None"""
        p = tmp_path / "empty.json"
        p.write_text("[]", encoding="utf-8")
        assert extract_api_fields_from_json(p) is None

    def test_merges_multiple_records(self, tmp_path: Path):
        """合并多条记录的字段"""
        data = [{"id": 1, "a": 1}, {"id": 2, "b": 2}]
        p = tmp_path / "test.json"
        p.write_text(json.dumps(data), encoding="utf-8")
        fields = extract_api_fields_from_json(p)
        assert fields == {"id", "a", "b"}


# ---------------------------------------------------------------------------
# check_api_vs_ods_fields
# ---------------------------------------------------------------------------

class TestCheckApiVsOds:
    """API vs ODS 字段完整性检查"""

    def test_all_fields_present(self):
        """所有 API 字段在 ODS 中都存在"""
        api = {"id", "name", "amount"}
        ods = {"id", "name", "amount", "fetched_at", "content_hash"}
        result = check_api_vs_ods_fields(api, ods)
        assert result.passed is True
        assert result.missing_fields == 0
        assert result.passed_fields == 3

    def test_missing_fields(self):
        """部分 API 字段在 ODS 中缺失"""
        api = {"id", "name", "extra_field"}
        ods = {"id", "name", "fetched_at"}
        result = check_api_vs_ods_fields(api, ods)
        assert result.passed is False
        assert result.missing_fields == 1
        missing = [f for f in result.field_results if f.status == "missing"]
        assert len(missing) == 1
        assert missing[0].field_name == "extra_field"

    def test_ods_meta_columns_excluded(self):
        """ODS 元数据列不参与对比"""
        api = {"id"}
        ods = {"id", "payload", "fetched_at", "content_hash", "source_file"}
        result = check_api_vs_ods_fields(api, ods)
        assert result.passed is True
        assert result.total_fields == 1

    def test_case_insensitive(self):
        """大小写不敏感匹配"""
        api = {"Id", "Name"}
        ods = {"id", "name", "fetched_at"}
        result = check_api_vs_ods_fields(api, ods)
        assert result.passed is True

    def test_empty_api_fields(self):
        """空 API 字段集"""
        result = check_api_vs_ods_fields(set(), {"id", "fetched_at"})
        assert result.passed is True
        assert result.total_fields == 0


# ---------------------------------------------------------------------------
# check_ods_vs_dwd_mappings
# ---------------------------------------------------------------------------

class TestCheckOdsVsDwd:
    """ODS vs DWD 映射正确性检查"""

    def test_all_mapped_explicitly(self):
        """所有 DWD 列都有显式映射"""
        dwd_cols = {"order_id", "amount", "scd2_is_current", "scd2_version"}
        ods_cols = {"id", "total_amount"}
        mappings = [
            ("order_id", "id", None),
            ("amount", "total_amount", None),
        ]
        result = check_ods_vs_dwd_mappings(
            "dwd.test_table", "ods.test_table",
            dwd_cols, ods_cols, mappings,
        )
        assert result.passed is True
        # SCD2 列不参与检查
        assert result.total_fields == 2

    def test_auto_mapping(self):
        """同名列自动映射"""
        dwd_cols = {"id", "name", "amount"}
        ods_cols = {"id", "name", "amount"}
        result = check_ods_vs_dwd_mappings(
            "dwd.test", "ods.test",
            dwd_cols, ods_cols, None,
        )
        assert result.passed is True
        assert result.passed_fields == 3

    def test_missing_mapping(self):
        """DWD 列无映射源"""
        dwd_cols = {"id", "orphan_col"}
        ods_cols = {"id"}
        result = check_ods_vs_dwd_mappings(
            "dwd.test", "ods.test",
            dwd_cols, ods_cols, None,
        )
        assert result.passed is False
        assert result.missing_fields == 1
        missing = [f for f in result.field_results if f.status == "missing"]
        assert missing[0].field_name == "orphan_col"

    def test_json_expression_mapping(self):
        """JSON 路径表达式映射"""
        dwd_cols = {"shop_name"}
        ods_cols = {"siteprofile"}
        mappings = [("shop_name", "siteprofile->>'shop_name'", None)]
        result = check_ods_vs_dwd_mappings(
            "dwd.test", "ods.test",
            dwd_cols, ods_cols, mappings,
        )
        assert result.passed is True

    def test_sql_expression_mapping(self):
        """SQL 表达式映射（CASE WHEN 等）"""
        dwd_cols = {"is_leaf"}
        ods_cols = {"categoryboxes"}
        mappings = [
            ("is_leaf",
             "CASE WHEN categoryboxes IS NULL OR jsonb_array_length(categoryboxes)=0 THEN 1 ELSE 0 END",
             None),
        ]
        result = check_ods_vs_dwd_mappings(
            "dwd.test", "ods.test",
            dwd_cols, ods_cols, mappings,
        )
        assert result.passed is True

    def test_scd2_columns_excluded(self):
        """SCD2 列不参与检查"""
        dwd_cols = {"id", "scd2_start_time", "scd2_end_time", "scd2_is_current", "scd2_version"}
        ods_cols = {"id"}
        result = check_ods_vs_dwd_mappings(
            "dwd.test", "ods.test",
            dwd_cols, ods_cols, None,
        )
        assert result.total_fields == 1
        assert result.passed is True

    def test_invalid_ods_reference(self):
        """显式映射引用了不存在的 ODS 列"""
        dwd_cols = {"bad_col"}
        ods_cols = {"id"}
        mappings = [("bad_col", "nonexistent_col", None)]
        result = check_ods_vs_dwd_mappings(
            "dwd.test", "ods.test",
            dwd_cols, ods_cols, mappings,
        )
        assert result.passed is False
        assert result.mismatch_fields == 1

    def test_null_expression(self):
        """NULL 字面量映射"""
        dwd_cols = {"null_col"}
        ods_cols = {"id"}
        mappings = [("null_col", "NULL", None)]
        result = check_ods_vs_dwd_mappings(
            "dwd.test", "ods.test",
            dwd_cols, ods_cols, mappings,
        )
        assert result.passed is True

    def test_mixed_auto_and_explicit(self):
        """混合自动映射和显式映射"""
        dwd_cols = {"id", "name", "mapped_col", "scd2_is_current"}
        ods_cols = {"id", "name", "source_col"}
        mappings = [("mapped_col", "source_col", None)]
        result = check_ods_vs_dwd_mappings(
            "dwd.test", "ods.test",
            dwd_cols, ods_cols, mappings,
        )
        assert result.passed is True
        assert result.passed_fields == 3  # id, name, mapped_col


# ---------------------------------------------------------------------------
# _validate_ods_expression
# ---------------------------------------------------------------------------

class TestValidateOdsExpression:
    """ODS 表达式验证"""

    def test_simple_column(self):
        assert _validate_ods_expression("name", {"name", "id"}) is True

    def test_missing_column(self):
        assert _validate_ods_expression("missing", {"name", "id"}) is False

    def test_quoted_column(self):
        assert _validate_ods_expression('"siteGoodsId"', {"sitegoodsid"}) is True

    def test_json_path(self):
        assert _validate_ods_expression("siteprofile->>'shop_name'", {"siteprofile"}) is True

    def test_json_path_missing_base(self):
        assert _validate_ods_expression("missing_col->>'field'", {"siteprofile"}) is False

    def test_null_literal(self):
        assert _validate_ods_expression("NULL", set()) is True

    def test_case_expression(self):
        assert _validate_ods_expression("CASE WHEN x=1 THEN 'a' ELSE 'b' END", set()) is True

    def test_coalesce_expression(self):
        assert _validate_ods_expression("COALESCE(a, b)", set()) is True


# ---------------------------------------------------------------------------
# _extract_records
# ---------------------------------------------------------------------------

class TestExtractRecords:
    """API 响应记录提取"""

    def test_direct_list(self):
        assert _extract_records([{"a": 1}]) == [{"a": 1}]

    def test_data_list(self):
        assert _extract_records({"data": [{"a": 1}]}) == [{"a": 1}]

    def test_nested_list_key(self):
        result = _extract_records({"data": {"items": [{"a": 1}]}})
        assert result == [{"a": 1}]

    def test_empty_data(self):
        assert _extract_records({}) == []

    def test_none_input(self):
        assert _extract_records(None) == []


# ---------------------------------------------------------------------------
# generate_markdown_report
# ---------------------------------------------------------------------------

class TestGenerateMarkdownReport:
    """Markdown 报告生成"""

    def test_empty_report(self):
        report = ConsistencyReport(generated_at="2026-01-01T00:00:00")
        md = generate_markdown_report(report)
        assert "数据一致性黑盒测试报告" in md
        assert "全部通过" in md

    def test_failed_report(self):
        report = ConsistencyReport(
            generated_at="2026-01-01T00:00:00",
            ods_vs_dwd_results=[
                TableCheckResult(
                    table_name="dwd.test",
                    check_type="ods_vs_dwd",
                    passed=False,
                    total_fields=3,
                    passed_fields=2,
                    missing_fields=1,
                    field_results=[
                        FieldCheckResult("col_a", "pass", "自动映射"),
                        FieldCheckResult("col_b", "pass", "显式映射"),
                        FieldCheckResult("col_c", "missing", "无映射源"),
                    ],
                ),
            ],
        )
        md = generate_markdown_report(report)
        assert "存在异常" in md
        assert "col_c" in md
        assert "missing" in md

    def test_report_contains_summary(self):
        report = ConsistencyReport(
            generated_at="2026-01-01T00:00:00",
            api_vs_ods_results=[
                TableCheckResult("ods.t1", "api_vs_ods", passed=True, total_fields=5, passed_fields=5),
                TableCheckResult("ods.t2", "api_vs_ods", passed=False, total_fields=3, passed_fields=2, missing_fields=1),
            ],
        )
        md = generate_markdown_report(report)
        assert "1/2 张表通过" in md
