"""PixVerse Studio – Web-UI + Job-Verwaltung."""

from __future__ import annotations

import json
import threading
import webbrowser
from datetime import datetime
from http.server import SimpleHTTPRequestHandler, ThreadingHTTPServer
from pathlib import Path
from urllib.parse import parse_qs, urlparse

ROOT = Path(__file__).resolve().parents[1]

try:
    from jam.jampulse_desktop import studio_root, videos_frame_dir

    STUDIO_ROOT = studio_root()
    VIDEOS_DIR = videos_frame_dir()
except ImportError:
    STUDIO_ROOT = Path.home() / "Documents" / "PixVerse-Studio"
    VIDEOS_DIR = STUDIO_ROOT / "videos"

JOBS_DIR = STUDIO_ROOT / "jobs"
UPLOADS_DIR = STUDIO_ROOT / "uploads"
PORT = 8791
SITE_URL = f"http://127.0.0.1:{PORT}/"
PID_FILE = STUDIO_ROOT / ".frame-server.pid"
LOG_FILE = STUDIO_ROOT / "frame-server.log"

_lock = threading.Lock()


def server_running(port: int | None = None) -> bool:
    import socket

    p = port if port is not None else PORT
    with socket.socket(socket.AF_INET, socket.SOCK_STREAM) as s:
        s.settimeout(0.4)
        return s.connect_ex(("127.0.0.1", p)) == 0


def _log(msg: str) -> None:
    try:
        ensure_dirs()
        line = f"{datetime.now().isoformat(timespec='seconds')} {msg}\n"
        with LOG_FILE.open("a", encoding="utf-8") as f:
            f.write(line)
    except OSError:
        pass


def ensure_dirs() -> None:
    for d in (STUDIO_ROOT, JOBS_DIR, VIDEOS_DIR, UPLOADS_DIR):
        d.mkdir(parents=True, exist_ok=True)


def _job_path(job_id: str) -> Path:
    return JOBS_DIR / f"{job_id}.json"


def save_job(job: dict) -> None:
    ensure_dirs()
    jid = job["id"]
    _job_path(jid).write_text(json.dumps(job, ensure_ascii=False, indent=2) + "\n", encoding="utf-8")


def load_job(job_id: str) -> dict | None:
    p = _job_path(job_id)
    if not p.is_file():
        return None
    try:
        return json.loads(p.read_text(encoding="utf-8"))
    except (json.JSONDecodeError, OSError):
        return None


def list_jobs(limit: int = 30) -> list[dict]:
    ensure_dirs()
    files = sorted(JOBS_DIR.glob("*.json"), key=lambda p: p.stat().st_mtime, reverse=True)
    out: list[dict] = []
    for p in files[:limit]:
        try:
            out.append(json.loads(p.read_text(encoding="utf-8")))
        except (json.JSONDecodeError, OSError):
            pass
    return out


def _run_pixverse_job(job_id: str) -> None:
    job = load_job(job_id)
    if not job:
        return
    from jam import pixverse_api

    try:
        job["status"] = "generating"
        job["updated_at"] = datetime.now().isoformat(timespec="seconds")
        save_job(job)

        if job.get("mode") == "image":
            img_path = Path(job["image_path"])
            img_id = pixverse_api.upload_image(img_path)
            job["img_id"] = img_id
            video_id = pixverse_api.image_to_video(
                img_id,
                job["prompt"],
                aspect_ratio=job.get("aspect_ratio", "9:16"),
                duration=int(job.get("duration", 5)),
                model=job.get("model", "v5.5"),
                quality=job.get("quality", "540p"),
            )
        else:
            video_id = pixverse_api.text_to_video(
                job["prompt"],
                aspect_ratio=job.get("aspect_ratio", "9:16"),
                duration=int(job.get("duration", 5)),
                model=job.get("model", "v5.5"),
                quality=job.get("quality", "540p"),
                generate_audio=bool(job.get("audio", False)),
            )

        job["video_id"] = video_id
        job["status"] = "processing"
        save_job(job)

        result = pixverse_api.wait_for_video(video_id, timeout_sec=900)
        job["remote_url"] = result.get("url", "")
        job["status"] = "done"
        if job["remote_url"]:
            dest = VIDEOS_DIR / f"{job_id}.mp4"
            pixverse_api.download_video(job["remote_url"], dest)
            job["local_video"] = str(dest)
        job["updated_at"] = datetime.now().isoformat(timespec="seconds")
        save_job(job)
    except Exception as e:
        job["status"] = "error"
        job["error"] = str(e)[:500]
        job["updated_at"] = datetime.now().isoformat(timespec="seconds")
        save_job(job)


def _run_fallback_job(job_id: str) -> None:
    """Ohne PixVerse-Key: verkaufbares 60-Sek.-Video (Commercial)."""
    _run_minute_job(job_id, hd=True)


def _run_minute_job(job_id: str, *, hd: bool) -> None:
    job = load_job(job_id)
    if not job:
        return
    try:
        job["status"] = "generating"
        job["engine"] = "minute_hd" if hd else "ia1minute"
        save_job(job)
        from jam.ia1minute import run_ia1minute
        import shutil

        from jam.jampulse_license import can_generate, load_license, save_license

        kind = str(job.get("generation_kind") or "free")
        ok_lic, lic_msg, _ = can_generate(consume=False)
        if not ok_lic:
            job["status"] = "error"
            job["error"] = lic_msg
            job["updated_at"] = datetime.now().isoformat(timespec="seconds")
            save_job(job)
            return

        work = STUDIO_ROOT / ("minute_hd" if hd else "fallback") / job_id
        target = int(job.get("duration") or 60)
        watermark = bool(job.get("watermark"))
        quality = str(job.get("quality") or "1080p")
        aspect = str(job.get("aspect_ratio") or "9:16")
        resolution = "4k" if quality.lower() in ("4k", "2160p", "uhd") else "1080p"
        commercial = bool(job.get("commercial"))
        dialogue = bool(job.get("dialogue"))
        visual_style = str(job.get("visual_style") or ("3d" if dialogue else "realistic"))
        ia_kwargs: dict = {
            "prompt": job["prompt"],
            "work_dir": work,
            "hd": hd or resolution == "4k",
            "commercial": commercial,
            "target_sec": target,
            "resolution": resolution,
            "aspect_ratio": aspect,
            "visual_style": visual_style,
        }
        if dialogue:
            from jam.ia1minute import weekend_date_turns

            ia_kwargs["dialogue"] = True
            ia_kwargs["dialogue_turns"] = weekend_date_turns()
            ia_kwargs["dialogue_title"] = str(job.get("title") or "Das Wochenend-Date")
        result = run_ia1minute(**ia_kwargs)
        min_sec = max(59.9, float(target) * 0.98)
        if result.get("ok") and result.get("duration_sec", 0) >= min_sec:
            src = Path(result["video"])
            dest = VIDEOS_DIR / f"{job_id}.mp4"
            if src.is_file():
                shutil.copy2(src, dest)
                job["local_video"] = str(dest)
            else:
                job["local_video"] = result.get("video")
                dest = Path(job["local_video"]) if job.get("local_video") else dest
            if watermark and dest.is_file():
                from jam.jampulse_watermark import apply_watermark

                ok_wm, wm_msg = apply_watermark(dest)
                if not ok_wm:
                    job["watermark_error"] = wm_msg[:200]
            from jam.jampulse_license import is_owner

            if not is_owner():
                lic = load_license()
                if kind == "free":
                    lic["free_watermark_used"] = True
                elif kind == "paid":
                    lic["videos_remaining"] = max(0, int(lic.get("videos_remaining") or 0) - 1)
                save_license(lic)
            job["status"] = "done"
            job["engine"] = "artist" if dialogue else ("minute_hd" if hd else "ia1minute")
            job["caption"] = result.get("caption_file")
            job["duration_sec"] = result.get("duration_sec", target)
            job["target_sec"] = target
            job["resolution"] = resolution
            job["aspect_ratio"] = aspect
            job["word_count"] = result.get("word_count", 0)
            job["voice_sec"] = result.get("voice_sec", 0)
            job["commercial"] = commercial
            job["watermark"] = watermark
            if dialogue:
                job["dialogue"] = True
                job["visual_style"] = visual_style
            job["updated_at"] = datetime.now().isoformat(timespec="seconds")
            save_job(job)
            if job.get("auto_publish") and not watermark:
                platforms = []
                if job.get("publish_tiktok", True):
                    platforms.append("tiktok")
                if job.get("publish_youtube", True):
                    platforms.append("youtube")
                if job.get("publish_instagram", True):
                    platforms.append("instagram")
                if platforms:
                    try:
                        from jam.social_publish import publish_after_jampulse_job

                        publish_after_jampulse_job(job, platforms=tuple(platforms))
                        job = load_job(job_id) or job
                        job["publish_queued"] = True
                        job["publish_platforms"] = platforms
                        save_job(job)
                    except Exception as pub_err:
                        job = load_job(job_id) or job
                        job["publish_error"] = str(pub_err)[:200]
                        save_job(job)
        elif result.get("ok"):
            job["status"] = "error"
            job["error"] = (
                f"Video zu kurz ({result.get('duration_sec')}s) – "
                f"mindestens {int(min_sec)} Sek. nötig"
            )
            job["updated_at"] = datetime.now().isoformat(timespec="seconds")
            save_job(job)
        else:
            job["status"] = "error"
            job["error"] = result.get("error") or result.get("message") or "Generierung fehlgeschlagen"
            job["updated_at"] = datetime.now().isoformat(timespec="seconds")
            save_job(job)
    except Exception as e:
        job["status"] = "error"
        job["error"] = str(e)[:500]
        save_job(job)


def start_job(body: dict) -> dict:
    from jam.jampulse_brand import SINGLE_VIDEO_OFFER
    from jam.jampulse_license import next_generation_kind, needs_watermark
    from jam.pixverse_api import api_key
    import uuid as _uuid

    ensure_dirs()
    kind = next_generation_kind()
    if kind == "none":
        single_eur = f"{float(SINGLE_VIDEO_OFFER['price_eur']):.2f}".replace(".", ",")
        return {
            "ok": False,
            "error": f"Gratis-Video verbraucht – Einzelvideo ({single_eur} €) oder Pro-Abo kaufen",
        }

    job_id = _uuid.uuid4().hex[:12]
    prompt = str(body.get("prompt") or "").strip()
    if not prompt:
        return {"ok": False, "error": "Prompt fehlt"}

    mode = str(body.get("mode") or "text")
    req_duration = int(body.get("duration") or 60)
    req_quality = str(body.get("quality") or "1080p")
    minute_hd = mode == "minute_hd" or req_duration >= 60 or str(body.get("mode") or "") == "artist_dialog"
    dialogue = bool(body.get("dialogue")) or str(body.get("mode") or "") == "artist_dialog"
    watermark = needs_watermark(kind)
    is_free = kind == "free"

    job = {
        "id": job_id,
        "prompt": prompt,
        "mode": "minute_hd" if minute_hd else mode,
        "aspect_ratio": str(body.get("aspect_ratio") or "9:16"),
        "duration": max(60, min(120, req_duration)) if minute_hd else int(body.get("duration") or 5),
        "model": str(body.get("model") or "v5.5"),
        "quality": req_quality if minute_hd else str(body.get("quality") or "540p"),
        "audio": bool(body.get("audio", False)),
        "hd": minute_hd,
        "commercial": minute_hd,
        "watermark": watermark,
        "generation_kind": kind,
        "preview": False,
        "status": "queued",
        "created_at": datetime.now().isoformat(timespec="seconds"),
        "updated_at": datetime.now().isoformat(timespec="seconds"),
    }
    if dialogue:
        job["dialogue"] = True
        job["visual_style"] = str(body.get("visual_style") or "3d")
        job["title"] = str(body.get("title") or "Artist Dialog")
        job["engine"] = "artist"
        if str(body.get("mode") or "") == "artist_dialog":
            job["duration"] = 60
            job["quality"] = str(body.get("quality") or "4k")
    if body.get("image_path"):
        job["image_path"] = str(body["image_path"])

    if minute_hd:
        job["auto_publish"] = bool(
            not is_free
            and (
                body.get("auto_publish", body.get("publish_tiktok", True) or body.get("publish_youtube", True))
            )
        )
        job["publish_tiktok"] = bool(not is_free and body.get("publish_tiktok", True))
        job["publish_youtube"] = bool(not is_free and body.get("publish_youtube", True))
        job["publish_instagram"] = bool(not is_free and body.get("publish_instagram", True))
    else:
        job["auto_publish"] = False
        job["publish_tiktok"] = False
        job["publish_youtube"] = False
        job["publish_instagram"] = False

    save_job(job)

    if minute_hd:
        threading.Thread(target=_run_minute_job, args=(job_id,), kwargs={"hd": True}, daemon=True).start()
        return {"ok": True, "jobId": job_id, "engine": "minute_hd"}

    if api_key():
        threading.Thread(target=_run_pixverse_job, args=(job_id,), daemon=True).start()
        engine = "pixverse"
    else:
        threading.Thread(target=_run_fallback_job, args=(job_id,), daemon=True).start()
        engine = "ia1minute"

    return {"ok": True, "jobId": job_id, "engine": engine}


def studio_html() -> str:
    from jam.jampulse_frame_ui import render_studio_html

    return render_studio_html()


def build_site() -> Path:
    ensure_dirs()
    try:
        from jam.vidpuls_showcase import ensure_showcase_built

        ensure_showcase_built(min_count=8)
    except Exception:
        pass
    html = studio_html()
    (STUDIO_ROOT / "index.html").write_text(html, encoding="utf-8")
    return STUDIO_ROOT


class StudioHandler(SimpleHTTPRequestHandler):
    def __init__(self, *args, **kwargs):
        super().__init__(*args, directory=str(STUDIO_ROOT), **kwargs)

    def log_message(self, fmt, *args):
        pass

    def _json(self, data, code=200):
        body = json.dumps(data, ensure_ascii=False).encode("utf-8")
        self.send_response(code)
        self.send_header("Content-Type", "application/json; charset=utf-8")
        self.send_header("Content-Length", str(len(body)))
        self.end_headers()
        self.wfile.write(body)

    def _read_json(self) -> dict:
        n = int(self.headers.get("Content-Length") or 0)
        if n <= 0:
            return {}
        return json.loads(self.rfile.read(n).decode("utf-8"))

    def do_GET(self):
        path = urlparse(self.path).path
        if path == "/api/health":
            from jam.pixverse_api import api_key

            return self._json({"ok": True, "pixverse": bool(api_key()), "minute_hd": True, "social": True})
        if path == "/api/social/status":
            from jam.social_publish import STATUS_PATH, load_config

            st = {}
            if STATUS_PATH.is_file():
                try:
                    st = json.loads(STATUS_PATH.read_text(encoding="utf-8"))
                except (json.JSONDecodeError, OSError):
                    pass
            return self._json({"ok": True, "config": load_config(), "status": st})
        if path == "/api/license":
            from jam.jampulse_license import license_status

            return self._json({"ok": True, **license_status()})
        if path == "/api/calendar":
            from jam.jampulse_calendar import list_entries

            return self._json({"ok": True, "entries": list_entries()})
        if path == "/api/pricing":
            from jam.jampulse_checkout import product_catalog
            from jam.jampulse_brand import MINUTE_OFFER, SUBSCRIPTION_OFFER, PRODUCT_BRAND
            from jam.jampulse_payments import enrich_catalog, payment_methods_summary, sepa_info

            return self._json(
                {
                    "ok": True,
                    "brand": PRODUCT_BRAND,
                    "products": enrich_catalog(product_catalog()),
                    "minute": MINUTE_OFFER,
                    "subscription": SUBSCRIPTION_OFFER,
                    "paymentMethods": payment_methods_summary(),
                    "sepa": sepa_info(),
                }
            )
        if path == "/danke":
            q = urlparse(self.path).query
            from urllib.parse import parse_qs

            qs = parse_qs(q)
            sid = (qs.get("session_id") or [""])[0]
            product = (qs.get("product") or [""])[0]
            msg = "Danke für deinen Kauf!"
            if sid:
                from jam.jampulse_checkout import verify_session

                vr = verify_session(sid)
                if vr.get("ok"):
                    msg = f"Zahlung OK – {product or vr.get('productId', '')}. Du kannst jetzt Videos erstellen."
                else:
                    msg = vr.get("error") or "Zahlung wird noch geprüft…"
            from jam.jampulse_brand import PRODUCT_BRAND

            body = f"""<!DOCTYPE html><html lang="de"><head><meta charset="utf-8"/>
            <title>{PRODUCT_BRAND} – Danke</title></head><body style="font-family:sans-serif;background:#0a0810;color:#eee;padding:2rem">
            <h1>{PRODUCT_BRAND}</h1><p>{msg}</p><p><a href="/" style="color:#a855f7">Zurück zum Studio</a></p></body></html>"""
            self.send_response(200)
            self.send_header("Content-Type", "text/html; charset=utf-8")
            self.end_headers()
            self.wfile.write(body.encode("utf-8"))
            return
        if path == "/api/jobs":
            return self._json({"ok": True, "jobs": list_jobs()})
        if path == "/api/gallery":
            from jam.vidpuls_showcase import gallery_api_payload

            return self._json(gallery_api_payload(list_jobs()))
        if path.startswith("/api/showcase/"):
            sid = path.split("/")[-1].split("?")[0].removesuffix(".mp4")
            from jam.vidpuls_showcase import showcase_video_path

            fp = showcase_video_path(sid)
            if not fp:
                return self._json({"ok": False, "error": "Showcase-Video fehlt"}, 404)
            data = fp.read_bytes()
            self.send_response(200)
            self.send_header("Content-Type", "video/mp4")
            self.send_header("Content-Length", str(len(data)))
            self.send_header("Content-Disposition", f'inline; filename="{sid}.mp4"')
            self.end_headers()
            self.wfile.write(data)
            return
        if path.startswith("/api/job/"):
            jid = path.split("/")[-1]
            job = load_job(jid)
            if not job:
                return self._json({"ok": False, "error": "Nicht gefunden"}, 404)
            return self._json(job)
        if path.startswith("/api/video/"):
            jid = path.split("/")[-1].split("?")[0]
            job = load_job(jid)
            if not job or not job.get("local_video"):
                return self._json({"ok": False, "error": "Video fehlt"}, 404)
            fp = Path(job["local_video"])
            if not fp.is_file():
                return self._json({"ok": False, "error": "Datei fehlt"}, 404)
            data = fp.read_bytes()
            self.send_response(200)
            self.send_header("Content-Type", "video/mp4")
            self.send_header("Content-Length", str(len(data)))
            self.send_header("Content-Disposition", f'inline; filename="{jid}.mp4"')
            self.end_headers()
            self.wfile.write(data)
            return
        if path in ("/", "/index.html"):
            self.send_response(200)
            body = studio_html().encode("utf-8")
            self.send_header("Content-Type", "text/html; charset=utf-8")
            self.send_header("Content-Length", str(len(body)))
            self.end_headers()
            self.wfile.write(body)
            return
        from jam.jampulse_checkout import _site_url as clipwelle_site_url
        from jam.clipwelle_legal import legal_html_for_path

        legal_body = legal_html_for_path(path, site_url=clipwelle_site_url())
        if legal_body:
            data = legal_body.encode("utf-8")
            self.send_response(200)
            self.send_header("Content-Type", "text/html; charset=utf-8")
            self.send_header("Content-Length", str(len(data)))
            self.end_headers()
            self.wfile.write(data)
            return
        super().do_GET()

    def do_POST(self):
        path = urlparse(self.path).path
        if path == "/api/generate/text":
            body = self._read_json()
            return self._json(start_job(body))
        if path == "/api/enhance-prompt":
            from jam.jampulse_frame_ui import enhance_prompt

            body = self._read_json()
            raw = str(body.get("prompt") or "").strip()
            if not raw:
                return self._json({"ok": False, "error": "Prompt fehlt"}, 400)
            improved = enhance_prompt(raw)
            return self._json({"ok": True, "prompt": improved})
        if path == "/api/generate/image":
            body = self._read_json()
            import base64

            b64 = str(body.get("image_base64") or "").strip()
            if not b64:
                return self._json({"ok": False, "error": "image_base64 fehlt"}, 400)
            ensure_dirs()
            ext = Path(str(body.get("filename") or "img.jpg")).suffix or ".jpg"
            img_path = UPLOADS_DIR / f"{datetime.now().strftime('%Y%m%d%H%M%S')}{ext}"
            img_path.write_bytes(base64.b64decode(b64))
            payload = {
                "prompt": str(body.get("prompt") or "").strip(),
                "mode": "image",
                "image_path": str(img_path),
                "aspect_ratio": body.get("aspect_ratio", "9:16"),
                "duration": int(body.get("duration") or 5),
                "quality": body.get("quality", "540p"),
                "model": body.get("model", "v5.5"),
            }
            return self._json(start_job(payload))
        if path.startswith("/api/publish/"):
            jid = path.split("/")[-1]
            body = self._read_json()
            platforms = []
            if body.get("tiktok", True):
                platforms.append("tiktok")
            if body.get("youtube", True):
                platforms.append("youtube")
            if body.get("instagram", True):
                platforms.append("instagram")
            from jam.social_publish import publish_from_job_id

            return self._json(publish_from_job_id(jid, platforms=tuple(platforms)))
        if path == "/api/calendar/generate":
            from jam.jampulse_calendar import generate_calendar

            body = self._read_json()
            days = int(body.get("days") or 30)
            return self._json({"ok": True, **generate_calendar(days=days)})
        if path == "/api/calendar/run":
            from jam.jampulse_calendar import run_entry, run_due

            body = self._read_json()
            eid = str(body.get("entryId") or "").strip()
            if eid:
                return self._json(run_entry(eid))
            return self._json({"ok": True, "results": run_due(limit=int(body.get("limit") or 1))})
        if path == "/api/checkout":
            from jam.jampulse_checkout import create_checkout_session

            body = self._read_json()
            pid = str(body.get("productId") or body.get("product") or "pro_monthly")
            email = str(body.get("email") or "")
            return self._json(create_checkout_session(pid, email=email))
        if path == "/api/checkout/verify":
            from jam.jampulse_checkout import verify_session

            body = self._read_json()
            sid = str(body.get("sessionId") or body.get("session_id") or "")
            return self._json(verify_session(sid))
        return self._json({"ok": False, "error": "Unbekannt"}, 404)


def run_server(*, open_browser: bool = True, daemon: bool = False) -> None:
    if server_running():
        if not daemon:
            print(f"VidPuls Studio laeuft schon -> {SITE_URL}")
        _log("skip already running")
        return

    build_site()
    httpd = ThreadingHTTPServer(("127.0.0.1", PORT), StudioHandler)
    try:
        PID_FILE.write_text(str(httpd.server_address), encoding="utf-8")
    except OSError:
        pass

    msg = f"VidPuls Studio -> {SITE_URL}"
    if not daemon:
        print(msg)
    _log("started " + SITE_URL)

    from jam.pixverse_api import api_key

    engine = "PixVerse API" if api_key() else "Fallback IA 1 Minute"
    if not daemon:
        print(f"Engine: {engine}")
    _log(f"engine {engine}")

    if open_browser and not daemon:
        webbrowser.open(SITE_URL, new=1)
    try:
        httpd.serve_forever()
    except KeyboardInterrupt:
        if not daemon:
            print("\nBeendet.")
    finally:
        try:
            PID_FILE.unlink(missing_ok=True)
        except OSError:
            pass
