import pytest

from autoshorts.strategy.spine_v2 import (
    AIDisclosureDecision,
    AudienceNeed,
    ClaimRisk,
    ContentBriefV2,
    ExperimentHypothesis,
    HumanTexture,
    ReviewPackageV2,
    SaturationRisk,
    SeriesRegistry,
    StrategyScoreV2,
    VideoCandidateV2,
    ViewerEmotion,
    build_demo_candidates_v2,
    build_review_package_v2,
    render_review_package_v2,
    validate_candidate_v2,
    validate_review_package_v2,
)


def make_hypothesis() -> ExperimentHypothesis:
    return ExperimentHypothesis(
        statement="Tests whether visible decision-target fixes drive saves.",
        expected_winning_metric="saves_per_view",
    )


def make_brief(**overrides) -> ContentBriefV2:
    data = dict(
        brief_id="decision-summary-brief",
        series_id="decision_design",
        topic="AI summaries without a decision target",
        audience_need=AudienceNeed.CLARITY,
        job_to_be_done="When I review a meeting, I want decision evidence, so I can choose the next action.",
        viewer_identity="office_worker",
        emotion_before=ViewerEmotion.CONFUSED,
        emotion_after=ViewerEmotion.IN_CONTROL,
        audience_problem="AI summaries hide the one decision that matters.",
        payoff="Add a decision target before asking for the summary.",
        search_intent_phrase="better meeting summary prompt",
        saturation_risk=SaturationRisk.MEDIUM,
        human_texture=HumanTexture.MINI_CASE,
        claim_risk=ClaimRisk.LOW,
        ai_disclosure_required=AIDisclosureDecision.NO,
        experiment_hypothesis=make_hypothesis(),
        platforms=("youtube", "tiktok", "instagram"),
        target_duration_seconds=38,
    )
    data.update(overrides)
    return ContentBriefV2(**data)


def make_candidate(**overrides) -> VideoCandidateV2:
    data = dict(
        candidate_id="decision-design-summary-target-38s",
        version="v2.0.0",
        title="Your AI summary has no decision target",
        series_id="decision_design",
        platforms=("youtube", "tiktok", "instagram"),
        duration_seconds=38,
        audience_need=AudienceNeed.CLARITY,
        job_to_be_done="When I review a meeting, I want decision evidence, so I can choose the next action.",
        viewer_identity="office_worker",
        emotion_before=ViewerEmotion.CONFUSED,
        emotion_after=ViewerEmotion.IN_CONTROL,
        hook="Your AI summary is useless because it has no decision target.",
        first_frame="Split-screen: 47-minute transcript vs. useless AI summary stamped USELESS.",
        visible_fail="The AI summary says 'great discussion' but misses the launch decision.",
        mechanism="Without a decision target, AI compresses every sentence equally.",
        fix="Before summarizing, state the decision and ask for blockers, owners, risks, and next steps.",
        before_after="Before: generic summary. After: decision evidence with risks and owner.",
        takeaway="Don't ask for a summary. Ask for decision evidence.",
        save_reason="Viewer can copy the decision-target sentence into the next meeting summary prompt.",
        share_reason="Every office worker has seen useless meeting summaries.",
        follow_reason="Follow for small systems that stop digital work from wasting your life.",
        search_intent_phrase="better meeting summary prompt",
        saturation_risk=SaturationRisk.MEDIUM,
        human_texture=HumanTexture.MINI_CASE,
        claim_risk=ClaimRisk.LOW,
        claims=("AI summaries need a decision target to preserve relevance.",),
        ai_disclosure_required=AIDisclosureDecision.NO,
        experiment_hypothesis=make_hypothesis(),
        expected_winning_metric="saves_per_view",
        platform_fit={"youtube": "strong", "tiktok": "strong", "instagram": "medium"},
        risks=("medium saturation",),
        visual_structure="motion-led mechanism funnel with transcript cards, not slideshow/text cards",
        anti_example="A useless AI summary with no decision target.",
        human_texture_note="messy meeting transcript with launch decision",
    )
    data.update(overrides)
    return VideoCandidateV2(**data)


def test_content_brief_v2_requires_strategic_spine_fields():
    brief = make_brief(series_id="", save_reason="") if False else None
    with pytest.raises(TypeError):
        ContentBriefV2(brief_id="missing-required")

    invalid = make_candidate(save_reason="")
    result = validate_candidate_v2(invalid)

    assert not result.is_valid
    assert "save_reason is required" in result.errors


def test_quality_gates_block_missing_mechanism_and_first_frame_conflict():
    candidate = make_candidate(mechanism="", first_frame="Plain title card with text only")

    result = validate_candidate_v2(candidate)

    assert not result.is_valid
    assert "mechanism is required" in result.errors
    assert "visible first-frame conflict is required" in result.errors
    assert "candidate is not renderable until all hard gates pass" in result.errors


def test_anti_ai_slop_blocks_generic_hooks_and_slideshow_structure():
    candidate = make_candidate(
        title="Top 5 AI Tools That Will Change Your Life",
        hook="Nobody talks about this AI tool will change your life.",
        visual_structure="pure slideshow with text cards and generic AI background",
        human_texture=HumanTexture.NONE,
    )

    result = validate_candidate_v2(candidate)

    assert not result.is_valid
    assert "generic AI-slop hook/title detected" in result.errors
    assert "videos without concrete human texture are blocked" in result.errors
    assert "pure slideshow/text-card structure is blocked" in result.errors


def test_strategy_score_v2_uses_100_point_model_and_blocks_low_scores():
    candidate = make_candidate()
    score = StrategyScoreV2.from_candidate(candidate)

    assert 75 <= score.total <= 93
    assert score.render_allowed
    assert score.problem_urgency == 13
    assert score.practical_payoff == 13

    weak = StrategyScoreV2.from_candidate(make_candidate(save_reason="", share_reason="", follow_reason=""))
    assert weak.total < 80
    assert not weak.render_allowed
    assert "share/save/follow potential below threshold" in weak.reject_reasons


def test_series_registry_accepts_new_series_and_rejects_unknown():
    registry = SeriesRegistry.default()

    assert registry.validate_series_id("digital_red_flags").is_valid
    assert registry.get("attention_traps").name == "Attention Traps"
    assert not registry.validate_series_id("top_ai_tools").is_valid


def test_review_package_v2_contains_hashes_strategy_risk_and_approval_command():
    candidate = make_candidate(media_hash="abc123")

    package = build_review_package_v2(candidate)
    validation = validate_review_package_v2(package)
    text = render_review_package_v2(package)

    assert validation.is_valid
    assert isinstance(package, ReviewPackageV2)
    assert package.script_hash
    assert package.media_hash == "abc123"
    assert "Candidate ID: decision-design-summary-target-38s" in text
    assert "Series: Decision Design" in text
    assert "Visible Fail:" in text
    assert "AI Disclosure: no" in text
    assert "Experiment Hypothesis:" in text
    assert "APPROVE decision-design-summary-target-38s v2.0.0" in text


def test_approval_command_is_version_and_hash_bound():
    package = build_review_package_v2(make_candidate())

    command = package.approval_command

    assert command.startswith("APPROVE decision-design-summary-target-38s v2.0.0 ")
    assert package.script_hash in command


def test_build_demo_candidates_v2_returns_one_candidate_per_requested_series():
    candidates = build_demo_candidates_v2()

    assert [candidate.series_id for candidate in candidates] == [
        "ai_output_autopsy",
        "digital_red_flags",
        "workflow_teardown",
        "decision_design",
        "attention_traps",
    ]
    assert all(validate_candidate_v2(candidate).is_valid for candidate in candidates)
    assert all(StrategyScoreV2.from_candidate(candidate).render_allowed for candidate in candidates)
