#!/usr/bin/env python3
"""
Visual QA Pipeline — Section-by-Section Audit
Screenshots each section of a page, analyzes with vision model,
reports issues against a strict criteria checklist.
"""

import json
import os
import sys
import base64
import subprocess
from pathlib import Path

# Config
URL = sys.argv[1] if len(sys.argv) > 1 else "https://staging.curiousendeavor.com/etoro-competition/cashapp/"
OUT_DIR = Path(sys.argv[2]) if len(sys.argv) > 2 else Path("/tmp/visual-qa")
OUT_DIR.mkdir(parents=True, exist_ok=True)

CRITERIA = """
VISUAL QA CRITERIA — Score each issue as PASS, WARN, or FAIL:

1. IMAGE FIT: Does every image fill its container without awkward cropping?
   - No heads cut off at forehead or chin
   - No text/signage truncated at edges
   - Subject matter centered on the focal point
   - No excessive letterboxing or pillarboxing

2. IMAGE VISIBILITY: Is every image clearly visible?
   - No dark images on dark backgrounds
   - No images so small they're unreadable
   - No broken/missing images (gray boxes, placeholders)
   - Sufficient contrast between image and surrounding elements

3. ASPECT RATIO CONSISTENCY: Within any grid/row, are images consistently sized?
   - Same height across a row
   - No jarring size mismatches between grid cells
   - Portrait images not forced into landscape containers (or vice versa)

4. CONTENT RELEVANCE: Does the image match the section topic?
   - Brand identity section → logos, colors, type specimens
   - Campaign section → campaign stills, not random lifestyle
   - Audience section → people/demographics, not product UI
   - Each image earns its placement

5. SPACING & ALIGNMENT: Are gaps, margins, and borders consistent?
   - No uneven gutters between grid items
   - No orphaned elements (single item in a row meant for multiple)
   - Consistent padding within containers

6. TEXT READABILITY: Can text overlaid on images be read?
   - Hero text legible over hero image
   - Labels/captions not obscured
   - Campaign text in images readable at displayed size

7. VISUAL HIERARCHY: Do images support the page's reading flow?
   - Hero image is the largest
   - Supporting images smaller
   - No supporting image visually competing with hero

For each issue found, report:
- Section name
- Issue type (from 1-7 above)
- Severity (FAIL = broken/unusable, WARN = suboptimal, PASS = fine)
- Specific description (what's wrong, what image)
- Fix recommendation (specific CSS or image swap)
"""

def screenshot_sections(url):
    """Use Playwright to screenshot the full page and individual viewport sections."""
    # Step 1: Take full-page screenshot with Playwright (scroll first for lazy images)
    script = f"""
const {{ chromium }} = require('playwright');
(async () => {{
    const browser = await chromium.launch();
    const page = await browser.newPage({{ viewport: {{ width: 1200, height: 900 }} }});
    await page.goto('{url}', {{ waitUntil: 'domcontentloaded', timeout: 15000 }});
    await page.waitForTimeout(2000);
    
    // Scroll full page to trigger lazy-loading images
    await page.evaluate(async () => {{
        const delay = ms => new Promise(r => setTimeout(r, ms));
        const h = document.body.scrollHeight;
        for (let y = 0; y < h; y += 400) {{
            window.scrollTo(0, y);
            await delay(100);
        }}
        window.scrollTo(0, 0);
        await delay(500);
    }});
    await page.waitForTimeout(1000);
    
    await page.screenshot({{ path: '{OUT_DIR}/full-page.png', fullPage: true }});
    const totalHeight = await page.evaluate(() => document.body.scrollHeight);
    console.log(totalHeight);
    await browser.close();
}})();
"""
    result = subprocess.run(
        ["node", "-e", script],
        capture_output=True, text=True, timeout=45
    )
    if result.returncode != 0:
        print(f"Screenshot error: {result.stderr}", file=sys.stderr)
        return []
    
    total_height = int(result.stdout.strip())
    full_img = OUT_DIR / "full-page.png"
    if not full_img.exists():
        print("Full page screenshot not found", file=sys.stderr)
        return []
    
    # Step 2: Slice with ImageMagick
    section_height = 900
    sections = []
    y = 0
    i = 0
    while y < total_height:
        clip_h = min(section_height, total_height - y)
        if clip_h < 50:
            break
        out_file = f"section-{i:02d}.png"
        subprocess.run([
            "convert", str(full_img),
            "-crop", f"1200x{clip_h}+0+{y}",
            "+repage",
            str(OUT_DIR / out_file)
        ], capture_output=True)
        sections.append({
            "index": i,
            "file": out_file,
            "y_start": y,
            "y_end": y + clip_h,
            "height": clip_h
        })
        y += section_height
        i += 1
    
    return sections


def analyze_section(section_file, section_index, total_sections):
    """Send section screenshot to Gemini vision for analysis."""
    import google.generativeai as genai
    
    api_key = os.environ.get("GEMINI_API_KEY")
    if not api_key:
        # Try reading from bashrc
        import re
        bashrc = Path.home() / ".bashrc"
        if bashrc.exists():
            for line in bashrc.read_text().splitlines():
                m = re.match(r'export GEMINI_API_KEY=["\']?([^"\']+)', line)
                if m:
                    api_key = m.group(1)
                    break
    
    if not api_key:
        return {"error": "No GEMINI_API_KEY found"}
    
    genai.configure(api_key=api_key)
    model = genai.GenerativeModel("gemini-2.5-flash")
    
    img_path = OUT_DIR / section_file
    img_data = img_path.read_bytes()
    
    prompt = f"""You are a visual QA auditor for a brand analysis web page.

This is section {section_index + 1} of {total_sections} (viewport chunk from top to bottom).

{CRITERIA}

Analyze this screenshot. For EVERY image visible in this section:
1. Does it fit its container properly?
2. Is anything cropped awkwardly?
3. Is it visible and readable?
4. Does it match the section topic?

Be SPECIFIC. Reference exact images by their position (top-left, center, etc.) or visible content.
Only report actual issues — don't invent problems that aren't there.

Output as JSON array:
[
  {{
    "section": "visible section name or 'Hero' / 'Brand Identity' etc",
    "criterion": 1-7,
    "severity": "FAIL|WARN|PASS",
    "image_position": "top-left of grid / hero / right column etc",
    "description": "what's wrong specifically",
    "fix": "specific CSS change or image swap recommendation"
  }}
]

If no issues, return an empty array [].
Only return the JSON, no other text.
"""
    
    response = model.generate_content([
        prompt,
        {"mime_type": "image/png", "data": img_data}
    ])
    
    try:
        text = response.text.strip()
        # Strip markdown code fences if present
        if text.startswith("```"):
            text = text.split("\n", 1)[1]
            if text.endswith("```"):
                text = text.rsplit("```", 1)[0]
            text = text.strip()
        return json.loads(text)
    except (json.JSONDecodeError, Exception) as e:
        return [{"error": str(e), "raw": response.text[:500]}]


def main():
    print(f"🔍 Visual QA Pipeline — {URL}")
    print(f"📸 Capturing sections...")
    
    sections = screenshot_sections(URL)
    print(f"   {len(sections)} sections captured")
    
    all_issues = []
    for section in sections:
        print(f"   Analyzing section {section['index']}...")
        issues = analyze_section(section['file'], section['index'], len(sections))
        if isinstance(issues, list):
            for issue in issues:
                issue['viewport_section'] = section['index']
            all_issues.extend(issues)
        else:
            all_issues.append(issues)
    
    # Filter to actual issues (WARN or FAIL)
    real_issues = [i for i in all_issues if isinstance(i, dict) and i.get('severity') in ('WARN', 'FAIL')]
    
    # Summary
    print(f"\n{'='*60}")
    print(f"VISUAL QA REPORT — {URL}")
    print(f"{'='*60}")
    
    fails = [i for i in real_issues if i.get('severity') == 'FAIL']
    warns = [i for i in real_issues if i.get('severity') == 'WARN']
    
    print(f"\n❌ FAILS: {len(fails)}  ⚠️ WARNS: {len(warns)}")
    
    if fails:
        print(f"\n--- FAILS ---")
        for i, issue in enumerate(fails, 1):
            print(f"\n{i}. [{issue.get('section', '?')}] Criterion {issue.get('criterion', '?')}")
            print(f"   Image: {issue.get('image_position', '?')}")
            print(f"   Issue: {issue.get('description', '?')}")
            print(f"   Fix: {issue.get('fix', '?')}")
    
    if warns:
        print(f"\n--- WARNINGS ---")
        for i, issue in enumerate(warns, 1):
            print(f"\n{i}. [{issue.get('section', '?')}] Criterion {issue.get('criterion', '?')}")
            print(f"   Image: {issue.get('image_position', '?')}")
            print(f"   Issue: {issue.get('description', '?')}")
            print(f"   Fix: {issue.get('fix', '?')}")
    
    # Save full report
    report_path = OUT_DIR / "report.json"
    report_path.write_text(json.dumps({
        "url": URL,
        "sections_analyzed": len(sections),
        "total_issues": len(real_issues),
        "fails": len(fails),
        "warns": len(warns),
        "issues": all_issues
    }, indent=2))
    print(f"\nFull report: {report_path}")
    
    return len(fails)


if __name__ == "__main__":
    exit(main())
