#!/usr/bin/env python3
"""
QR Code Generator — macOS optimized, no network calls, no destructive operations.
Built for Tony/OpenClaw. Generates QR codes locally using qrcode + Pillow.

Usage: python3 qr.py [options] [data]
"""

import argparse
import csv
import json
import sys
from datetime import datetime
from pathlib import Path
from typing import Optional

# ── Dependency check ──────────────────────────────────────────────────────────
try:
    import qrcode
    from qrcode.constants import ERROR_CORRECT_L, ERROR_CORRECT_M, ERROR_CORRECT_H
except ImportError:
    print("❌ Missing: pip3 install qrcode[pil]", file=sys.stderr)
    sys.exit(1)

try:
    from PIL import Image
    HAS_PIL = True
except ImportError:
    HAS_PIL = False

try:
    import segno
    HAS_SEGNO = True
except ImportError:
    HAS_SEGNO = False

# ── Formatters ────────────────────────────────────────────────────────────────

def format_url(data: str) -> str:
    if not data.startswith(("http://", "https://")):
        return f"https://{data}"
    return data

def format_wifi(ssid: str, password: str = "", security: str = "WPA", hidden: bool = False) -> str:
    sec = security.upper() if security.upper() in ("WPA", "WEP", "nopass") else "WPA"
    h = "true" if hidden else "false"
    if sec == "nopass":
        return f"WIFI:T:{sec};S:{ssid};H:{h};;"
    return f"WIFI:T:{sec};S:{ssid};P:{password};H:{h};;"

def format_vcard(name: str, phone: str = "", email: str = "", org: str = "", url: str = "") -> str:
    lines = ["BEGIN:VCARD", "VERSION:3.0", f"FN:{name}"]
    if phone:
        lines.append(f"TEL;TYPE=CELL:{phone}")
    if email:
        lines.append(f"EMAIL:{email}")
    if org:
        lines.append(f"ORG:{org}")
    if url:
        lines.append(f"URL:{url}")
    lines.append("END:VCARD")
    return "\n".join(lines)

def format_sms(phone: str, message: str = "") -> str:
    return f"SMSTO:{phone}:{message}" if message else f"SMSTO:{phone}"

def format_email(address: str, subject: str = "", body: str = "") -> str:
    parts = []
    if subject:
        parts.append(f"subject={subject}")
    if body:
        parts.append(f"body={body}")
    return f"mailto:{address}{'?' + '&'.join(parts) if parts else ''}"

def format_geo(lat: float, lng: float) -> str:
    return f"geo:{lat},{lng}"

# ── QR Generation ─────────────────────────────────────────────────────────────

def parse_color(color: str):
    """Accept hex (#RRGGBB) or named color strings."""
    if color.startswith("#"):
        r = int(color[1:3], 16)
        g = int(color[3:5], 16)
        b = int(color[5:7], 16)
        return (r, g, b)
    return color  # PIL handles named colors

def make_qr(
    data: str,
    output: Path,
    fmt: str = "png",
    fg_color="black",
    bg_color="white",
    logo: Optional[Path] = None,
    error_correction: str = "M",
    box_size: int = 10,
    border: int = 4,
):
    ec_map = {"L": ERROR_CORRECT_L, "M": ERROR_CORRECT_M, "H": ERROR_CORRECT_H}
    ec = ec_map.get(error_correction.upper(), ERROR_CORRECT_M)

    # Use higher correction when embedding a logo
    if logo:
        ec = ERROR_CORRECT_H

    qr = qrcode.QRCode(
        version=None,
        error_correction=ec,
        box_size=box_size,
        border=border,
    )
    qr.add_data(data)
    qr.make(fit=True)

    fg = parse_color(fg_color)
    bg = parse_color(bg_color)

    if fmt.lower() == "svg" and HAS_SEGNO:
        # Use segno for SVG
        qr_segno = segno.make(data, error="m" if not logo else "h")
        qr_segno.save(str(output), dark=fg_color, light=bg_color, scale=10)
        return output

    # PNG (default)
    img = qr.make_image(fill_color=fg, back_color=bg)

    if logo and HAS_PIL:
        logo_path = Path(logo)
        if logo_path.exists():
            logo_img = Image.open(logo_path)
            img_pil = img.convert("RGB") if hasattr(img, "convert") else img.get_image()
            qr_w, qr_h = img_pil.size
            logo_max = min(qr_w, qr_h) // 4
            logo_img.thumbnail((logo_max, logo_max), Image.LANCZOS)
            logo_w, logo_h = logo_img.size
            pos = ((qr_w - logo_w) // 2, (qr_h - logo_h) // 2)
            img_pil.paste(logo_img, pos, logo_img if logo_img.mode == "RGBA" else None)
            img_pil.save(str(output))
            return output

    img.save(str(output))
    return output

# ── Batch ─────────────────────────────────────────────────────────────────────

def batch_from_csv(csv_path: Path, output_dir: Path, args):
    output_dir.mkdir(parents=True, exist_ok=True)
    results = []
    with open(csv_path, newline="") as f:
        reader = csv.DictReader(f)
        for row in reader:
            name = row.get("name", row.get("slug", "qr"))
            url = row.get("url", row.get("data", ""))
            if not url:
                continue
            out = output_dir / f"{name}.{args.format}"
            make_qr(format_url(url), out, fmt=args.format, fg_color=args.fg_color, bg_color=args.bg_color)
            results.append(str(out))
            print(f"  ✅ {name} → {out}")
    return results

# ── CLI ───────────────────────────────────────────────────────────────────────

def main():
    parser = argparse.ArgumentParser(description="QR Code Generator — local, private, macOS-optimized")
    parser.add_argument("data", nargs="?", help="Data to encode (URL, text, etc.)")
    parser.add_argument("--type", choices=["url", "text", "wifi", "vcard", "sms", "email", "geo"],
                        default="url", help="Data type (default: url)")
    # WiFi options
    parser.add_argument("--ssid", help="WiFi SSID")
    parser.add_argument("--password", default="", help="WiFi password")
    parser.add_argument("--security", default="WPA", help="WiFi security (WPA/WEP/nopass)")
    parser.add_argument("--hidden", action="store_true", help="Hidden WiFi network")
    # vCard options
    parser.add_argument("--name", help="Contact name")
    parser.add_argument("--phone", help="Phone number")
    parser.add_argument("--email", dest="email_addr", help="Email address")
    parser.add_argument("--org", help="Organization")
    # Email options
    parser.add_argument("--subject", help="Email subject")
    parser.add_argument("--body", help="Email body")
    # Geo
    parser.add_argument("--lat", type=float, help="Latitude")
    parser.add_argument("--lng", type=float, help="Longitude")
    # Output
    parser.add_argument("--output", "-o", help="Output file path")
    parser.add_argument("--format", "-f", choices=["png", "svg"], default="png")
    parser.add_argument("--output-dir", help="Output directory (batch mode)")
    parser.add_argument("--batch", help="CSV file for batch generation (columns: name, url)")
    # Style
    parser.add_argument("--fg-color", default="black", help="Foreground color (hex or name)")
    parser.add_argument("--bg-color", default="white", help="Background color (hex or name)")
    parser.add_argument("--logo", help="Logo image path to embed in center")
    parser.add_argument("--box-size", type=int, default=10)
    parser.add_argument("--border", type=int, default=4)
    parser.add_argument("--error-correction", choices=["L", "M", "H"], default="M")

    args = parser.parse_args()

    # ── Batch mode ────────────────────────────────────────────────────────────
    if args.batch:
        csv_path = Path(args.batch)
        if not csv_path.exists():
            print(f"❌ CSV not found: {csv_path}", file=sys.stderr)
            sys.exit(1)
        out_dir = Path(args.output_dir) if args.output_dir else Path.cwd() / "qrcodes"
        results = batch_from_csv(csv_path, out_dir, args)
        print(f"\n✅ Batch complete: {len(results)} QR codes → {out_dir}")
        return

    # ── Single mode ───────────────────────────────────────────────────────────
    data = None
    if args.type == "url":
        if not args.data:
            parser.error("Provide a URL as positional argument")
        data = format_url(args.data)
    elif args.type == "text":
        if not args.data:
            parser.error("Provide text as positional argument")
        data = args.data
    elif args.type == "wifi":
        if not args.ssid:
            parser.error("--ssid required for wifi type")
        data = format_wifi(args.ssid, args.password, args.security, args.hidden)
    elif args.type == "vcard":
        if not args.name:
            parser.error("--name required for vcard type")
        data = format_vcard(args.name, args.phone or "", args.email_addr or "", args.org or "")
    elif args.type == "sms":
        if not args.phone:
            parser.error("--phone required for sms type")
        data = format_sms(args.phone, args.body or "")
    elif args.type == "email":
        if not args.email_addr:
            parser.error("--email required for email type")
        data = format_email(args.email_addr, args.subject or "", args.body or "")
    elif args.type == "geo":
        if args.lat is None or args.lng is None:
            parser.error("--lat and --lng required for geo type")
        data = format_geo(args.lat, args.lng)
    else:
        data = args.data or ""

    if not data:
        parser.error("No data to encode")

    # Output path
    if args.output:
        out = Path(args.output)
    else:
        ts = datetime.now().strftime("%Y%m%d_%H%M%S")
        out = Path.cwd() / f"qrcode_{args.type}_{ts}.{args.format}"

    out.parent.mkdir(parents=True, exist_ok=True)

    logo = Path(args.logo) if args.logo else None
    result = make_qr(
        data, out,
        fmt=args.format,
        fg_color=args.fg_color,
        bg_color=args.bg_color,
        logo=logo,
        error_correction=args.error_correction,
        box_size=args.box_size,
        border=args.border,
    )

    print(f"✅ QR code saved: {result}")
    print(f"   Type: {args.type} | Format: {args.format.upper()} | Size: {result.stat().st_size:,} bytes")


if __name__ == "__main__":
    main()
