from __future__ import annotations

from .models import CompareIssue, GradeReport, LessonStep, SkillStep, StepCompareResult


CORE_ARGUMENTS = {
    "extrude_boss_mm": ["depth_mm", "end_condition_code", "through_all"],
    "extrude_cut_mm": ["depth_mm", "end_condition_code", "through_all"],
    "shell_remove_face_at_point_mm": ["thickness_mm"],
    "draw_circle_diameter_mm": ["diameter_mm", "center_model_mm", "cx_mm", "cy_mm"],
    "fillet_edges_radius_mm": ["radius_mm"],
    "chamfer_edges_angle_distance_mm": ["distance_mm", "angle_deg"],
    "linear_pattern_feature_mm": ["count1", "spacing1_mm"],
    "circular_pattern_feature": ["count", "angle_deg"],
}


class FeatureGrader:
    def compare_step(self, expected: LessonStep, observed: SkillStep | None) -> StepCompareResult:
        if observed is None:
            return StepCompareResult(
                ok=False,
                score=0,
                expected_step=expected,
                feedback_text=f"没有检测到本步骤的操作。请执行：{expected.title}。",
                issues=[
                    CompareIssue(
                        code="missing_operation",
                        message=f"Expected {expected.expected_skill}, but no user feature was observed.",
                        expected={"skill": expected.expected_skill},
                    )
                ],
            )

        issues: list[CompareIssue] = []
        if expected.expected_skill != observed.skill:
            issues.append(
                CompareIssue(
                    code="wrong_feature_method",
                    message=f"方法错误：标准答案要求 {expected.expected_skill}，学生实际使用 {observed.skill}。",
                    expected={"skill": expected.expected_skill, "title": expected.title},
                    actual={"skill": observed.skill, "name": observed.name, "source_feature": observed.source_feature},
                )
            )

        issues.extend(compare_core_arguments(expected.expected_skill, expected.expected_arguments, observed.arguments))
        ok = not issues
        return StepCompareResult(
            ok=ok,
            score=100.0 if ok else max(0.0, 60.0 - 20.0 * len(issues)),
            expected_step=expected,
            observed_step=observed,
            issues=issues,
            feedback_text=success_feedback(expected) if ok else failure_feedback(expected, issues),
        )

    def grade_sequence(self, expected: list[LessonStep], observed: list[SkillStep]) -> GradeReport:
        issues: list[CompareIssue] = []
        passed = 0
        total = len(expected)
        for index, step in enumerate(expected):
            obs = observed[index] if index < len(observed) else None
            result = self.compare_step(step, obs)
            if result.ok:
                passed += 1
            issues.extend(result.issues)

        score = 100.0 if total == 0 else round((passed / total) * 100.0, 2)
        return GradeReport(
            ok=score >= 90.0 and not any(issue.severity == "error" for issue in issues),
            score=score,
            passed_steps=passed,
            total_steps=total,
            issues=issues,
            summary=f"Passed {passed}/{total} feature-method steps.",
        )


def compare_core_arguments(skill: str, expected: dict[str, object], actual: dict[str, object]) -> list[CompareIssue]:
    result: list[CompareIssue] = []
    for key in CORE_ARGUMENTS.get(skill, []):
        if key not in expected:
            continue
        if key not in actual:
            result.append(
                CompareIssue(
                    code="missing_parameter",
                    message=f"参数缺失：{key}。",
                    expected={key: expected.get(key)},
                    actual={},
                )
            )
            continue
        if not values_close(expected.get(key), actual.get(key)):
            result.append(
                CompareIssue(
                    code="wrong_parameter",
                    message=f"参数错误：{key} 标准值为 {expected.get(key)}，实际值为 {actual.get(key)}。",
                    expected={key: expected.get(key)},
                    actual={key: actual.get(key)},
                )
            )
    return result


def values_close(expected: object, actual: object, tolerance: float = 0.05) -> bool:
    if isinstance(expected, (int, float)) and isinstance(actual, (int, float)):
        return abs(float(expected) - float(actual)) <= tolerance
    if isinstance(expected, list) and isinstance(actual, list) and len(expected) == len(actual):
        return all(values_close(e, a, tolerance) for e, a in zip(expected, actual))
    return str(expected) == str(actual)


def success_feedback(expected: LessonStep) -> str:
    return f"正确。你使用了本步骤要求的 {expected.title}，可以进入下一步。"


def failure_feedback(expected: LessonStep, issues: list[CompareIssue]) -> str:
    first = issues[0].message if issues else "操作不符合标准答案。"
    return f"{first} 本步骤要求：{expected.voice_text}"

