"""Push project metadata to gnommoeditor (prod) or gnommoweb (local).

Usage:
  gnommo push -p video1                    # push parent video project
  gnommo push -p short_pixelated_universe  # push a short project
  gnommo push -p myproject --force         # force push, overwrite server

Reads project.json and companion JSON files, then POSTs to:
  Production:  POST /api/ingest  (gnommoeditor, uses INGEST_API_KEY)
  Local:       POST /api/projects/push  (gnommoweb, uses GNOMMOWEB_API_KEY)

Configuration (from .env or environment):
  GNOMMOEDITOR_URL   Base URL for production (e.g. https://editor.glitch.university)
  INGEST_API_KEY     Bearer token for gnommoeditor ingest endpoint
  GNOMMOWEB_URL      Base URL for local dev (e.g. http://localhost:3001)
  GNOMMOWEB_API_KEY  Bearer token for local gnommoweb
"""

import json
import os
import sys
from datetime import datetime, timezone
from pathlib import Path

try:
    import requests
except ImportError:
    print(
        "Error: 'requests' package is required. Run: pip install requests",
        file=sys.stderr,
    )
    sys.exit(1)

SYNC_FILE_LOCAL = ".gnommo_sync.json"
SYNC_FILE_PROD = ".gnommo_sync.prod.json"


def _sync_file(prod: bool) -> str:
    return SYNC_FILE_PROD if prod else SYNC_FILE_LOCAL


def _load_env_file():
    env_path = Path(__file__).parent.parent / ".env"
    if not env_path.exists():
        return
    with open(env_path) as f:
        for line in f:
            line = line.strip()
            if not line or line.startswith("#") or "=" not in line:
                continue
            key, _, value = line.partition("=")
            key = key.strip()
            value = value.strip().strip('"').strip("'")
            if key not in os.environ:
                os.environ[key] = value


def _read_sync(project_path: Path, prod: bool = False) -> dict:
    sync_file = project_path / _sync_file(prod)
    if sync_file.exists():
        with open(sync_file) as f:
            return json.load(f)
    return {}


def _write_sync(project_path: Path, data: dict, prod: bool = False):
    with open(project_path / _sync_file(prod), "w") as f:
        json.dump(data, f, indent=2)


def _load_json_file(path: Path, label: str, verbose: bool) -> dict | list | None:
    """Load a JSON file, returning None if it doesn't exist."""
    if not path.exists():
        if verbose:
            print(f"  {label}: not found at {path}")
        return None
    try:
        with open(path) as f:
            return json.load(f)
    except json.JSONDecodeError as e:
        print(f"  Warning: could not parse {label} ({path}): {e}", file=sys.stderr)
        return None


def _load_text_file(path: Path, label: str) -> str | None:
    """Load a text file, returning None if it doesn't exist."""
    if not path.exists():
        return None
    try:
        return path.read_text(encoding="utf-8")
    except UnicodeDecodeError:
        return path.read_text(encoding="latin-1")


def _parse_seconds(value) -> float | None:
    """Convert a time value like '30s', '1:30', or 30 into a plain float of seconds."""
    if value is None:
        return None
    if isinstance(value, (int, float)):
        return float(value)
    value = str(value).strip()
    if value.endswith("s"):
        value = value[:-1]
    if ":" in value:
        parts = value.split(":")
        if len(parts) == 2:
            return float(parts[0]) * 60 + float(parts[1])
        elif len(parts) == 3:
            return float(parts[0]) * 3600 + float(parts[1]) * 60 + float(parts[2])
    return float(value)


def _sanitize_time_fields(data: dict | None, fields: list[str]) -> dict | None:
    """Return a copy of dict with the given fields converted to plain floats."""
    if not data:
        return data
    result = dict(data)
    for field in fields:
        if field in result and result[field] is not None:
            try:
                result[field] = _parse_seconds(result[field])
            except (ValueError, TypeError):
                pass  # leave invalid values for the server to reject with a clear error
    return result


def _build_ingest_payload(project: dict, project_path: Path, verbose: bool) -> dict:
    """Build the rich ingest payload for gnommoeditor POST /api/ingest."""

    # ── slides ────────────────────────────────────────────────────────────────
    slides_path_str = project.get("slides", "slides.json")
    slides_path = project_path / slides_path_str
    slides = _load_json_file(slides_path, "slides", verbose)
    if slides and verbose:
        print(f"  slides: {len(slides)} entries")

    # ── manuscript ────────────────────────────────────────────────────────────
    manuscript_path_str = project.get("manuscript", "manuscript.txt")
    manuscript_path = project_path / manuscript_path_str
    manuscript = _load_text_file(manuscript_path, "manuscript")
    if manuscript:
        print(f"  manuscript: {len(manuscript)} chars")
    elif verbose:
        print(f"  manuscript: not found at {manuscript_path}")

    # ── narration ─────────────────────────────────────────────────────────────
    narration_path_str = project.get("narration", "narration.json")
    narration_path = project_path / narration_path_str
    narration = _load_json_file(narration_path, "narration", verbose)

    # ── audio ─────────────────────────────────────────────────────────────────
    audio_path_str = project.get("audio_tracks", "audio.json")
    audio_path = project_path / audio_path_str
    audio = _load_json_file(audio_path, "audio", verbose)

    # ── videos ────────────────────────────────────────────────────────────────
    videos_path_str = project.get("videos", "videos.json")
    videos_path = project_path / videos_path_str
    videos = _load_json_file(videos_path, "videos", verbose)

    # ── citations ─────────────────────────────────────────────────────────────
    citations_path = project_path / "citations.json"
    citations = _load_json_file(citations_path, "citations", verbose)

    # Sanitize time fields — convert "30s", "1:30" etc. to plain floats
    _VIDEO_TIME_FIELDS = ["duration", "pause_narration", "skip", "take"]
    _NARRATION_TIME_FIELDS = ["skip", "take"]
    _AUDIO_TIME_FIELDS = ["overlap", "duration"]

    if videos:
        videos = {
            k: _sanitize_time_fields(v, _VIDEO_TIME_FIELDS) for k, v in videos.items()
        }
    if narration:
        narration = {
            k: _sanitize_time_fields(v, _NARRATION_TIME_FIELDS)
            for k, v in narration.items()
        }
    if audio:
        audio = {
            k: _sanitize_time_fields(v, _AUDIO_TIME_FIELDS) for k, v in audio.items()
        }

    return {
        "project": project,
        "slides": slides,
        "manuscript": manuscript,
        "narration": narration,
        "audio": audio,
        "videos": videos,
        "citations": citations,
    }


def cmd_push(
    project_path: Path, verbose: bool = False, force: bool = False, prod: bool = False
) -> int:
    _load_env_file()

    project_file = project_path / "project.json"
    if not project_file.exists():
        print(f"Error: {project_file} not found", file=sys.stderr)
        return 1

    with open(project_file) as f:
        project = json.load(f)

    project_id = project.get("id")
    name = project.get("name")
    if not project_id or not name:
        print("Error: project.json must have 'id' and 'name' fields.", file=sys.stderr)
        return 1

    if prod:
        return _push_prod(project, project_path, verbose)
    else:
        return _push_local(project, project_path, verbose, force)


# ── Production: gnommoeditor POST /api/ingest ─────────────────────────────────


def _push_prod(project: dict, project_path: Path, verbose: bool) -> int:
    api_url = os.environ.get("GNOMMOEDITOR_URL", "").rstrip("/")
    api_key = os.environ.get("INGEST_API_KEY", "")
    if not api_url:
        print("Error: GNOMMOEDITOR_URL is not set.", file=sys.stderr)
        return 1
    if not api_key:
        print("Error: INGEST_API_KEY is not set.", file=sys.stderr)
        return 1

    project_id = project["id"]
    payload = _build_ingest_payload(project, project_path, verbose)

    # Attach sync state so the server can record it
    sync = _read_sync(project_path, prod=True)
    if sync:
        payload["sync"] = sync

    print(f"  → {api_url}/api/ingest")

    try:
        r = requests.post(
            f"{api_url}/api/ingest",
            json=payload,
            headers={"Authorization": f"Bearer {api_key}"},
            timeout=30,
        )
    except requests.exceptions.ConnectionError:
        print(f"✗ Could not connect to {api_url}")
        return 1

    if not r.ok:
        try:
            body = r.json()
        except Exception:
            body = r.text[:500]
        print(f"✗ Server returned {r.status_code}: {body}")
        return 1

    result = r.json()
    video_id = result.get("video_id")
    slides_upserted = result.get("slides_upserted", 0)

    # Update sync state
    now_iso = datetime.now(tz=timezone.utc).isoformat(timespec="seconds")
    existing_sync = _read_sync(project_path, prod=True)
    _write_sync(
        project_path,
        {**existing_sync, "last_pushed_at": now_iso},
        prod=True,
    )

    print(f"✓ {project_id} → video #{video_id} ({slides_upserted} slides)")
    return 0


# ── Local dev: gnommoweb POST /api/projects/push ──────────────────────────────


def _push_local(project: dict, project_path: Path, verbose: bool, force: bool) -> int:
    api_url = os.environ.get("GNOMMOWEB_URL", "").rstrip("/")
    api_key = os.environ.get("GNOMMOWEB_API_KEY", "")
    if not api_url:
        print("Error: GNOMMOWEB_URL is not set.", file=sys.stderr)
        return 1
    if not api_key:
        print("Error: GNOMMOWEB_API_KEY is not set.", file=sys.stderr)
        return 1

    if verbose:
        print(f"  → local: {api_url}")

    project_id = project["id"]
    parent_project = project.get("parent_project")

    if parent_project:
        payload = _build_short_payload(project, project_path, verbose)
    else:
        payload = _build_parent_payload(project, project_path, verbose)

    if verbose:
        kind = "short" if parent_project else "parent video"
        print(f"Pushing {project_id} ({kind}) to {api_url}")

    try:
        r = requests.post(
            f"{api_url}/api/projects/push",
            json=payload,
            headers={"Authorization": f"Bearer {api_key}"},
            timeout=30,
        )
    except requests.exceptions.ConnectionError:
        print(f"✗ Could not connect to {api_url}")
        return 1

    if not r.ok:
        try:
            body = r.json()
        except Exception:
            body = r.text[:500]
        print(f"✗ Server returned {r.status_code}: {body}")
        return 1

    result = r.json()
    server_updated_at = result.get("server_updated_at")

    now_iso = datetime.now(tz=timezone.utc).isoformat(timespec="seconds")
    existing_sync = _read_sync(project_path, prod=False)
    _write_sync(
        project_path,
        {
            **existing_sync,
            "last_pushed_at": now_iso,
            "server_updated_at": server_updated_at,
        },
        prod=False,
    )

    asset = result.get("asset", {})
    if result.get("type") == "short":
        print(f"✓ {project_id} → gn_asset #{asset.get('id')} [{asset.get('status')}]")
        if result.get("task_created"):
            print(f"  task #{result['task_id']} created")
    else:
        print(f"✓ {project_id} → gn_asset #{asset.get('id')} ({asset.get('name')})")
        if verbose:
            script_len = len(asset.get("script") or "")
            print(
                f"  server.script: {script_len} chars | fps={asset.get('fps')} res={asset.get('resolution')}"
            )

    return 0


def _build_parent_payload(project: dict, project_path: Path, verbose: bool) -> dict:
    script_content = None
    manuscript_str = project.get("manuscript")
    if manuscript_str:
        manuscript_path = project_path / manuscript_str
        if manuscript_path.exists():
            try:
                script_content = manuscript_path.read_text(encoding="utf-8")
            except UnicodeDecodeError:
                script_content = manuscript_path.read_text(encoding="latin-1")
                print(f"  Warning: manuscript is not UTF-8, read as latin-1")
            print(f"  manuscript: {len(script_content)} chars")
        else:
            print(f"  Warning: manuscript not found: {manuscript_path}")
    else:
        if verbose:
            print(f"  no manuscript field in project.json")

    return {
        "project_id": project["id"],
        "name": project["name"],
        "description": project.get("description"),
        "coursecode": project.get("coursecode"),
        "script_content": script_content,
        "resolution": project.get("resolution"),
        "fps": project.get("fps"),
        "duration_seconds": project.get("duration_seconds"),
        "hook": project.get("hook"),
        "platform_targets": project.get("platform_targets"),
        "status": project.get("status"),
        "youtube_url": project.get("youtube_url"),
        "shorts": project.get("shorts", []),
    }


def _build_short_payload(project: dict, project_path: Path, verbose: bool) -> dict:
    script_content = None
    script_path_str = project.get("script")
    if script_path_str:
        script_path = project_path / script_path_str
        if script_path.exists():
            script_content = script_path.read_text()
            if verbose:
                print(f"  Read script: {script_path} ({len(script_content)} chars)")
        else:
            print(f"  Warning: script file not found: {script_path}", file=sys.stderr)

    return {
        "project_id": project["id"],
        "name": project["name"],
        "description": project.get("description"),
        "parent_project": project["parent_project"],
        "hook": project.get("hook"),
        "script_content": script_content,
        "platform_targets": project.get("platform_targets", ["youtube"]),
        "resolution": project.get("resolution"),
        "fps": project.get("fps"),
        "duration_seconds": project.get("duration_seconds"),
    }
