import dataclasses
from typing import List, Optional

from .actual import ActualSummary
from .inspect import ModelStructureFacts, ProfileCandidate
from .patch_report import PatchReport
from .verifier import VerificationReport


@dataclasses.dataclass(frozen=True)
class AdvisorSuggestion:
    code: str
    message: str
    confidence: str
    evidence: str


def _has_category(report: VerificationReport, category: str) -> bool:
    return any(issue.category == category for issue in report.issues)


def advise(
    structure: Optional[ModelStructureFacts] = None,
    candidate: Optional[ProfileCandidate] = None,
    patch_reports: Optional[List[PatchReport]] = None,
    actual: Optional[ActualSummary] = None,
    verification: Optional[VerificationReport] = None,
) -> List[AdvisorSuggestion]:
    suggestions: List[AdvisorSuggestion] = []
    patch_reports = [] if patch_reports is None else patch_reports

    for report in patch_reports:
        if report.target_module_name and report.replacement_count == 0:
            suggestions.append(
                AdvisorSuggestion(
                    code="PATCH_NOT_APPLIED",
                    message=(
                        f"{report.pass_name} matched {len(report.matched_modules)} "
                        f"{report.target_module_name} modules but replaced none. "
                        "Check module_name and field name overrides."
                    ),
                    confidence="high",
                    evidence=f"patch_reports[{report.pass_name}].skipped_modules",
                )
            )
        elif report.expected_replacements is not None and report.replacement_count < report.expected_replacements:
            suggestions.append(
                AdvisorSuggestion(
                    code="PATCH_COUNT_MISMATCH",
                    message=(
                        f"{report.pass_name} replaced {report.replacement_count} modules, "
                        f"expected {report.expected_replacements}. Check hybrid layers or patterns."
                    ),
                    confidence="high",
                    evidence=f"patch_reports[{report.pass_name}].replaced_modules",
                )
            )

    if verification is not None:
        if _has_category(verification, "OP_COUNT_MISMATCH"):
            suggestions.append(
                AdvisorSuggestion(
                    code="OP_COUNT_MISMATCH",
                    message=(
                        "Major op count differs from evidence. Check repetition, MTP/layer override, "
                        "or missing TensorCast wrapper replacement."
                    ),
                    confidence="medium",
                    evidence="verification.issues[OP_COUNT_MISMATCH]",
                )
            )
        if _has_category(verification, "OP_MAPPING_MISSING"):
            suggestions.append(
                AdvisorSuggestion(
                    code="OP_MAPPING_MISSING",
                    message=(
                        "Expected profiling op is absent in actual summary. Check op naming/mapping "
                        "or whether the model falls back to original HF modules."
                    ),
                    confidence="medium",
                    evidence="verification.issues[OP_MAPPING_MISSING]",
                )
            )
        if _has_category(verification, "LATENCY_MODEL_MISMATCH"):
            message = "Latency differs while deterministic counts may still match. Check profiling coverage first."
            if actual and actual.coverage:
                message += " Coverage data is available in ActualSummary.coverage."
            suggestions.append(
                AdvisorSuggestion(
                    code="LATENCY_MODEL_MISMATCH",
                    message=message,
                    confidence="medium",
                    evidence="verification.issues[LATENCY_MODEL_MISMATCH]",
                )
            )
        if _has_category(verification, "PROFILING_SHAPE_MISSING"):
            suggestions.append(
                AdvisorSuggestion(
                    code="PROFILING_SHAPE_MISSING",
                    message=(
                        "Profiling hit rate is incomplete. Add missing op mapping/shape records "
                        "before treating latency mismatch as a model-structure bug."
                    ),
                    confidence="high",
                    evidence="verification.issues[PROFILING_SHAPE_MISSING]",
                )
            )
        if _has_category(verification, "PATCH_SEMANTICS_MISSING"):
            suggestions.append(
                AdvisorSuggestion(
                    code="PATCH_SEMANTICS_MISSING",
                    message=(
                        "Expected TensorCast wrapper ops are absent. Check ModelProfile fields, "
                        "runtime patch_method, and PatchReport replacement counts."
                    ),
                    confidence="high",
                    evidence="verification.issues[PATCH_SEMANTICS_MISSING]",
                )
            )
        if _has_category(verification, "COMMUNICATION_GAP"):
            suggestions.append(
                AdvisorSuggestion(
                    code="COMMUNICATION_GAP",
                    message=(
                        "Communication operators appear missing or unexplained. Check TP/DP/EP sizes, "
                        "collective naming, and whether communication evidence should be added or accepted."
                    ),
                    confidence="medium",
                    evidence="verification.issues[COMMUNICATION_GAP]",
                )
            )

    if structure and candidate:
        if structure.moe_like_modules and candidate.moe_module_name is None:
            suggestions.append(
                AdvisorSuggestion(
                    code="PROFILE_FIELD_MISSING_OR_WRONG",
                    message="MoE-like modules were found but no moe_module_name candidate was generated.",
                    confidence="medium",
                    evidence="ModelStructureFacts.moe_like_modules",
                )
            )
        if "deepseek_like_mla" in structure.known_recipe_matches and candidate.mla_module_name is None:
            suggestions.append(
                AdvisorSuggestion(
                    code="PROFILE_FIELD_MISSING_OR_WRONG",
                    message="MLA-like attention modules were found but no mla_module_name candidate was generated.",
                    confidence="low",
                    evidence="ModelStructureFacts.known_recipe_matches[deepseek_like_mla]",
                )
            )

    return suggestions