"""Controlled image refresh for current Radar signals.

Order: existing explicit image_url, YouTube thumbnail, article OG/Twitter image,
then NULL. Only rows in the requested observed_at window are touched. A URL is
marked image_found=1 only after an HTTP image response is verified.
"""
from __future__ import annotations

import argparse
import concurrent.futures
import re
import sqlite3
import sys
import urllib.request
from datetime import datetime, timezone
from pathlib import Path

ROOT = Path('/opt/struktur/social-media-radar')
sys.path.insert(0, str(ROOT))
from intake.og_fetch import fetch_og_image  # noqa: E402

DB = '/var/lib/sma-data/signals.db'
HEADERS = {'User-Agent': 'Mozilla/5.0 (compatible; MallorcaRadar/1.0)'}
YT_RE = re.compile(r'[?&]v=([A-Za-z0-9_-]{11})')


def validate_image(url: str) -> tuple[bool, int | None, str, int]:
    if not url or not url.startswith(('http://', 'https://')):
        return False, None, '', 0
    try:
        req = urllib.request.Request(url, headers=HEADERS)
        with urllib.request.urlopen(req, timeout=10) as response:
            content_type = (response.headers.get('Content-Type') or '').split(';')[0].lower()
            head = response.read(64)
            ok = response.status == 200 and content_type.startswith('image/') and bool(head)
            return ok, response.status, content_type, len(head)
    except Exception:
        return False, None, '', 0


def youtube_thumbnail(source_url: str) -> str:
    match = YT_RE.search(source_url or '')
    return f'https://i.ytimg.com/vi/{match.group(1)}/hqdefault.jpg' if match else ''


def resolve_candidate(row: sqlite3.Row) -> tuple[str, str, str | None]:
    """Return image_url, source label, final article URL."""
    existing = (row['image_url'] or '').strip()
    if existing:
        return existing, (row['image_source'] or 'explicit_signal_image'), row['final_article_url']

    yt = youtube_thumbnail(row['source_url']) if row['source_type'] in ('youtube', 'watch_entity') else ''
    if yt:
        return yt, 'youtube_thumbnail', row['final_article_url']

    # Facebook search-index pages are not a reliable image source and are not fetched.
    if row['source_type'] == 'facebook_public':
        return '', '', row['final_article_url']

    source_url = row['final_article_url'] or row['source_url']
    if not source_url:
        return '', '', row['final_article_url']
    try:
        result = fetch_og_image(source_url)
        if result.get('image_url'):
            return result['image_url'], result.get('image_source') or 'og:image', result.get('final_article_url')
    except Exception:
        pass
    return '', '', row['final_article_url']


def refresh(days: int = 30, limit: int = 500) -> dict:
    now = datetime.now(timezone.utc).isoformat()
    con = sqlite3.connect(DB)
    con.row_factory = sqlite3.Row
    cutoff = f"-{int(days)} days"
    rows = con.execute(
        """SELECT signal_id, topic, source_type, source_name, source_url,
                  image_url, image_source, image_found, cached_image_path,
                  final_article_url
           FROM signals
           WHERE observed_at >= datetime('now', ?) ORDER BY observed_at DESC LIMIT ?""",
        (cutoff, limit),
    ).fetchall()

    candidates = []
    for row in rows:
        url, source, final_url = resolve_candidate(row)
        candidates.append((row, url, source, final_url))

    # Network validation is parallel; DB writes remain serialized below.
    def checked(item):
        row, url, source, final_url = item
        ok, status, ctype, byte_count = validate_image(url)
        return row, url, source, final_url, ok, status, ctype, byte_count

    results = []
    with concurrent.futures.ThreadPoolExecutor(max_workers=12) as pool:
        results = list(pool.map(checked, candidates))

    stats = {'checked': len(rows), 'with_image_before': 0, 'valid_images': 0,
             'new_image_urls': 0, 'marked_found': 0, 'no_image': 0,
             'http_200_images': 0, 'errors': 0}
    stats['with_image_before'] = sum(bool((r['image_url'] or '').strip()) and bool(r['image_found']) for r in rows)
    evidence = []
    for row, url, source, final_url, ok, status, ctype, byte_count in results:
        if ok:
            sets = ['image_url=?', 'image_source=?', 'image_found=1', 'image_checked_at=?']
            values = [url, source or 'validated_image', now]
            if final_url:
                sets.append('final_article_url=COALESCE(NULLIF(final_article_url,\'\'),?)')
                values.append(final_url)
            values.append(row['signal_id'])
            con.execute(f"UPDATE signals SET {', '.join(sets)} WHERE signal_id=?", values)
            stats['valid_images'] += 1
            stats['http_200_images'] += 1
            if not row['image_url']:
                stats['new_image_urls'] += 1
            if not row['image_found']:
                stats['marked_found'] += 1
            evidence.append({'signal_id': row['signal_id'], 'title': row['topic'],
                             'source_name': row['source_name'], 'image_url': url,
                             'image_source': source, 'http_status': status,
                             'content_type': ctype, 'bytes_head': byte_count,
                             'used_field': 'image_url'})
        else:
            con.execute('UPDATE signals SET image_checked_at=?, image_found=0 WHERE signal_id=?',
                        (now, row['signal_id']))
            stats['no_image'] += 1
            if url and status is not None and not ctype.startswith('image/'):
                stats['errors'] += 1
    con.commit()
    con.close()
    stats['evidence'] = evidence
    return stats


if __name__ == '__main__':
    parser = argparse.ArgumentParser()
    parser.add_argument('--days', type=int, default=30)
    parser.add_argument('--limit', type=int, default=500)
    args = parser.parse_args()
    import json
    print(json.dumps(refresh(args.days, args.limit), ensure_ascii=False, indent=2))
