# -*- coding: utf-8 -*-
"""任务注册表分组属性测试（Property-Based Testing）。

Property 4: 对于 Task_Registry 中的任务集合，分组结果中每个任务应出现在
且仅出现在其所属业务域的分组中。

Validates: Requirements 2.1

测试策略：
1. 直接测试 get_tasks_grouped_by_domain 函数：
   - 每个任务出现在且仅出现在其 domain 对应的分组中
   - 分组中的任务总数等于全部任务数（不多不少）
   - 每个分组的 key 等于该分组内所有任务的 domain
2. 通过 API 端点测试（TestClient + mock auth）：
   - 返回的 groups 中每个任务的 domain 与其所在分组 key 一致
   - 所有任务都出现在结果中
3. 随机子集验证：
   - 随机选取任务子集，验证分组逻辑的一致性
   - 随机选取 domain，验证该 domain 下的任务都正确
"""

import os

os.environ.setdefault("JWT_SECRET_KEY", "test-secret-key-for-registry")

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

from app.services.task_registry import (
    get_all_tasks,
    get_tasks_grouped_by_domain,
    TaskDefinition,
)
from fastapi.testclient import TestClient
from app.main import app
from app.auth.dependencies import get_current_user, CurrentUser


# ---------------------------------------------------------------------------
# 辅助
# ---------------------------------------------------------------------------

ALL_TASKS = get_all_tasks()
ALL_CODES = [t.code for t in ALL_TASKS]
ALL_DOMAINS = list({t.domain for t in ALL_TASKS})


def _mock_user() -> CurrentUser:
    return CurrentUser(user_id=1, site_id=1)


# ---------------------------------------------------------------------------
# Property 4.1: 分组完整性 — 每个任务出现在且仅出现在其 domain 分组中
# Validates: Requirements 2.1
# ---------------------------------------------------------------------------

@settings(max_examples=100)
@given(data=st.data())
def test_every_task_in_exactly_its_domain_group(data):
    """Property 4.1: 每个任务出现在且仅出现在其所属业务域的分组中。

    从全量任务中随机选取一个任务，验证它只出现在对应 domain 的分组里，
    且不出现在其他任何分组中。
    """
    grouped = get_tasks_grouped_by_domain()
    # 随机选取一个任务
    task = data.draw(st.sampled_from(ALL_TASKS))

    # 该任务必须出现在其 domain 分组中
    assert task.domain in grouped, (
        f"任务 {task.code} 的 domain '{task.domain}' 不在分组 keys 中"
    )
    domain_codes = [t.code for t in grouped[task.domain]]
    assert task.code in domain_codes, (
        f"任务 {task.code} 未出现在其 domain '{task.domain}' 的分组中"
    )

    # 该任务不应出现在其他任何分组中
    for other_domain, other_tasks in grouped.items():
        if other_domain == task.domain:
            continue
        other_codes = [t.code for t in other_tasks]
        assert task.code not in other_codes, (
            f"任务 {task.code}（domain={task.domain}）错误地出现在 "
            f"domain '{other_domain}' 的分组中"
        )


# ---------------------------------------------------------------------------
# Property 4.2: 分组总数守恒 — 分组中的任务总数等于全部任务数
# Validates: Requirements 2.1
# ---------------------------------------------------------------------------

@settings(max_examples=100)
@given(data=st.data())
def test_grouped_total_equals_all_tasks(data):
    """Property 4.2: 分组中的任务总数等于全部任务数（不多不少）。

    随机选取若干 domain 进行局部验证，同时验证全局总数守恒。
    """
    all_tasks = get_all_tasks()
    grouped = get_tasks_grouped_by_domain()

    # 全局守恒：分组内任务总数 == 全量任务数
    grouped_total = sum(len(tasks) for tasks in grouped.values())
    assert grouped_total == len(all_tasks), (
        f"分组总数 {grouped_total} != 全量任务数 {len(all_tasks)}"
    )

    # 随机选取一个 domain，验证该 domain 下的任务数量正确
    domain = data.draw(st.sampled_from(ALL_DOMAINS))
    expected_count = sum(1 for t in all_tasks if t.domain == domain)
    actual_count = len(grouped[domain])
    assert actual_count == expected_count, (
        f"domain '{domain}' 分组内任务数 {actual_count} != 预期 {expected_count}"
    )


# ---------------------------------------------------------------------------
# Property 4.3: 分组 key 一致性 — 每个分组的 key 等于组内所有任务的 domain
# Validates: Requirements 2.1
# ---------------------------------------------------------------------------

@settings(max_examples=100)
@given(data=st.data())
def test_group_key_matches_task_domains(data):
    """Property 4.3: 每个分组的 key 等于该分组内所有任务的 domain。

    随机选取一个 domain 分组，验证组内每个任务的 domain 字段都等于分组 key。
    """
    grouped = get_tasks_grouped_by_domain()
    domain = data.draw(st.sampled_from(list(grouped.keys())))

    for task in grouped[domain]:
        assert task.domain == domain, (
            f"分组 '{domain}' 中的任务 {task.code} 的 domain 为 "
            f"'{task.domain}'，与分组 key 不一致"
        )


# ---------------------------------------------------------------------------
# Property 4.4: 任务 code 全局唯一 — 分组后不应出现重复 code
# Validates: Requirements 2.1
# ---------------------------------------------------------------------------

@settings(max_examples=100)
@given(data=st.data())
def test_no_duplicate_codes_across_groups(data):
    """Property 4.4: 分组后所有任务的 code 全局唯一，无重复。

    随机选取若干 domain 的任务合并，验证 code 不重复。
    """
    grouped = get_tasks_grouped_by_domain()

    # 收集所有分组中的 code
    all_codes_in_groups = []
    for tasks in grouped.values():
        all_codes_in_groups.extend(t.code for t in tasks)

    assert len(all_codes_in_groups) == len(set(all_codes_in_groups)), (
        "分组中存在重复的任务 code"
    )

    # 随机选取两个不同 domain，验证它们的任务 code 无交集
    if len(ALL_DOMAINS) >= 2:
        domains = data.draw(
            st.lists(st.sampled_from(ALL_DOMAINS), min_size=2, max_size=2, unique=True)
        )
        codes_a = {t.code for t in grouped[domains[0]]}
        codes_b = {t.code for t in grouped[domains[1]]}
        overlap = codes_a & codes_b
        assert not overlap, (
            f"domain '{domains[0]}' 和 '{domains[1]}' 存在重叠任务 code: {overlap}"
        )


# ---------------------------------------------------------------------------
# Property 4.5: 随机子集分组一致性 — 子集中的任务分组结果与全量一致
# Validates: Requirements 2.1
# ---------------------------------------------------------------------------

@settings(max_examples=100)
@given(
    indices=st.lists(
        st.integers(min_value=0, max_value=len(ALL_TASKS) - 1),
        min_size=1,
        max_size=min(20, len(ALL_TASKS)),
        unique=True,
    )
)
def test_subset_grouping_consistency(indices):
    """Property 4.5: 随机选取任务子集，验证每个任务在全量分组中的归属正确。

    对于随机选取的任务子集，每个任务在 get_tasks_grouped_by_domain() 的结果中
    都应出现在其 domain 对应的分组里。
    """
    grouped = get_tasks_grouped_by_domain()
    subset = [ALL_TASKS[i] for i in indices]

    for task in subset:
        # 任务的 domain 必须是分组的 key 之一
        assert task.domain in grouped
        # 任务必须在对应分组中
        group_codes = {t.code for t in grouped[task.domain]}
        assert task.code in group_codes, (
            f"任务 {task.code} 未出现在 domain '{task.domain}' 的分组中"
        )


# ---------------------------------------------------------------------------
# Property 4.6: API 端点分组正确性 — GET /api/tasks/registry 返回一致的分组
# Validates: Requirements 2.1
# ---------------------------------------------------------------------------

@settings(max_examples=100)
@given(data=st.data())
def test_api_registry_grouping_correctness(data):
    """Property 4.6: API 端点返回的分组中，每个任务的 domain 与分组 key 一致，
    且所有任务都出现在结果中。
    """
    app.dependency_overrides[get_current_user] = _mock_user
    try:
        client = TestClient(app)
        resp = client.get("/api/tasks/registry")

        assert resp.status_code == 200
        body = resp.json()
        groups = body["groups"]

        # 收集 API 返回的所有任务 code
        api_codes: set[str] = set()
        for domain_key, task_list in groups.items():
            for task_item in task_list:
                # 每个任务的 domain 必须等于分组 key
                assert task_item["domain"] == domain_key, (
                    f"API 返回的任务 {task_item['code']}（domain={task_item['domain']}）"
                    f"出现在分组 '{domain_key}' 中，不一致"
                )
                api_codes.add(task_item["code"])

        # 所有任务都应出现在 API 结果中
        all_codes_set = {t.code for t in get_all_tasks()}
        assert api_codes == all_codes_set, (
            f"API 返回的任务集合与全量任务不一致。"
            f"缺失: {all_codes_set - api_codes}，"
            f"多余: {api_codes - all_codes_set}"
        )

        # 随机选取一个 domain，验证该 domain 下的任务数量与服务层一致
        if groups:
            domain = data.draw(st.sampled_from(list(groups.keys())))
            expected = get_tasks_grouped_by_domain()
            assert len(groups[domain]) == len(expected[domain]), (
                f"API 返回的 domain '{domain}' 任务数 {len(groups[domain])} "
                f"!= 服务层 {len(expected[domain])}"
            )
    finally:
        app.dependency_overrides.pop(get_current_user, None)


# ---------------------------------------------------------------------------
# Property 4.7: 随机 domain 过滤验证
# Validates: Requirements 2.1
# ---------------------------------------------------------------------------

@settings(max_examples=100)
@given(domain=st.sampled_from(ALL_DOMAINS))
def test_random_domain_tasks_all_correct(domain):
    """Property 4.7: 随机选取一个 domain，验证该 domain 下的所有任务都正确归属。

    对于选定的 domain：
    - 分组中的每个任务的 domain 字段都等于选定的 domain
    - 全量任务中所有属于该 domain 的任务都出现在分组中
    """
    grouped = get_tasks_grouped_by_domain()
    all_tasks = get_all_tasks()

    # 分组中该 domain 的任务
    group_tasks = grouped.get(domain, [])

    # 全量任务中属于该 domain 的任务
    expected_tasks = [t for t in all_tasks if t.domain == domain]

    # 数量一致
    assert len(group_tasks) == len(expected_tasks), (
        f"domain '{domain}': 分组中 {len(group_tasks)} 个任务，"
        f"预期 {len(expected_tasks)} 个"
    )

    # code 集合一致
    group_codes = {t.code for t in group_tasks}
    expected_codes = {t.code for t in expected_tasks}
    assert group_codes == expected_codes, (
        f"domain '{domain}': 分组 codes {group_codes} != 预期 {expected_codes}"
    )

    # 每个任务的 domain 字段都正确
    for task in group_tasks:
        assert task.domain == domain
