"""Strategy scoring for beginner-friendly Scam Self-Defense candidates."""

from __future__ import annotations

from dataclasses import dataclass

from autoshorts.ideas.candidate import ValidationResult, VideoCandidate
from autoshorts.strategy.series import FORBIDDEN_PUBLIC_TOPIC_PATTERNS

MIN_SIGNAL_SCORE = 7
MIN_AVERAGE_SCORE = 7.5
GENERIC_AI_SLOP_PHRASES = (
    "in this video",
    "unlock your potential",
    "unlock your full potential",
    "best ai tools",
    "ai tools you need",
    "follow for more ai tips",
    "everyone should use",
    "prompt engineering",
)
JARGON_PHRASES = (
    "credential harvesting",
    "social engineering",
    "validate the authenticity",
    "transactional flow",
    "attack vector",
    "threat actor",
    "cognitive load",
)
VISIBLE_SCREEN_TERMS = (
    "screen",
    "sms",
    "text",
    "email",
    "invoice",
    "chat",
    "popup",
    "login",
    "qr",
    "app",
    "message",
    "page",
)
RED_FLAG_TERMS = (
    "red flag",
    "trap",
    "scam",
    "fake",
    "suspicious",
    "pressure",
    "rush",
    "urgent",
    "changed",
    "link",
)
SAFE_MOVE_TERMS = (
    "open the app yourself",
    "check it another way",
    "verify",
    "close",
    "do not tap",
    "don't tap",
    "slow down",
    "deny",
    "inside the platform",
)
SERIES_NAMES = (
    "One Screen. One Red Flag.",
    "Send This To Your Parents",
    "Before You Click",
    "Money Move Red Flags",
    "Account Trap",
)


@dataclass(frozen=True)
class StrategySignal:
    score: int
    rationale: str


@dataclass(frozen=True)
class ContentStrategyScore:
    hook: StrategySignal
    curiosity: StrategySignal
    identity: StrategySignal
    emotion: StrategySignal
    trust: StrategySignal
    follow_reason: StrategySignal
    originality: StrategySignal
    variety: StrategySignal
    target_emotion: str
    viewer_identity: str
    future_value_promise: str
    format_family: str
    variation_axis: str

    @property
    def average_score(self) -> float:
        signals = (
            self.hook,
            self.curiosity,
            self.identity,
            self.emotion,
            self.trust,
            self.follow_reason,
            self.originality,
            self.variety,
        )
        return sum(signal.score for signal in signals) / len(signals)


@dataclass(frozen=True)
class CandidateStrategyReview:
    candidate_id: str
    strategy_score: ContentStrategyScore
    render_allowed: bool
    reject_reasons: tuple[str, ...]
    improvement_suggestions: tuple[str, ...]


def _is_blank(value: str) -> bool:
    return not (value or "").strip()


def _visible_candidate_text(candidate: VideoCandidate) -> str:
    return " ".join(
        (
            candidate.title,
            candidate.hook,
            candidate.script_outline,
            candidate.visual_concept,
            candidate.caption,
            candidate.cta,
        )
    ).casefold()


def _combined_candidate_text(candidate: VideoCandidate) -> str:
    return " ".join(
        (
            _visible_candidate_text(candidate),
            candidate.hypothesis,
            " ".join(candidate.policy_notes),
        )
    ).casefold()


def _has_any(text: str, terms: tuple[str, ...]) -> bool:
    return any(term in text for term in terms)


def _has_generic_ai_slop(text: str) -> bool:
    return any(phrase in text for phrase in GENERIC_AI_SLOP_PHRASES)


def _has_jargon(text: str) -> bool:
    return any(phrase in text for phrase in JARGON_PHRASES)


def _infer_format_family(candidate: VideoCandidate) -> str:
    text = _combined_candidate_text(candidate)
    if "marketplace" in text:
        return "Money Move Red Flags"
    if "invoice" in text or "payment" in text or "bank" in text:
        return "Money Move Red Flags"
    if "login" in text or "password" in text or "account" in text or "mfa" in text:
        return "Account Trap"
    if "sms" in text or "delivery" in text or "text" in text or "link" in text or "qr" in text:
        return "Before You Click"
    if "parents" in text or "mom" in text or "family" in text:
        return "Send This To Your Parents"
    for series_name in SERIES_NAMES:
        if series_name.casefold() in text:
            return series_name
    return "One Screen. One Red Flag."


def _score_signal(score: int, rationale: str) -> StrategySignal:
    return StrategySignal(score=max(0, min(10, score)), rationale=rationale)


def _forbidden_public_topic_matches(text: str) -> tuple[str, ...]:
    matches: list[str] = []
    for canonical_topic, patterns in FORBIDDEN_PUBLIC_TOPIC_PATTERNS.items():
        if any(pattern in text for pattern in patterns):
            matches.append(canonical_topic)
    return tuple(matches)


def validate_content_strategy_score(score: ContentStrategyScore) -> ValidationResult:
    errors: list[str] = []
    signals = {
        "hook": score.hook,
        "curiosity": score.curiosity,
        "identity": score.identity,
        "emotion": score.emotion,
        "trust": score.trust,
        "follow_reason": score.follow_reason,
        "originality": score.originality,
        "variety": score.variety,
    }
    for name, signal in signals.items():
        if not 0 <= signal.score <= 10:
            errors.append(f"{name} score must be between 0 and 10")
        if _is_blank(signal.rationale):
            errors.append(f"{name} rationale is required")

    for field_name, value in {
        "target_emotion": score.target_emotion,
        "viewer_identity": score.viewer_identity,
        "future_value_promise": score.future_value_promise,
        "format_family": score.format_family,
        "variation_axis": score.variation_axis,
    }.items():
        if _is_blank(value):
            errors.append(f"{field_name} is required")

    if score.hook.score < MIN_SIGNAL_SCORE:
        errors.append("hook must be at least 7")
    if score.curiosity.score < MIN_SIGNAL_SCORE:
        errors.append("curiosity must be at least 7")
    if score.identity.score < MIN_SIGNAL_SCORE:
        errors.append("identity must be at least 7")
    if score.follow_reason.score < MIN_SIGNAL_SCORE:
        errors.append("follow_reason must be at least 7")
    if score.originality.score < MIN_SIGNAL_SCORE:
        errors.append("originality must be at least 7")
    if score.average_score < MIN_AVERAGE_SCORE:
        errors.append("average strategy score must be at least 7.5")

    return ValidationResult(is_valid=not errors, errors=tuple(errors))


def should_advance_strategy(score: ContentStrategyScore) -> bool:
    return validate_content_strategy_score(score).is_valid


def build_candidate_strategy_review(
    candidate: VideoCandidate,
    *,
    recent_format_families: tuple[str, ...] = (),
) -> CandidateStrategyReview:
    text = _combined_candidate_text(candidate)
    visible_text = _visible_candidate_text(candidate)
    format_family = _infer_format_family(candidate)
    generic_slop = _has_generic_ai_slop(visible_text)
    jargon = _has_jargon(visible_text)
    visible_screen = _has_any(visible_text, VISIBLE_SCREEN_TERMS)
    red_flag = _has_any(visible_text, RED_FLAG_TERMS)
    safe_move = _has_any(visible_text, SAFE_MOVE_TERMS)
    screenshot_value = "save" in text or "screenshot" in text or "quick check" in text or "checklist" in text
    family_share = any(term in text for term in ("family", "parents", "partner", "colleague", "team", "send this"))
    forbidden_public_topics = _forbidden_public_topic_matches(visible_text)
    repeated_recently = bool(recent_format_families) and recent_format_families.count(format_family) >= 2
    pretty_empty = not visible_screen or not red_flag

    strategy_score = ContentStrategyScore(
        hook=_score_signal(
            9 if red_flag and not generic_slop and not jargon else 5,
            "Hook names a visible everyday scam/red flag in plain language."
            if red_flag and not generic_slop and not jargon
            else "Hook is too generic, too AI-ish, or too technical.",
        ),
        curiosity=_score_signal(
            9 if red_flag and ("before" in text or "if" in text or "designed" in text or "stop" in text) else 6,
            "Viewer gets a concrete open loop before clicking/paying/logging in.",
        ),
        identity=_score_signal(
            9 if visible_screen else 5,
            "Targets normal users through a familiar screen."
            if visible_screen
            else "Audience/problem is too abstract.",
        ),
        emotion=_score_signal(
            8 if red_flag else 5,
            "Creates useful concern without fearmongering.",
        ),
        trust=_score_signal(
            9 if safe_move else 6,
            "Trust comes from one practical safer move."
            if safe_move
            else "Needs a clearer safe next action.",
        ),
        follow_reason=_score_signal(
            9 if family_share or screenshot_value else 7,
            "Viewer can save/share this as a simple digital self-defense rule."
            if family_share or screenshot_value
            else "Follow reason exists but could be more share/save oriented.",
        ),
        originality=_score_signal(
            8 if not generic_slop and not pretty_empty else 4,
            "Specific screen + red flag avoids pretty-empty AI content."
            if not generic_slop and not pretty_empty
            else "Pretty-empty visual or generic topic risk.",
        ),
        variety=_score_signal(
            5 if repeated_recently else 8,
            "Recent format repetition risk." if repeated_recently else "Format is fresh enough against recent context.",
        ),
        target_emotion="warned but in control" if red_flag else "mild interest",
        viewer_identity="normal smartphone/internet users" if visible_screen else "generic viewers",
        future_value_promise="one red flag before you click, pay, scan, or log in" if safe_move else "general tips",
        format_family=format_family,
        variation_axis="screen + red flag + safer move" if not pretty_empty else "pretty-empty visual risk",
    )

    validation = validate_content_strategy_score(strategy_score)
    reject_reasons = list(validation.errors)
    if generic_slop:
        reject_reasons.append("generic AI-slop language detected")
    if jargon:
        reject_reasons.append("language is too technical for normal users")
    if pretty_empty:
        reject_reasons.append("pretty-empty visual blocker: first screen/red flag is not clear")
    if not safe_move:
        reject_reasons.append("safe move clarity is missing")
    if forbidden_public_topics:
        reject_reasons.extend(f"candidate uses forbidden public topic: {topic}" for topic in forbidden_public_topics)
    if repeated_recently:
        reject_reasons.append("recent format repetition risk")

    suggestions: list[str] = []
    if not visible_screen:
        suggestions.append("Start with a familiar phone/email/invoice/chat/login/popup screen.")
    if not red_flag:
        suggestions.append("Name one concrete red flag visible in the screen.")
    if not safe_move:
        suggestions.append("Add one plain safer move: open the app yourself, check another way, or keep it inside the platform.")
    if jargon:
        suggestions.append("Replace security jargon with everyday words.")
    if not screenshot_value:
        suggestions.append("Add a max-three-point SAVE THIS or Quick check card.")

    return CandidateStrategyReview(
        candidate_id=candidate.candidate_id,
        strategy_score=strategy_score,
        render_allowed=not reject_reasons,
        reject_reasons=tuple(dict.fromkeys(reject_reasons)),
        improvement_suggestions=tuple(suggestions),
    )
