from dataclasses import dataclass, field

from .concept_graph import ConceptGraph
from .evaluator_pipeline import (
    LearnerAttempt,
    RubricEvaluator,
    CodeTestEvaluator,
    SymbolicRuleEvaluator,
    CritiqueEvaluator,
    PortfolioEvaluator,
    run_pipeline,
    aggregate,
)
from .planner import PlannerWeights, rank_next_concepts


@dataclass
class ConceptEvidenceSummary:
    concept_key: str
    weak_dimensions: list[str] = field(default_factory=list)
    mastered: bool = False
    aggregated: dict = field(default_factory=dict)
    evaluators: list[str] = field(default_factory=list)


@dataclass
class EvidenceState:
    summary_by_concept: dict[str, ConceptEvidenceSummary] = field(default_factory=dict)
    resurfaced_concepts: set[str] = field(default_factory=set)


@dataclass
class AgenticStudentState:
    learner_id: str = "demo-agent"
    display_name: str = "Demo Agentic Student"
    mastered_concepts: set[str] = field(default_factory=set)
    evidence_state: EvidenceState = field(default_factory=EvidenceState)
    attempt_history: list[dict] = field(default_factory=list)
    artifacts: list[dict] = field(default_factory=list)


def synthetic_attempt_for_concept(concept: str) -> LearnerAttempt:
    if "descriptive-statistics" in concept:
        return LearnerAttempt(
            concept=concept,
            artifact_type="explanation",
            content="Mean and variance summarize a dataset because they describe center and spread.",
            metadata={"deliverable_count": 1, "artifact_name": "descriptive_statistics_note.md"},
        )
    if "probability-basics" in concept:
        return LearnerAttempt(
            concept=concept,
            artifact_type="explanation",
            content="Conditional probability changes because context changes the sample space.",
            metadata={"deliverable_count": 1, "artifact_name": "probability_basics_note.md"},
        )
    if "prior" in concept:
        return LearnerAttempt(
            concept=concept,
            artifact_type="explanation",
            content="A prior is an assumption before evidence, but one limitation is bias.",
            metadata={"deliverable_count": 1, "artifact_name": "prior_reflection.md"},
        )
    if "posterior" in concept:
        return LearnerAttempt(
            concept=concept,
            artifact_type="symbolic",
            content="Therefore posterior = updated belief after evidence, but one assumption may be model fit.",
            metadata={"deliverable_count": 1, "artifact_name": "posterior_symbolic_note.md"},
        )
    return LearnerAttempt(
        concept=concept,
        artifact_type="critique",
        content="A weakness is hidden assumptions; a limitation is poor fit; uncertainty remains.",
        metadata={"deliverable_count": 2, "artifact_name": "critique_report.md"},
    )


def evaluator_set_for_attempt(attempt: LearnerAttempt):
    evaluators = [RubricEvaluator(), CritiqueEvaluator()]
    if attempt.artifact_type == "code":
        evaluators.append(CodeTestEvaluator())
    if attempt.artifact_type == "symbolic":
        evaluators.append(SymbolicRuleEvaluator())
    if attempt.artifact_type in {"project", "portfolio", "critique"}:
        evaluators.append(PortfolioEvaluator())
    return evaluators


def integrate_attempt(state: AgenticStudentState, attempt: LearnerAttempt) -> None:
    results = run_pipeline(attempt, evaluator_set_for_attempt(attempt))
    aggregated = aggregate(results)
    weak = [dim for dim, score in aggregated.items() if score < 0.75]
    mastered = len(aggregated) > 0 and all(score >= 0.75 for score in aggregated.values())

    summary = ConceptEvidenceSummary(
        concept_key=attempt.concept,
        weak_dimensions=weak,
        mastered=mastered,
        aggregated=aggregated,
        evaluators=[r.evaluator_name for r in results],
    )
    state.evidence_state.summary_by_concept[attempt.concept] = summary

    if mastered:
        state.mastered_concepts.add(attempt.concept)
        state.evidence_state.resurfaced_concepts.discard(attempt.concept)
    else:
        if attempt.concept in state.mastered_concepts:
            state.mastered_concepts.remove(attempt.concept)
            state.evidence_state.resurfaced_concepts.add(attempt.concept)

    state.attempt_history.append({
        "concept": attempt.concept,
        "artifact_type": attempt.artifact_type,
        "aggregated": aggregated,
        "weak_dimensions": weak,
        "mastered": mastered,
        "evaluators": [r.evaluator_name for r in results],
    })

    state.artifacts.append({
        "concept": attempt.concept,
        "artifact_type": attempt.artifact_type,
        "artifact_name": attempt.metadata.get("artifact_name", f"{attempt.concept}.txt"),
    })


def run_demo_agentic_loop(concepts: list[str]) -> AgenticStudentState:
    state = AgenticStudentState()
    for concept in concepts:
        attempt = synthetic_attempt_for_concept(concept)
        integrate_attempt(state, attempt)
    return state


def run_agentic_learning_loop(
    graph: ConceptGraph,
    project_catalog: list[dict],
    target_concepts: list[str],
    weights: PlannerWeights,
    max_steps: int = 4,
) -> AgenticStudentState:
    state = AgenticStudentState()
    for _ in range(max_steps):
        ranked = rank_next_concepts(
            graph=graph,
            mastered=state.mastered_concepts,
            targets=target_concepts,
            weak_dimensions_by_concept={},
            fragile_concepts=state.evidence_state.resurfaced_concepts,
            project_catalog=project_catalog,
            weights=weights,
        )
        if not ranked:
            break
        concept = ranked[0]["concept"]
        integrate_attempt(state, synthetic_attempt_for_concept(concept))
        if set(target_concepts).issubset(state.mastered_concepts):
            break
    return state
