#!/usr/bin/env python3
"""
preview_server.py — HTTP server pro náhled fotek z DB.
Spustit v Docker kontejneru python-runner na Tower.

Spuštění:
    docker exec -d python-runner python /scripts/preview_server.py

API:
    GET /preview?ids=101,202,303
    GET /preview?ids=101,202,303&thumb=400
    GET /health
"""

import base64
import io
import socketserver
import urllib.parse
from http.server import BaseHTTPRequestHandler

import psycopg
from PIL import Image

# ── Konfigurace ───────────────────────────────────────────────────────────────

PORT = 8765

DB = dict(
    host="192.168.1.76", port=5432,
    user="vladimir.buzalka",
    password="Vlado7309208104++",
    dbname="fotky_buzalkovi"
)

# Prefix v DB (jak to zapsal collect_pictures.py na Tower1)
NAS_LINUX_PREFIX   = "/mnt/user/ZalohaVsechObrazku/"
# Cesta uvnitř kontejneru (NFS mount přidaný v Unraid Docker UI)
NAS_CONTAINER_PATH = "/mnt/ZalohaVsechObrazku/"

# ── DB dotaz ──────────────────────────────────────────────────────────────────

QUERY = """
    SELECT
        z.id,
        z.cesta_zalohy,
        z.velikost,
        p.taken_at,
        p.taken_at_source,
        p.camera_make,
        p.camera_model,
        p.width,
        p.height,
        p.megapixels,
        p.gps_lat,
        p.gps_lon,
        COALESCE(
            (SELECT string_agg(e.error_code, ', ')
             FROM photo_errors e WHERE e.photo_id = p.id),
            ''
        ) AS chyby
    FROM zaloha_obrazku z
    JOIN photos p ON p.zaloha_id = z.id
    WHERE z.id = ANY(%s)
"""


def linux_to_container(p: str) -> str:
    rel = p.removeprefix(NAS_LINUX_PREFIX)
    return NAS_CONTAINER_PATH + rel


def fetch_rows(ids: list[int]) -> list:
    conn = psycopg.connect(**DB)
    cur = conn.cursor()
    cur.execute(QUERY, (ids,))
    rows = cur.fetchall()
    conn.close()
    order = {rid: i for i, rid in enumerate(ids)}
    return sorted(rows, key=lambda r: order.get(r[0], 9999))


# ── Thumbnaily ────────────────────────────────────────────────────────────────

def make_thumb_b64(path: str, size: int) -> str | None:
    try:
        img = Image.open(path)
        img.thumbnail((size, size), Image.LANCZOS)
        if img.mode not in ("RGB", "L"):
            img = img.convert("RGB")
        buf = io.BytesIO()
        img.save(buf, format="JPEG", quality=82)
        return base64.b64encode(buf.getvalue()).decode()
    except Exception:
        return None


# ── HTML ──────────────────────────────────────────────────────────────────────

def format_size(n) -> str:
    if n is None:
        return "?"
    for unit in ("B", "kB", "MB", "GB"):
        if n < 1024:
            return f"{n:.0f} {unit}"
        n /= 1024
    return f"{n:.1f} GB"


def render_html(rows: list, ids: list[int], thumb_size: int) -> str:
    cards = []
    ok = 0

    for i, row in enumerate(rows, 1):
        (rid, cesta, velikost, taken_at, taken_src,
         make_, model, width, height, mp, lat, lon, chyby) = row

        container_path = linux_to_container(cesta)
        b64 = make_thumb_b64(container_path, thumb_size)

        if b64:
            ok += 1
            img_tag = f'<img src="data:image/jpeg;base64,{b64}" alt="foto" data-id="{rid}" onclick="openLightbox(this)">'
            card_cls = "card"
        else:
            img_tag = (
                '<div class="no-img">⚠ soubor nedostupný</div>'
            )
            card_cls = "card broken"

        camera = " ".join(filter(None, [make_, model])) or "<em>neznámá</em>"
        date_str = taken_at.strftime("%Y-%m-%d %H:%M") if taken_at else "?"
        res = f"{width}×{height}" if width and height else "?"
        mp_str = f"{mp:.1f} Mpx" if mp else ""
        gps_link = (
            f'<a href="https://maps.google.com/?q={lat},{lon}" target="_blank">'
            f'📍 {lat:.4f}, {lon:.4f}</a>'
            if lat and lon else ""
        )
        chyby_html = f'<div class="err">{chyby}</div>' if chyby else ""

        cards.append(f"""
        <div class="{card_cls}">
            <div class="thumb">{img_tag}</div>
            <div class="meta">
                <div class="idx">id={rid} &nbsp;#{i}</div>
                <div><strong>{date_str}</strong> <span class="src">({taken_src})</span></div>
                <div>📷 {camera}</div>
                <div>{res} {mp_str} &nbsp; {format_size(velikost)}</div>
                {"<div>" + gps_link + "</div>" if gps_link else ""}
                {chyby_html}
                <div class="path" title="{container_path}">{container_path}</div>
            </div>
        </div>""")

    missing_ids = set(ids) - {r[0] for r in rows}
    missing_note = ""
    if missing_ids:
        missing_note = f'<div class="warn">ID nenalezena v DB: {sorted(missing_ids)}</div>'

    return f"""<!DOCTYPE html>
<html lang="cs">
<head>
<meta charset="utf-8">
<title>Preview fotek ({len(rows)})</title>
<style>
  body {{ font-family: sans-serif; background: #1a1a2e; color: #eee; margin: 0; padding: 16px; }}
  h1 {{ color: #e0c97f; margin-bottom: 4px; }}
  .info {{ color: #aaa; margin-bottom: 16px; font-size: .9em; }}
  .warn {{ color: #f99; margin-bottom: 12px; }}
  .grid {{ display: flex; flex-wrap: wrap; gap: 16px; }}
  .card {{ background: #16213e; border-radius: 8px; overflow: hidden;
            width: {thumb_size + 24}px; box-shadow: 0 2px 8px #0006; }}
  .card.broken {{ opacity: .55; }}
  .thumb {{ display:flex; align-items:center; justify-content:center;
            background:#0f3460; min-height: 120px; }}
  .thumb img {{ max-width:100%; max-height:{thumb_size}px; display:block; }}
  .no-img {{ color:#f88; padding:20px; text-align:center; font-size:.85em; }}
  .meta {{ padding: 8px 12px; font-size: .78em; line-height: 1.75; }}
  .meta strong {{ font-size: 1em; }}
  .idx {{ float:right; color:#666; font-size:.75em; }}
  .src {{ color:#888; }}
  .err {{ color:#f99; }}
  .path {{ color:#555; word-break:break-all; margin-top:4px; font-size:.7em; }}
  a {{ color:#7ec8e3; }}
  .thumb img {{ cursor: zoom-in; }}
  /* Lightbox */
  #lightbox {{
    display: none; position: fixed; inset: 0;
    background: #000c; z-index: 1000;
    align-items: center; justify-content: center;
  }}
  #lightbox.active {{ display: flex; }}
  #lightbox img {{
    max-width: 95vw; max-height: 95vh;
    object-fit: contain;
    border-radius: 4px;
    box-shadow: 0 0 40px #000a;
    cursor: zoom-out;
  }}
</style>
</head>
<body>
<div id="lightbox" onclick="closeLightbox()">
  <img id="lightbox-img" src="" alt="" onclick="event.stopPropagation()">
  <div id="lightbox-spin" style="display:none;color:#fff;font-size:2em;">⏳</div>
</div>
<script>
  function openLightbox(thumb) {{
    var lb = document.getElementById('lightbox');
    var lbImg = document.getElementById('lightbox-img');
    var spin = document.getElementById('lightbox-spin');
    var id = thumb.dataset.id;
    lbImg.src = '';
    lbImg.style.display = 'none';
    spin.style.display = 'block';
    lb.classList.add('active');
    var full = new Image();
    full.onload = function() {{
      lbImg.src = full.src;
      lbImg.style.display = 'block';
      spin.style.display = 'none';
    }};
    full.src = '/image?id=' + id;
  }}
  function closeLightbox() {{
    document.getElementById('lightbox').classList.remove('active');
    document.getElementById('lightbox-img').src = '';
  }}
  document.addEventListener('keydown', e => {{ if (e.key === 'Escape') closeLightbox(); }});
</script>
<h1>Preview fotek</h1>
<div class="info">
  Celkem ID: <strong>{len(ids)}</strong> &nbsp;·&nbsp;
  Načteno z DB: <strong>{len(rows)}</strong> &nbsp;·&nbsp;
  Obrázky dostupné: <strong>{ok}/{len(rows)}</strong>
</div>
{missing_note}
<div class="grid">
{"".join(cards)}
</div>
</body>
</html>"""


# ── HTTP handler ──────────────────────────────────────────────────────────────

class Handler(BaseHTTPRequestHandler):

    def log_message(self, format, *args):
        print(f"[{self.address_string()}] {format % args}", flush=True)

    def do_GET(self):
        parsed = urllib.parse.urlparse(self.path)

        # ── /health ──────────────────────────────────────────────────────────
        if parsed.path == "/health":
            self._text(200, "OK")
            return

        # ── /image ───────────────────────────────────────────────────────────
        if parsed.path == "/image":
            params = urllib.parse.parse_qs(parsed.query)
            try:
                zid = int(params.get("id", [""])[0])
            except ValueError:
                self._text(400, "Chybí id")
                return
            conn = psycopg.connect(**DB)
            cur = conn.cursor()
            cur.execute(
                "SELECT z.cesta_zalohy FROM zaloha_obrazku z JOIN photos p ON p.zaloha_id = z.id WHERE z.id = %s",
                (zid,)
            )
            row = cur.fetchone()
            conn.close()
            if not row:
                self._text(404, "ID nenalezeno")
                return
            path = linux_to_container(row[0])
            try:
                data = open(path, "rb").read()
                self.send_response(200)
                self.send_header("Content-Type", "image/jpeg")
                self.send_header("Content-Length", str(len(data)))
                self.end_headers()
                self.wfile.write(data)
            except Exception as ex:
                self._text(404, f"Soubor nedostupný: {ex}")
            return

        # ── /preview ─────────────────────────────────────────────────────────
        if parsed.path != "/preview":
            self._text(404, "Not found. Použij /preview?ids=101,202,303")
            return

        params = urllib.parse.parse_qs(parsed.query)
        ids_raw = params.get("ids", [""])[0]
        thumb = int(params.get("thumb", ["320"])[0])

        try:
            ids = [int(x.strip()) for x in ids_raw.split(",") if x.strip()]
        except ValueError:
            self._text(400, "Chybné IDs — očekávaný formát: ?ids=101,202,303")
            return

        if not ids:
            self._text(400, "Chybí IDs — např. /preview?ids=101,202,303")
            return

        print(f"  → {len(ids)} IDs, thumb={thumb}px", flush=True)
        rows = fetch_rows(ids)
        html = render_html(rows, ids, thumb)

        encoded = html.encode("utf-8")
        self.send_response(200)
        self.send_header("Content-Type", "text/html; charset=utf-8")
        self.send_header("Content-Length", str(len(encoded)))
        self.end_headers()
        self.wfile.write(encoded)

    def _text(self, code: int, msg: str):
        body = msg.encode("utf-8")
        self.send_response(code)
        self.send_header("Content-Type", "text/plain; charset=utf-8")
        self.send_header("Content-Length", str(len(body)))
        self.end_headers()
        self.wfile.write(body)


# ── Main ──────────────────────────────────────────────────────────────────────

if __name__ == "__main__":
    print(f"Preview server běží na http://0.0.0.0:{PORT}", flush=True)
    print(f"Test: http://tower:{PORT}/health", flush=True)
    print(f"Použití: http://tower:{PORT}/preview?ids=101,202,303&thumb=400", flush=True)
    socketserver.TCPServer.allow_reuse_address = True
    with socketserver.TCPServer(("", PORT), Handler) as httpd:
        httpd.serve_forever()
