#!/usr/bin/env python3
"""
ad_copy_validator.py — Validates ad copy against platform specs.

Checks: character counts, rejection triggers (ALL CAPS, excessive punctuation,
trademarked terms), and scores each ad 0-100.

Usage:
    python3 ad_copy_validator.py                  # runs embedded sample
    python3 ad_copy_validator.py ads.json         # validates a JSON file
    echo '{"platform":"google_rsa","headlines":["My headline"]}' | python3 ad_copy_validator.py

JSON input format:
    {
      "platform": "google_rsa" | "meta_feed" | "linkedin" | "twitter" | "tiktok",
      "headlines": ["...", ...],
      "descriptions": ["...", ...],   # for google
      "primary_text": "...",          # for meta, linkedin, twitter, tiktok
      "headline": "...",              # for meta headline field
      "intro_text": "..."             # for linkedin
    }
"""

import json
import re
import sys
from collections import defaultdict

# ---------------------------------------------------------------------------
# Platform specifications
# ---------------------------------------------------------------------------
PLATFORM_SPECS = {
    "google_rsa": {
        "name": "Google RSA",
        "headline_max": 30,
        "headline_count_max": 15,
        "headline_count_min": 3,
        "description_max": 90,
        "description_count_max": 4,
        "description_count_min": 2,
    },
    "google_display": {
        "name": "Google Display",
        "headline_max": 30,
        "description_max": 90,
    },
    "meta_feed": {
        "name": "Meta (Facebook/Instagram) Feed",
        "primary_text_max": 125,   # preview limit; 2200 absolute max
        "headline_max": 40,
        "description_max": 30,
    },
    "linkedin": {
        "name": "LinkedIn Sponsored Content",
        "intro_text_max": 150,     # preview limit; 600 absolute max
        "headline_max": 70,
        "description_max": 100,
    },
    "twitter": {
        "name": "Twitter/X Promoted",
        "primary_text_max": 257,   # 280 - 23 chars for URL
    },
    "tiktok": {
        "name": "TikTok In-Feed",
        "primary_text_max": 100,
    },
}

# ---------------------------------------------------------------------------
# Rejection triggers
# ---------------------------------------------------------------------------
TRADEMARKED_TERMS = [
    "facebook", "instagram", "google", "youtube", "tiktok", "twitter",
    "linkedin", "snapchat", "whatsapp", "amazon", "apple", "microsoft",
]

PROHIBITED_PHRASES = [
    "click here",
    "limited time offer",  # allowed if real — flagged for review
    "guaranteed",
    "100% free",
    "act now",
    "best in class",
    "world's best",
    "#1 rated",
    "number one",
]

# Financial / health claim patterns
SUSPICIOUS_PATTERNS = [
    r"\$\d{3,}[k+]?\s*per\s*(day|week|month)",   # "make $1,000 per day"
    r"\d{3,}%\s*(return|roi|profit|gain)",         # "300% return"
    r"(cure|treat|heal|eliminate)\s+\w+",          # health claims
    r"lose\s+\d+\s*(pound|lb|kg)",                 # weight loss claims
]

# ---------------------------------------------------------------------------
# Validation logic
# ---------------------------------------------------------------------------

def count_chars(text):
    return len(text.strip())


def check_all_caps(text):
    """Returns True if more than 30% of alpha chars are uppercase — not counting acronyms."""
    words = text.split()
    violations = []
    for word in words:
        alpha = re.sub(r'[^a-zA-Z]', '', word)
        if len(alpha) > 3 and alpha.isupper():
            violations.append(word)
    return violations


def check_excessive_punctuation(text):
    """Flags repeated punctuation (!!!, ???, ...)."""
    return re.findall(r'[!?\.]{2,}', text)


def check_trademark_mentions(text):
    lowered = text.lower()
    return [term for term in TRADEMARKED_TERMS if re.search(r'\b' + term + r'\b', lowered)]


def check_prohibited_phrases(text):
    lowered = text.lower()
    return [phrase for phrase in PROHIBITED_PHRASES if phrase in lowered]


def check_suspicious_claims(text):
    hits = []
    for pattern in SUSPICIOUS_PATTERNS:
        if re.search(pattern, text, re.IGNORECASE):
            hits.append(pattern)
    return hits


def score_ad(issues):
    """
    Score 0-100. Start at 100, deduct per issue category.
    """
    score = 100
    deductions = {
        "char_over_limit": 20,
        "all_caps": 15,
        "excessive_punctuation": 10,
        "trademark_mention": 25,
        "prohibited_phrase": 15,
        "suspicious_claim": 30,
        "count_too_few": 10,
        "count_too_many": 5,
    }
    for category, items in issues.items():
        if items:
            score -= deductions.get(category, 5) * (1 if isinstance(items, bool) else min(len(items), 3))
    return max(0, score)


def validate_google_rsa(ad):
    spec = PLATFORM_SPECS["google_rsa"]
    issues = defaultdict(list)
    report = []

    headlines = ad.get("headlines", [])
    descriptions = ad.get("descriptions", [])

    # Count checks
    if len(headlines) < spec["headline_count_min"]:
        issues["count_too_few"].append(f"Need ≥{spec['headline_count_min']} headlines, got {len(headlines)}")
    if len(headlines) > spec["headline_count_max"]:
        issues["count_too_many"].append(f"Max {spec['headline_count_max']} headlines, got {len(headlines)}")
    if len(descriptions) < spec["description_count_min"]:
        issues["count_too_few"].append(f"Need ≥{spec['description_count_min']} descriptions, got {len(descriptions)}")

    # Character checks per headline
    for i, h in enumerate(headlines):
        length = count_chars(h)
        status = "✅" if length <= spec["headline_max"] else "❌"
        if length > spec["headline_max"]:
            issues["char_over_limit"].append(f"Headline {i+1}: {length} chars (max {spec['headline_max']})")
        report.append(f"  Headline {i+1}: {status} '{h}' ({length}/{spec['headline_max']} chars)")

        # Rejection trigger checks on each headline
        caps = check_all_caps(h)
        if caps:
            issues["all_caps"].extend(caps)
        punct = check_excessive_punctuation(h)
        if punct:
            issues["excessive_punctuation"].extend(punct)
        trademarks = check_trademark_mentions(h)
        if trademarks:
            issues["trademark_mention"].extend(trademarks)
        prohibited = check_prohibited_phrases(h)
        if prohibited:
            issues["prohibited_phrase"].extend(prohibited)

    for i, d in enumerate(descriptions):
        length = count_chars(d)
        status = "✅" if length <= spec["description_max"] else "❌"
        if length > spec["description_max"]:
            issues["char_over_limit"].append(f"Description {i+1}: {length} chars (max {spec['description_max']})")
        report.append(f"  Description {i+1}: {status} '{d}' ({length}/{spec['description_max']} chars)")

        suspicious = check_suspicious_claims(d)
        if suspicious:
            issues["suspicious_claim"].extend(suspicious)

    return report, dict(issues)


def validate_meta_feed(ad):
    spec = PLATFORM_SPECS["meta_feed"]
    issues = defaultdict(list)
    report = []

    primary = ad.get("primary_text", "")
    headline = ad.get("headline", "")

    if primary:
        length = count_chars(primary)
        status = "✅" if length <= spec["primary_text_max"] else "⚠️ (preview truncated)"
        report.append(f"  Primary text: {status} ({length}/{spec['primary_text_max']} preview chars)")
        if length > spec["primary_text_max"]:
            issues["char_over_limit"].append(f"Primary text {length} chars exceeds {spec['primary_text_max']}-char preview")

        for check_fn, key in [
            (check_all_caps, "all_caps"),
            (check_excessive_punctuation, "excessive_punctuation"),
            (check_trademark_mentions, "trademark_mention"),
            (check_prohibited_phrases, "prohibited_phrase"),
            (check_suspicious_claims, "suspicious_claim"),
        ]:
            result = check_fn(primary)
            if result:
                issues[key].extend(result if isinstance(result, list) else [str(result)])

    if headline:
        length = count_chars(headline)
        status = "✅" if length <= spec["headline_max"] else "❌"
        if length > spec["headline_max"]:
            issues["char_over_limit"].append(f"Headline {length} chars (max {spec['headline_max']})")
        report.append(f"  Headline: {status} '{headline}' ({length}/{spec['headline_max']} chars)")

    return report, dict(issues)


def validate_linkedin(ad):
    spec = PLATFORM_SPECS["linkedin"]
    issues = defaultdict(list)
    report = []

    intro = ad.get("intro_text", ad.get("primary_text", ""))
    headline = ad.get("headline", "")

    if intro:
        length = count_chars(intro)
        status = "✅" if length <= spec["intro_text_max"] else "⚠️ (preview truncated)"
        report.append(f"  Intro text: {status} ({length}/{spec['intro_text_max']} preview chars)")
        if length > spec["intro_text_max"]:
            issues["char_over_limit"].append(f"Intro text {length} chars exceeds {spec['intro_text_max']}-char preview")

        for check_fn, key in [
            (check_all_caps, "all_caps"),
            (check_excessive_punctuation, "excessive_punctuation"),
            (check_trademark_mentions, "trademark_mention"),
        ]:
            result = check_fn(intro)
            if result:
                issues[key].extend(result if isinstance(result, list) else [str(result)])

    if headline:
        length = count_chars(headline)
        status = "✅" if length <= spec["headline_max"] else "❌"
        if length > spec["headline_max"]:
            issues["char_over_limit"].append(f"Headline {length} chars (max {spec['headline_max']})")
        report.append(f"  Headline: {status} '{headline}' ({length}/{spec['headline_max']} chars)")

    return report, dict(issues)


def validate_generic(ad, platform_key):
    spec = PLATFORM_SPECS.get(platform_key, {})
    issues = defaultdict(list)
    report = []

    text = ad.get("primary_text", ad.get("text", ""))
    max_chars = spec.get("primary_text_max", 280)

    if text:
        length = count_chars(text)
        status = "✅" if length <= max_chars else "❌"
        if length > max_chars:
            issues["char_over_limit"].append(f"Text {length} chars (max {max_chars})")
        report.append(f"  Text: {status} ({length}/{max_chars} chars)")

        for check_fn, key in [
            (check_all_caps, "all_caps"),
            (check_excessive_punctuation, "excessive_punctuation"),
            (check_trademark_mentions, "trademark_mention"),
            (check_prohibited_phrases, "prohibited_phrase"),
        ]:
            result = check_fn(text)
            if result:
                issues[key].extend(result if isinstance(result, list) else [str(result)])

    return report, dict(issues)


def validate_ad(ad):
    platform = ad.get("platform", "").lower()

    if platform == "google_rsa":
        return validate_google_rsa(ad)
    elif platform == "meta_feed":
        return validate_meta_feed(ad)
    elif platform == "linkedin":
        return validate_linkedin(ad)
    elif platform in ("twitter", "tiktok"):
        return validate_generic(ad, platform)
    else:
        return [f"  ⚠️  Unknown platform '{platform}' — using generic validation"], {}


# ---------------------------------------------------------------------------
# Reporting
# ---------------------------------------------------------------------------

def format_report(ad, char_lines, issues):
    platform = ad.get("platform", "unknown")
    spec = PLATFORM_SPECS.get(platform, {})
    platform_name = spec.get("name", platform.upper())

    score = score_ad(issues)
    grade = "🟢 Excellent" if score >= 85 else "🟡 Needs Work" if score >= 60 else "🔴 High Risk"

    lines = []
    lines.append(f"\n{'='*60}")
    lines.append(f"Platform: {platform_name}")
    lines.append(f"Quality Score: {score}/100  {grade}")
    lines.append(f"{'='*60}")

    lines.append("\nCharacter Counts:")
    lines.extend(char_lines)

    if issues:
        lines.append("\nIssues Found:")
        category_labels = {
            "char_over_limit": "❌ Over character limit",
            "all_caps": "⚠️  ALL CAPS words",
            "excessive_punctuation": "⚠️  Excessive punctuation",
            "trademark_mention": "🚫 Trademarked term",
            "prohibited_phrase": "🚫 Prohibited phrase",
            "suspicious_claim": "🚨 Suspicious claim (review required)",
            "count_too_few": "⚠️  Too few elements",
            "count_too_many": "⚠️  Too many elements",
        }
        for category, items in issues.items():
            label = category_labels.get(category, category)
            lines.append(f"  {label}: {', '.join(str(i) for i in items)}")
    else:
        lines.append("\n✅ No rejection triggers found.")

    lines.append("")
    return "\n".join(lines)


# ---------------------------------------------------------------------------
# Sample data (embedded — runs with zero config)
# ---------------------------------------------------------------------------

SAMPLE_ADS = [
    {
        "platform": "google_rsa",
        "headlines": [
            "Cut Reporting Time by 80%",                   # 26 chars ✅
            "Automated Reports, Zero Effort",              # 31 chars ❌ over limit
            "Your Data. Your Way. Every Week.",            # 33 chars ❌ over limit
            "Save 8 Hours Per Week on Reports",            # 32 chars ❌ over limit
            "Try Free for 14 Days",                        # 21 chars ✅
            "No Code. No Complexity. Just Results.",        # 38 chars ❌
            "5,000 Teams Use This",                        # 21 chars ✅
            "Replace Your Weekly Standup Deck",            # 32 chars ❌
            "Connect Your Tools in 15 Minutes",            # 32 chars ❌
            "Instant Dashboards for Your Team",            # 32 chars ❌
            "Start Free — No Credit Card",                 # 28 chars ✅
            "Built for Growth Teams",                      # 22 chars ✅
            "See Your KPIs at a Glance",                   # 25 chars ✅
            "Data-Driven Decisions, Made Easy",            # 32 chars ❌
            "GUARANTEED Results — Try Now!!!",             # 31 chars ❌ + ALL CAPS + excessive punct
        ],
        "descriptions": [
            "Connect your tools, set your KPIs, and let the platform handle the weekly reporting. Free 14-day trial.",  # 103 chars ❌
            "Stop wasting Monday mornings on spreadsheets. Automated reports your whole team actually reads.",           # 94 chars ❌
        ],
    },
    {
        "platform": "meta_feed",
        "primary_text": "Your team is shipping features, but nobody can see the impact. [Product] connects your tools and shows you exactly what's working — in one dashboard, updated automatically. Start free today.",
        "headline": "See Your Impact, Automatically",
    },
    {
        "platform": "linkedin",
        "intro_text": "Growth teams at 3,200+ companies use [Product] to replace their manual weekly reports with automated dashboards.",
        "headline": "Automated Reporting for Growth Teams",
    },
    {
        "platform": "twitter",
        "primary_text": "Stop spending 8 hours on a report nobody reads. [Product] automates it — connect your tools, set your KPIs, and it runs itself. Free trial → [link]",
    },
]


# ---------------------------------------------------------------------------
# Main
# ---------------------------------------------------------------------------

def main():
    import argparse

    parser = argparse.ArgumentParser(
        description="Validates ad copy against platform specs. "
                    "Checks character counts, rejection triggers, and scores each ad 0-100."
    )
    parser.add_argument(
        "file", nargs="?", default=None,
        help="Path to a JSON file containing ad data. "
             "If omitted, reads from stdin or runs embedded sample."
    )
    args = parser.parse_args()

    # Load from file or stdin, else use sample
    ads = None

    if args.file:
        try:
            with open(args.file) as f:
                data = json.load(f)
                ads = data if isinstance(data, list) else [data]
        except Exception as e:
            print(f"Error reading file: {e}", file=sys.stderr)
            sys.exit(1)
    elif not sys.stdin.isatty():
        raw = sys.stdin.read().strip()
        if raw:
            try:
                data = json.loads(raw)
                ads = data if isinstance(data, list) else [data]
            except Exception as e:
                print(f"Error reading stdin: {e}", file=sys.stderr)
                sys.exit(1)
        else:
            print("No input provided — running embedded sample ads.\n")
            ads = SAMPLE_ADS
    else:
        print("No input provided — running embedded sample ads.\n")
        ads = SAMPLE_ADS

    # Aggregate results for JSON output
    results = []
    all_output = []

    for ad in ads:
        char_lines, issues = validate_ad(ad)
        score = score_ad(issues)
        report_text = format_report(ad, char_lines, issues)
        all_output.append(report_text)
        results.append({
            "platform": ad.get("platform"),
            "score": score,
            "issues": {k: v for k, v in issues.items()},
            "passed": score >= 70,
        })

    # Human-readable output
    for block in all_output:
        print(block)

    # Summary
    avg_score = sum(r["score"] for r in results) / len(results) if results else 0
    passed = sum(1 for r in results if r["passed"])
    print(f"\nSUMMARY: {passed}/{len(results)} ads passed (avg score: {avg_score:.0f}/100)")

    # JSON output to stdout (for programmatic use) — write to separate section
    print("\n--- JSON Output ---")
    print(json.dumps(results, indent=2))


if __name__ == "__main__":
    main()
