"""Pull project metadata from gnommoweb server.

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

For a parent project: updates name, description, and the shorts index
(list of slugs) in project.json.

For a short project: updates title, hook, platform_targets, resolution,
fps, duration_seconds. Preserves local script path reference.

Conflict detection:
  - If local project.json mtime > last_pushed_at → local has unpushed changes
    → warn and abort unless --force

Configuration (from .env or environment):
  GNOMMOWEB_URL      Base URL (e.g. http://localhost:3001)
  GNOMMOWEB_API_KEY  Bearer token (CONTENT_API_KEY)
"""

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 _parse_ts(ts_str) -> datetime | None:
    if not ts_str:
        return None
    try:
        return datetime.fromisoformat(ts_str.replace("Z", "+00:00"))
    except ValueError:
        return None


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

    if prod:
        api_url = os.environ.get("GNOMMOWEB_PROD_URL", "").rstrip("/")
        api_key = os.environ.get("GNOMMOWEB_PROD_API_KEY", "")
        if not api_url:
            print("Error: GNOMMOWEB_PROD_URL is not set.", file=sys.stderr)
            return 1
        if not api_key:
            print("Error: GNOMMOWEB_PROD_API_KEY is not set.", file=sys.stderr)
            return 1
    else:
        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:
        target = "production" if prod else "local"
        print(f"  → {target}: {api_url}")

    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:
        local_project = json.load(f)

    project_id = local_project.get("id")
    if not project_id:
        print("Error: project.json missing 'id'.", file=sys.stderr)
        return 1

    # ── Conflict check ────────────────────────────────────────────────────────
    if not force:
        sync = _read_sync(project_path, prod)
        last_pushed_at = _parse_ts(sync.get("last_pushed_at"))
        local_mtime = datetime.fromtimestamp(
            project_file.stat().st_mtime, tz=timezone.utc
        )
        if last_pushed_at and local_mtime > last_pushed_at:
            print(
                f"⚠  project.json has local changes since last push "
                f"({local_mtime.strftime('%Y-%m-%d %H:%M')} > "
                f"{last_pushed_at.strftime('%Y-%m-%d %H:%M')}).",
                file=sys.stderr,
            )
            print(
                "   Push first with `gnommo push -p` or use `gnommo pull -p --force`.",
                file=sys.stderr,
            )
            return 1

    # ── Fetch from server ─────────────────────────────────────────────────────
    if verbose:
        print(f"Pulling {project_id} from {api_url}…")

    try:
        r = requests.get(
            f"{api_url}/api/projects/{project_id}",
            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:
        if r.status_code == 404:
            print(f"✗ Project '{project_id}' not found on server. Push it first.")
        else:
            try:
                body = r.json()
            except Exception:
                body = r.text[:500]
            print(f"✗ Server returned {r.status_code}: {body}")
        return 1

    server = r.json()
    server_updated_at = server.get("updated_at")
    project_type = server.get("type")

    # ── Merge into project.json ───────────────────────────────────────────────
    if project_type == "parent":
        _merge_parent(local_project, server, verbose)
        count = len(server.get("shorts", []))
        print(f"✓ Pulled {project_id} (parent video) — {count} short(s) in index")
    elif project_type == "short":
        _merge_short(local_project, server, verbose)
        print(f"✓ Pulled {project_id} (short) — [{server.get('status')}]")
    else:
        print(f"Error: unexpected project type: {project_type}", file=sys.stderr)
        return 1

    # ── Write back ────────────────────────────────────────────────────────────
    with open(project_file, "w") as f:
        json.dump(local_project, f, indent=2, ensure_ascii=False)
        f.write("\n")

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

    return 0


def _merge_parent(local: dict, server: dict, verbose: bool):
    """Update parent project.json: name, description, shorts index (slugs)."""
    local["name"] = server.get("title", local.get("name"))
    local["description"] = server.get("description") or local.get("description")
    # shorts is a list of slugs — update from server's shorts list
    server_shorts = server.get("shorts", [])
    local["shorts"] = [s["project_id"] for s in server_shorts]
    if verbose:
        print(f"  shorts index: {local['shorts']}")


def _merge_short(local: dict, server: dict, verbose: bool):
    """Update short project.json: name, hook, platform_targets, resolution, fps, duration."""
    local["name"] = server.get("title", local.get("name"))
    if server.get("hook"):
        local["hook"] = server["hook"]
    if server.get("platform_targets"):
        local["platform_targets"] = server["platform_targets"]
    if server.get("resolution"):
        local["resolution"] = server["resolution"]
    if server.get("fps"):
        local["fps"] = server["fps"]
    if server.get("duration_seconds"):
        local["duration_seconds"] = server["duration_seconds"]
    if server.get("parent_project_id"):
        local["parent_project"] = server["parent_project_id"]
    # Never overwrite local script path — that stays local
