use serde_json::{Value, json};

use millrace_ai::contracts::{
    BlueprintCritiqueDocument, BlueprintDraftDocument, BlueprintDraftStatus,
    BlueprintEvaluationDecision, BlueprintEvaluationDocument, BlueprintManifestDocument,
    BlueprintPacketDocument, BlueprintPromotionRecord, BlueprintRepairAction,
    BlueprintRepairDecisionDocument, BlueprintRepairMutationPhase, BlueprintSourceWorkItemKind,
    RuntimeJsonContract, TaskDocument,
};

const NOW: &str = "2026-05-21T00:00:00Z";

fn round_trip<T>(value: Value) -> T
where
    T: RuntimeJsonContract + PartialEq + std::fmt::Debug,
{
    let decoded = T::from_json_value(value).unwrap();
    let serialized = serde_json::to_value(&decoded).unwrap();
    let decoded_again = T::from_json_value(serialized).unwrap();
    assert_eq!(decoded_again, decoded);
    decoded
}

fn manifest_json() -> Value {
    json!({
        "schema_version": "1.0",
        "kind": "blueprint_manifest",
        "manifest_id": "manifest-001",
        "root_spec_id": "spec-root",
        "root_idea_id": "idea-root",
        "source_work_item_kind": "spec",
        "source_work_item_id": "spec-root",
        "source_spec_id": "spec-root",
        "draft_ids": ["draft-001"],
        "draft_count": 1,
        "strict_sequence": true,
        "spec_summary": "Build the feature.",
        "decomposition_strategy": "One draft.",
        "global_acceptance_intent": ["all checks pass"],
        "created_at": NOW,
        "created_by": "manager_blueprint"
    })
}

fn draft_json() -> Value {
    json!({
        "schema_version": "1.0",
        "kind": "blueprint_draft",
        "draft_id": "draft-001",
        "manifest_id": "manifest-001",
        "root_spec_id": "spec-root",
        "root_idea_id": "idea-root",
        "source_spec_id": "spec-root",
        "sequence_number": 1,
        "dependency_draft_ids": [],
        "title": "Implement contract models",
        "scope_summary": "Add typed contract models.",
        "target_paths": ["src/contracts/blueprint.rs"],
        "acceptance": ["documents round trip"],
        "context_excerpt": "Relevant spec excerpt.",
        "revision": 0,
        "status": "queued",
        "created_at": NOW,
        "created_by": "manager_blueprint"
    })
}

fn packet_json() -> Value {
    json!({
        "schema_version": "1.0",
        "kind": "blueprint_packet",
        "packet_id": "blueprint-001",
        "draft_id": "draft-001",
        "manifest_id": "manifest-001",
        "root_spec_id": "spec-root",
        "root_idea_id": "idea-root",
        "revision": 1,
        "title": "Contract model packet",
        "implementation_scope": ["Add contracts"],
        "intended_files": ["src/contracts/blueprint.rs"],
        "design_decisions": ["Use serde aliases for legacy field names"],
        "verification_plan": ["cargo test --test contracts_blueprint"],
        "task_acceptance": ["Blueprint packet is valid"],
        "required_checks": ["cargo test --test contracts_blueprint"],
        "risk_notes": ["Schema drift"],
        "created_at": NOW,
        "created_by": "contractor_blueprint"
    })
}

fn approved_evaluation_json() -> Value {
    json!({
        "schema_version": "1.0",
        "kind": "blueprint_evaluation",
        "evaluation_id": "evaluation-002",
        "blueprint_id": "blueprint-001",
        "draft_id": "draft-001",
        "manifest_id": "manifest-001",
        "root_spec_id": "spec-root",
        "root_idea_id": "idea-root",
        "decision": "approved",
        "rubric_findings": ["Ready."],
        "required_task_fields": ["Task-ID", "Acceptance"],
        "created_at": NOW,
        "created_by": "evaluator_blueprint"
    })
}

fn repair_decision_json() -> Value {
    json!({
        "schema_version": "1.0",
        "kind": "blueprint_repair_decision",
        "repair_id": "repair-001",
        "failed_handler_id": "evaluator_blueprint_approved_to_task",
        "failed_run_id": "run-evaluator-001",
        "failed_stage_kind_id": "evaluator_blueprint",
        "failed_node_id": "evaluator_blueprint",
        "failed_terminal_result": "BLUEPRINT_APPROVED",
        "failure_class": "generated_task_invalid",
        "mutation_phase": "pre_mutation",
        "work_item_family_id": "blueprint_draft",
        "work_item_id": "draft-001",
        "draft_id": "draft-001",
        "manifest_id": "manifest-001",
        "root_spec_id": "spec-root",
        "root_idea_id": "idea-root",
        "repair_action": "apply_repaired_generated_task",
        "target_blueprint_id": "blueprint-001",
        "target_revision": 1,
        "target_evaluation_id": "evaluation-002",
        "generated_task_id": "task-001",
        "repaired_artifact_id": "repaired_generated_task",
        "repaired_artifact_path": "repaired_generated_task.json",
        "reason": "Generated task omitted required Blueprint lineage.",
        "verified_invariants": ["Evaluation, packet, draft, task, and root lineage match."],
        "references": ["run-evaluator-001/blueprint_evaluation.json"],
        "created_at": NOW,
        "created_by": "mechanic_blueprint"
    })
}

fn repaired_task() -> TaskDocument {
    serde_json::from_value(json!({
        "task_id": "task-001",
        "title": "Repaired generated task",
        "summary": "Generated by Mechanic Blueprint repair.",
        "root_idea_id": "idea-root",
        "root_spec_id": "spec-root",
        "spec_id": "spec-root",
        "target_paths": ["src/contracts/blueprint.rs"],
        "acceptance": ["repair decision is valid"],
        "required_checks": ["cargo test --test contracts_blueprint"],
        "references": ["millrace-agents/blueprints/evaluations/evaluation-002.json"],
        "risk": ["repair context drift"],
        "status_hint": "queued",
        "created_at": NOW,
        "created_by": "mechanic_blueprint"
    }))
    .unwrap()
}

#[test]
fn blueprint_documents_round_trip_and_preserve_alias_compatibility() {
    let manifest = round_trip::<BlueprintManifestDocument>(manifest_json());
    assert_eq!(
        manifest.source_work_item_kind,
        BlueprintSourceWorkItemKind::Spec
    );

    let draft = round_trip::<BlueprintDraftDocument>(draft_json());
    assert_eq!(draft.draft_index, 1);
    assert_eq!(draft.current_revision, 0);
    assert_eq!(draft.status, BlueprintDraftStatus::Queued);

    let packet = round_trip::<BlueprintPacketDocument>(packet_json());
    assert_eq!(packet.blueprint_id, "blueprint-001");
    packet.ensure_matches_draft(&draft).unwrap();

    let critique = round_trip::<BlueprintCritiqueDocument>(json!({
        "schema_version": "1.0",
        "kind": "blueprint_critique",
        "critique_id": "critique-001",
        "evaluation_id": "evaluation-001",
        "blueprint_id": "blueprint-001",
        "draft_id": "draft-001",
        "manifest_id": "manifest-001",
        "root_spec_id": "spec-root",
        "root_idea_id": "idea-root",
        "revision": 1,
        "required_changes": ["Tighten verification."],
        "blocking_reason": "Verification is too broad.",
        "created_at": NOW,
        "created_by": "evaluator_blueprint"
    }));
    critique.ensure_matches_packet(&packet).unwrap();

    let rejected = round_trip::<BlueprintEvaluationDocument>(json!({
        "schema_version": "1.0",
        "kind": "blueprint_evaluation",
        "evaluation_id": "evaluation-001",
        "blueprint_id": "blueprint-001",
        "draft_id": "draft-001",
        "manifest_id": "manifest-001",
        "root_spec_id": "spec-root",
        "root_idea_id": "idea-root",
        "decision": "rejected",
        "rubric_findings": ["Needs revision."],
        "critique_id": "critique-001",
        "created_at": NOW,
        "created_by": "evaluator_blueprint"
    }));
    assert_eq!(rejected.decision, BlueprintEvaluationDecision::Rejected);
    rejected.ensure_matches_packet(&packet).unwrap();

    let approved = round_trip::<BlueprintEvaluationDocument>(approved_evaluation_json());
    assert_eq!(approved.decision, BlueprintEvaluationDecision::Approved);

    let promotion = round_trip::<BlueprintPromotionRecord>(json!({
        "schema_version": "1.0",
        "kind": "blueprint_promotion",
        "promotion_id": "promotion-001",
        "blueprint_id": "blueprint-001",
        "evaluation_id": "evaluation-002",
        "draft_id": "draft-001",
        "manifest_id": "manifest-001",
        "root_spec_id": "spec-root",
        "root_idea_id": "idea-root",
        "generated_task_id": "task-001",
        "generated_task_path": "millrace-agents/tasks/queue/task-001.md",
        "approved_blueprint_path": "millrace-agents/blueprints/packets/approved/blueprint-001.json",
        "evaluation_path": "millrace-agents/blueprints/evaluations/evaluation-002.json",
        "promoted_at": NOW,
        "promoted_by": "runtime"
    }));
    promotion.ensure_matches_evaluation(&approved).unwrap();
}

#[test]
fn blueprint_repair_decision_round_trips_repaired_generated_task_context() {
    let packet = round_trip::<BlueprintPacketDocument>(packet_json());
    let evaluation = round_trip::<BlueprintEvaluationDocument>(approved_evaluation_json());
    let repair = round_trip::<BlueprintRepairDecisionDocument>(repair_decision_json());
    assert_eq!(
        repair.repair_action,
        BlueprintRepairAction::ApplyRepairedGeneratedTask
    );
    assert_eq!(
        repair.mutation_phase,
        BlueprintRepairMutationPhase::PreMutation
    );

    repair.ensure_matches_packet(&packet).unwrap();
    repair.ensure_matches_evaluation(&evaluation).unwrap();
    repair
        .ensure_matches_repaired_generated_task(&repaired_task())
        .unwrap();

    let manager_rerun = round_trip::<BlueprintRepairDecisionDocument>(json!({
        "schema_version": "1.0",
        "kind": "blueprint_repair_decision",
        "repair_id": "repair-manager-001",
        "failed_handler_id": "manager_blueprint_manifest_to_blueprint_drafts",
        "failed_run_id": "run-manager-001",
        "failed_stage_kind_id": "manager_blueprint",
        "failed_node_id": "manager_blueprint",
        "failed_terminal_result": "MANAGER_BLUEPRINT_COMPLETE",
        "failure_class": "blueprint_manifest_missing",
        "mutation_phase": "pre_mutation",
        "work_item_family_id": "spec",
        "work_item_id": "spec-root",
        "draft_id": "draft-001",
        "manifest_id": "manifest-001",
        "root_spec_id": "spec-root",
        "root_idea_id": "idea-root",
        "repair_action": "request_manager_rerun",
        "target_blueprint_id": "blueprint-001",
        "target_revision": 1,
        "next_resume_stage": "manager_blueprint",
        "reason": "Manager output should be regenerated by Manager Blueprint.",
        "verified_invariants": ["No runtime-owned mutation has occurred."],
        "created_at": NOW,
        "created_by": "mechanic_blueprint"
    }));
    assert_eq!(
        manager_rerun.repair_action,
        BlueprintRepairAction::RequestManagerRerun
    );

    let operator_block = round_trip::<BlueprintRepairDecisionDocument>(json!({
        "schema_version": "1.0",
        "kind": "blueprint_repair_decision",
        "repair_id": "repair-block-001",
        "failed_handler_id": "contractor_blueprint_candidate_persist",
        "failed_run_id": "run-contractor-001",
        "failed_stage_kind_id": "contractor_blueprint",
        "failed_node_id": "contractor_blueprint",
        "failed_terminal_result": "BLUEPRINT_CANDIDATE_READY",
        "failure_class": "blueprint_candidate_markdown_conflict",
        "mutation_phase": "unknown",
        "work_item_family_id": "blueprint_draft",
        "work_item_id": "draft-001",
        "draft_id": "draft-001",
        "manifest_id": "manifest-001",
        "root_spec_id": "spec-root",
        "root_idea_id": "idea-root",
        "repair_action": "block_for_operator",
        "target_blueprint_id": "blueprint-001",
        "target_revision": 1,
        "operator_reason": "Existing candidate conflicts with repair evidence.",
        "reason": "Conflict is not safe to repair locally.",
        "verified_invariants": ["Original failure evidence remains intact."],
        "created_at": NOW,
        "created_by": "mechanic_blueprint"
    }));
    assert_eq!(
        operator_block.repair_action,
        BlueprintRepairAction::BlockForOperator
    );
}

#[test]
fn blueprint_document_invariants_fail_before_runtime_consumers() {
    let mut bad_manifest = manifest_json();
    bad_manifest["draft_count"] = json!(2);
    let error = BlueprintManifestDocument::from_json_value(bad_manifest).unwrap_err();
    assert!(error.to_string().contains("draft_count"));

    let mut bad_draft = draft_json();
    bad_draft["dependency_draft_ids"] = json!(["draft-001"]);
    let error = BlueprintDraftDocument::from_json_value(bad_draft).unwrap_err();
    assert!(error.to_string().contains("depends_on_draft_ids"));

    let mut bad_eval = json!({
        "schema_version": "1.0",
        "kind": "blueprint_evaluation",
        "evaluation_id": "evaluation-bad",
        "blueprint_id": "blueprint-001",
        "draft_id": "draft-001",
        "manifest_id": "manifest-001",
        "root_spec_id": "spec-root",
        "root_idea_id": "idea-root",
        "decision": "rejected",
        "rubric_findings": ["Needs revision."],
        "created_at": NOW,
        "created_by": "evaluator_blueprint"
    });
    let error = BlueprintEvaluationDocument::from_json_value(bad_eval.take()).unwrap_err();
    assert!(error.to_string().contains("critique_id"));

    let mut bad_repair = repair_decision_json();
    bad_repair["repaired_artifact_id"] = Value::Null;
    let error = BlueprintRepairDecisionDocument::from_json_value(bad_repair).unwrap_err();
    assert!(error.to_string().contains("repaired_artifact_id"));

    let mut bad_handler = repair_decision_json();
    bad_handler["failed_handler_id"] = json!("planner_disposition");
    let error = BlueprintRepairDecisionDocument::from_json_value(bad_handler).unwrap_err();
    assert!(error.to_string().contains("failed_handler_id"));
}
