#!/usr/bin/env python3
"""Warm Redis cache for vehicle info.

Runs in batches over all parts in the catalog, populating
nexus:vehicle:{part_id} keys in Redis. This eliminates the
DISTINCT ON + 4 JOINs query on vehicle_parts (2B rows) for
cached parts.

Usage:
    python3 warm_vehicle_cache.py
    python3 warm_vehicle_cache.py --dsn "postgresql://user:pass@localhost/db"
    python3 warm_vehicle_cache.py --batch-size 10000 --ttl 7200
"""

import argparse
import os
import subprocess
import sys
import time
from datetime import datetime

sys.path.insert(0, os.path.join(os.path.dirname(__file__), '..', 'pos'))

import psycopg2
import redis

DEFAULT_DSN = os.environ.get('MASTER_DB_URL', 'postgresql://postgres@/nexus_autoparts')
REDIS_URL = os.environ.get('REDIS_URL', 'redis://localhost:6379/0')
DEFAULT_BATCH_SIZE = 5000
DEFAULT_TTL = 3600


def log(msg):
    print(f"[{datetime.now().isoformat(timespec='seconds')}] {msg}", flush=True)


def _connect(dsn):
    """Connect to PostgreSQL; raise on failure."""
    return psycopg2.connect(dsn)


def _ensure_connection(dsn):
    """Try to connect. On peer-auth failure, re-run with sudo -u postgres."""
    try:
        return _connect(dsn)
    except psycopg2.OperationalError as exc:
        err = str(exc).lower()
        if 'peer' in err or 'authentication' in err:
            if os.geteuid() == 0:
                # Already root — can't sudo to postgres usefully; give clear message
                log("ERROR: PostgreSQL peer authentication failed.")
                log("       Run as postgres OS user:")
                log("         sudo -u postgres python3 " + __file__)
                log("       Or set MASTER_DB_URL with TCP host+password:")
                log("         export MASTER_DB_URL=postgresql://user:pass@localhost/nexus_autoparts")
                sys.exit(1)
            log("Peer auth failed. Re-running with sudo -u postgres ...")
            cmd = ['sudo', '-u', 'postgres', sys.executable, __file__]
            # Forward original env + CLI args
            env = os.environ.copy()
            env['MASTER_DB_URL'] = dsn
            for i, arg in enumerate(sys.argv[1:], start=1):
                if arg in ('--dsn', '-d') and i < len(sys.argv) - 1:
                    env['MASTER_DB_URL'] = sys.argv[i + 1]
            ret = subprocess.call(cmd, env=env)
            sys.exit(ret)
        raise


def main():
    parser = argparse.ArgumentParser(description='Warm Redis cache for vehicle info')
    parser.add_argument('--dsn', '-d', default=DEFAULT_DSN,
                        help='PostgreSQL DSN (default: MASTER_DB_URL env or peer auth)')
    parser.add_argument('--batch-size', '-b', type=int, default=DEFAULT_BATCH_SIZE,
                        help=f'Batch size (default: {DEFAULT_BATCH_SIZE})')
    parser.add_argument('--ttl', '-t', type=int, default=DEFAULT_TTL,
                        help=f'Redis TTL in seconds (default: {DEFAULT_TTL})')
    parser.add_argument('--dry-run', action='store_true',
                        help='Do not write to Redis, just log what would be done')
    args = parser.parse_args()

    log("Connecting to master DB and Redis...")
    conn = _ensure_connection(args.dsn)
    cur = conn.cursor()
    r = redis.from_url(REDIS_URL, decode_responses=True)
    r.ping()
    log("Connected.")

    # Get all part_ids
    cur.execute("SELECT id_part FROM parts WHERE oem_part_number IS NOT NULL ORDER BY id_part")
    all_ids = [row[0] for row in cur.fetchall()]
    total = len(all_ids)
    log(f"Total parts to warm: {total}")

    if total == 0:
        log("No parts found. Exiting.")
        return

    processed = 0
    cached = 0
    start = time.time()

    for i in range(0, total, args.batch_size):
        batch = all_ids[i:i + args.batch_size]
        cur.execute("""
            SELECT part_id, name_brand, name_model, year_car
            FROM part_vehicle_preview
            WHERE part_id = ANY(%s)
        """, (batch,))

        rows = cur.fetchall()
        if not args.dry_run:
            pipe = r.pipeline()
            for row in rows:
                info = f"{row[1]} {row[2]} {row[3]}"
                pipe.setex(f'nexus:vehicle:{row[0]}', args.ttl, info)
            pipe.execute()

        batch_cached = len(rows)
        processed += len(batch)
        cached += batch_cached
        elapsed = time.time() - start
        rate = processed / elapsed if elapsed > 0 else 0
        log(f"[{processed}/{total}] cached={batch_cached} ({rate:.0f}/s)")

    cur.close()
    conn.close()
    elapsed = time.time() - start
    log(f"Done. Cached {cached} vehicle entries in {elapsed:.0f}s")


if __name__ == '__main__':
    main()
