"""Flora canvas sync — mirror generation outputs to Flora canvas for review.

Usage:
    python3 -m recoil.pipeline.tools.flora_canvas_sync \
        --project tartarus --episode 1 --shot-id EP001_SH05A \
        --video-path projects/tartarus/output/video/EP001_SH05A_T3.mp4

Or programmatically:
    from recoil.pipeline.tools.flora_canvas_sync import attach_video_to_canvas
    result = attach_video_to_canvas(video_path_bytes, file_name, shot_id)
"""

from __future__ import annotations

import argparse
import json
import logging
import os
import sys
from pathlib import Path
from typing import Optional

import requests

from recoil.execution.providers.flora_projects import resolve_flora_project

logger = logging.getLogger(__name__)

_BASE = "https://app.flora.ai/api/v1"

# Phase 9 decision (orchestrator default, JT may override): review-worthy only —
# gate-failed takes NOT attached.

# Phase 9 decision (orchestrator default, JT may override): every successful take
# attaches a node; replace-node dedup deferred (needs node-ID registry).


def _headers() -> dict:
    """Return base headers for Flora API requests.

    User-Agent is MANDATORY — Vercel WAF (fronting Flora) returns fake HTTP 401
    'Invalid API key' when the UA is Python-urllib/3.X or absent.
    See: feedback-vercel-waf-blocks-python-urllib-ua.md
    """
    key = os.environ.get("FLORA_API_KEY", "")
    return {
        "Authorization": f"Bearer {key}",
        "Content-Type": "application/json",
        "User-Agent": "recoil-pipeline/1.0",
    }


def _upload_video_signed_url(
    workspace_id: str,
    project_id: str,
    file_bytes: bytes,
    file_name: str,
    content_type: str = "video/mp4",
    folder: str = "recoil-renders",
) -> Optional[str]:
    """Upload video bytes to Flora via the proven signed-url flow.

    Flow:
      1. POST /assets  (source=signed-url, workspace_id, content_type, file_name, folder)
         → signed upload URL + asset_id
      2. Multipart PUT of file bytes to the signed upload URL
      3. POST /assets/{id}/complete
      4. POST /projects/{id}/assets/{id}/attach  → canvas node
      5. Return asset_id (or None on any failure)

    This mirrors _upload_local_refs in recoil/execution/providers/flora.py exactly.
    The helper's original source_url reserve schema is NOT used — it is unverified.
    """
    h = _headers()

    # Step 1: reserve signed upload slot
    reserve_body = {
        "source": "signed-url",
        "workspace_id": workspace_id,
        "content_type": content_type,
        "file_name": file_name,
        "folder": folder,
    }
    try:
        reserve = requests.post(
            f"{_BASE}/assets",
            headers=h,
            json=reserve_body,
            timeout=30,
        )
    except Exception as e:
        logger.warning("[flora-sync] asset reserve request failed: %s", e)
        return None

    if reserve.status_code not in (200, 201):
        logger.warning(
            "[flora-sync] asset reserve failed: %s %s",
            reserve.status_code,
            reserve.text[:200],
        )
        return None

    try:
        data = reserve.json()
    except Exception as e:
        logger.warning("[flora-sync] asset reserve response parse failed: %s", e)
        return None

    asset_id = data.get("asset_id", "")
    upload_info = data.get("upload", {})
    upload_endpoint = upload_info.get("url", "")
    upload_method = upload_info.get("method", "PUT").upper()
    file_field = upload_info.get("file_field", "file")
    form_fields = upload_info.get("form_fields", {})

    if not asset_id or not upload_endpoint:
        logger.warning("[flora-sync] asset reserve missing asset_id or upload url: %s", data)
        return None

    # Step 2: multipart upload to the signed URL
    boundary = "----RecoilUploadBoundary"
    body_parts: list[bytes] = []
    for k, v in form_fields.items():
        body_parts.append(
            f'--{boundary}\r\nContent-Disposition: form-data; name="{k}"\r\n\r\n{v}\r\n'.encode()
        )
    body_parts.append(
        f'--{boundary}\r\nContent-Disposition: form-data; name="{file_field}"; filename="{file_name}"\r\nContent-Type: {content_type}\r\n\r\n'.encode()
        + file_bytes
        + f"\r\n--{boundary}--\r\n".encode()
    )
    multipart_body = b"".join(body_parts)

    try:
        upload_resp = requests.request(
            method=upload_method,
            url=upload_endpoint,
            data=multipart_body,
            headers={
                "Content-Type": f"multipart/form-data; boundary={boundary}",
                "User-Agent": "recoil-pipeline/1.0",
            },
            timeout=120,
        )
    except Exception as e:
        logger.warning("[flora-sync] file upload request failed: %s", e)
        return None

    if upload_resp.status_code not in (200, 201, 204):
        logger.warning(
            "[flora-sync] file upload failed: %s %s",
            upload_resp.status_code,
            upload_resp.text[:200],
        )
        return None

    # Step 3: complete the upload
    try:
        complete = requests.post(
            f"{_BASE}/assets/{asset_id}/complete",
            headers=h,
            timeout=30,
        )
    except Exception as e:
        logger.warning("[flora-sync] asset complete request failed: %s", e)
        return None

    if complete.status_code not in (200, 201):
        logger.warning(
            "[flora-sync] asset complete failed: %s %s",
            complete.status_code,
            complete.text[:200],
        )
        # Non-fatal — some Flora implementations accept the asset even if complete returns 4xx.
        # Fall through to attach attempt.

    # Step 4: attach to canvas
    try:
        attach = requests.post(
            f"{_BASE}/projects/{project_id}/assets/{asset_id}/attach",
            headers=h,
            timeout=30,
        )
    except Exception as e:
        logger.warning("[flora-sync] canvas attach request failed: %s", e)
        return None

    if attach.status_code not in (200, 201):
        logger.warning(
            "[flora-sync] canvas attach failed: %s %s",
            attach.status_code,
            attach.text[:200],
        )
        return None

    return asset_id


def attach_video_to_canvas(
    video_bytes: bytes,
    file_name: str,
    shot_id: str,
    workspace_id: Optional[str] = None,
    project_id: Optional[str] = None,
    project: Optional[str] = None,
    episode: Optional[int] = None,
) -> dict:
    """Attach video bytes to the Flora canvas via the proven signed-url flow.

    This is the PRIMARY entry point for Phase 9 canvas attach.
    Uses signed-url upload (mirrors flora.py _upload_local_refs).

    Returns dict with asset_id and success status.
    """
    ws = workspace_id or os.environ.get("RECOIL_FLORA_WORKSPACE", "")
    if project_id:
        prj = project_id
    elif project and episode is not None:
        try:
            prj = resolve_flora_project(
                project,
                episode,
                api_key=os.environ.get("FLORA_API_KEY", ""),
                workspace_id=ws,
                create=False,
            )
        except RuntimeError:
            prj = ""
    else:
        prj = os.environ.get("RECOIL_FLORA_PROJECT", "")

    if not ws or not prj:
        logger.debug(
            "[flora-sync] skipping canvas attach for %s — "
            "RECOIL_FLORA_WORKSPACE or RECOIL_FLORA_PROJECT not configured",
            shot_id,
        )
        return {"success": False, "error": "RECOIL_FLORA_WORKSPACE and RECOIL_FLORA_PROJECT required"}

    asset_id = _upload_video_signed_url(
        workspace_id=ws,
        project_id=prj,
        file_bytes=video_bytes,
        file_name=file_name,
    )
    if not asset_id:
        return {"success": False, "error": "signed-url upload+attach failed", "shot_id": shot_id}

    return {
        "success": True,
        "asset_id": asset_id,
        "shot_id": shot_id,
        "flora_project_id": prj,
    }


# ---------------------------------------------------------------------------
# Legacy helpers (retained for CLI + any existing callers)
# NOTE: upload_asset below is REPLACED by the signed-url flow above.
# The original source_url reserve schema is unverified — do not use it for
# new callers. Use attach_video_to_canvas() instead.
# ---------------------------------------------------------------------------

def upload_asset(
    workspace_id: str,
    file_url: str,
    name: str,
    content_type: str = "video/mp4",
) -> Optional[str]:
    """[LEGACY] Upload a file to Flora via signed-URL flow. Returns asset_id or None.

    DEPRECATED for new callers — use attach_video_to_canvas() which mirrors the
    proven signed-url flow from flora.py. This legacy form used a source_url
    reserve schema that is unverified.
    """
    h = _headers()
    reserve = requests.post(
        f"{_BASE}/assets",
        headers=h,
        json={
            "workspace_id": workspace_id,
            "name": name,
            "content_type": content_type,
            "source_url": file_url,
        },
        timeout=30,
    )
    if reserve.status_code not in (200, 201):
        print(f"[flora-sync] asset reserve failed: {reserve.status_code} {reserve.text}", file=sys.stderr)
        return None

    data = reserve.json()
    asset_id = data.get("asset_id")
    if not asset_id:
        print(f"[flora-sync] no asset_id in response: {data}", file=sys.stderr)
        return None

    complete = requests.post(
        f"{_BASE}/assets/{asset_id}/complete",
        headers=h,
        timeout=30,
    )
    if complete.status_code not in (200, 201):
        print(f"[flora-sync] asset complete failed: {complete.status_code}", file=sys.stderr)

    return asset_id


def attach_to_canvas(
    project_id: str,
    asset_id: str,
) -> Optional[str]:
    """[LEGACY] Attach an asset to a project canvas. Returns node_id or None."""
    h = _headers()
    resp = requests.post(
        f"{_BASE}/projects/{project_id}/assets/{asset_id}/attach",
        headers=h,
        timeout=30,
    )
    if resp.status_code not in (200, 201):
        print(f"[flora-sync] attach failed: {resp.status_code} {resp.text}", file=sys.stderr)
        return None
    return resp.json().get("node_id")


def sync_shot_to_canvas(
    shot_id: str,
    video_url: str,
    ref_urls: Optional[list[str]] = None,
    label: Optional[str] = None,
    workspace_id: Optional[str] = None,
    project_id: Optional[str] = None,
) -> dict:
    """[LEGACY] Sync a single shot output to the Flora canvas.

    Returns dict with asset_id, node_id, and success status.
    NOTE: This uses the legacy upload_asset (source_url schema).
    For new production callers use attach_video_to_canvas().
    """
    ws = workspace_id or os.environ.get("RECOIL_FLORA_WORKSPACE", "")
    prj = project_id or os.environ.get("RECOIL_FLORA_PROJECT", "")

    if not ws or not prj:
        return {"success": False, "error": "RECOIL_FLORA_WORKSPACE and RECOIL_FLORA_PROJECT required"}

    asset_name = f"{label or shot_id}.mp4"
    asset_id = upload_asset(ws, video_url, asset_name)
    if not asset_id:
        return {"success": False, "error": "asset upload failed"}

    node_id = attach_to_canvas(prj, asset_id)
    if not node_id:
        return {"success": False, "error": "canvas attach failed", "asset_id": asset_id}

    return {
        "success": True,
        "asset_id": asset_id,
        "node_id": node_id,
        "shot_id": shot_id,
        "flora_project_id": prj,
    }


def main():
    parser = argparse.ArgumentParser(description="Sync shot output to Flora canvas")
    parser.add_argument("--project", default=None)
    parser.add_argument("--episode", type=int, default=None)
    parser.add_argument("--shot-id", required=True)
    parser.add_argument("--video-path", required=True, help="Local path of the generated video")
    parser.add_argument("--workspace-id", default=os.environ.get("RECOIL_FLORA_WORKSPACE"))
    parser.add_argument("--project-id", default=None)
    parser.add_argument("--label", default=None)
    args = parser.parse_args()

    video_path = Path(args.video_path)
    if not video_path.exists():
        print(f"[flora-sync] video file not found: {video_path}", file=sys.stderr)
        sys.exit(1)

    video_bytes = video_path.read_bytes()
    file_name = f"{args.label or args.shot_id}.mp4"

    result = attach_video_to_canvas(
        video_bytes=video_bytes,
        file_name=file_name,
        shot_id=args.shot_id,
        workspace_id=args.workspace_id,
        project_id=args.project_id,
        project=args.project,
        episode=args.episode,
    )
    print(json.dumps(result, indent=2))
    sys.exit(0 if result["success"] else 1)


if __name__ == "__main__":
    main()
