"""`agnes store {upload,update,delete,mine}` — Flea Market creator-side ops.

For browsing and installing marketplace items use ``agnes marketplace``.
These commands cover the creator workflow: publish, update, remove, and
download your own entries. All commands authenticate via the configured PAT
(see ``cli auth``); endpoints are gated by ``get_current_user`` server-side.
"""

from __future__ import annotations

import json
from pathlib import Path
from typing import Optional

import typer

from cli.v2_client import (
    V2ClientError,
    api_delete,
    api_get_stream,
    api_post_multipart,
    api_put_multipart,
)

store_app = typer.Typer(help="Flea Market — upload and manage your own skills/agents/plugins")



@store_app.command("upload")
def upload_entity(
    type: str = typer.Argument(..., help="skill | agent | plugin"),
    zip_path: Path = typer.Argument(..., exists=True, dir_okay=False, readable=True),
    name: Optional[str] = typer.Option(None, "--name"),
    description: Optional[str] = typer.Option(None, "--description"),
    category: Optional[str] = typer.Option(None, "--category"),
    video_url: Optional[str] = typer.Option(None, "--video-url"),
):
    """Upload a Flea Market entity from a local ZIP file."""
    files = {
        "file": (zip_path.name, zip_path.read_bytes(), "application/zip"),
    }
    data: dict = {"type": type}
    if name:
        data["name"] = name
    if description:
        data["description"] = description
    if category:
        data["category"] = category
    if video_url:
        data["video_url"] = video_url
    try:
        body = api_post_multipart("/api/store/entities", files=files, data=data)
    except V2ClientError as e:
        typer.echo(str(e), err=True)
        raise typer.Exit(1)
    typer.echo(
        f"Uploaded: id={body['id']} name={body['name']} "
        f"invocation={body['invocation_name']} version={body['version']}"
    )


@store_app.command("delete")
def delete_entity(
    entity_id: str = typer.Argument(...),
    yes: bool = typer.Option(False, "--yes", "-y", help="Skip confirmation"),
):
    """Delete a Flea Market entity (owner or admin only)."""
    if not yes:
        confirm = typer.confirm(f"Delete entity {entity_id}?")
        if not confirm:
            raise typer.Abort()
    try:
        api_delete(f"/api/store/entities/{entity_id}")
    except V2ClientError as e:
        typer.echo(str(e), err=True)
        raise typer.Exit(1)
    typer.echo(f"Deleted: {entity_id}")


@store_app.command("update")
def update_entity(
    entity_id: str = typer.Argument(...),
    description: Optional[str] = typer.Option(None, "--description"),
    category: Optional[str] = typer.Option(None, "--category"),
    video_url: Optional[str] = typer.Option(None, "--video-url"),
    photo: Optional[Path] = typer.Option(
        None, "--photo", exists=True, dir_okay=False, readable=True,
        help="Replace the entity's photo with this image file",
    ),
    zip_path: Optional[Path] = typer.Option(
        None, "--zip", exists=True, dir_okay=False, readable=True,
        help="Replace the plugin tree with this new ZIP",
    ),
):
    """In-place edit a Flea Market entity. Owner or admin only.

    Server-side authorization (PUT /api/store/entities/{id}) admits the
    owner OR any member of the Admin group; CLI doesn't enforce, the
    server does. Pass any combination of --description / --category /
    --video-url / --photo / --zip; omitted fields are left untouched
    (note: an empty string clears nothing — there's no API affordance to
    clear a field back to NULL via PUT today).
    """
    files: dict = {}
    data: dict = {}
    if zip_path:
        files["file"] = (zip_path.name, zip_path.read_bytes(), "application/zip")
    if photo:
        files["photo"] = (photo.name, photo.read_bytes(), f"image/{photo.suffix.lstrip('.')}")
    if description is not None:
        data["description"] = description
    if category is not None:
        data["category"] = category
    if video_url is not None:
        data["video_url"] = video_url
    if not files and not data:
        typer.echo("Nothing to update — pass at least one of --description / --category / --video-url / --photo / --zip.", err=True)
        raise typer.Exit(2)
    try:
        body = api_put_multipart(
            f"/api/store/entities/{entity_id}",
            files=files or None, data=data,
        )
    except V2ClientError as e:
        typer.echo(str(e), err=True)
        raise typer.Exit(1)
    typer.echo(
        f"Updated: id={body['id']} version={body['version']}"
    )


# ---------------------------------------------------------------------------
# `agnes store mine` — bundle of the caller's OWN entities (creator scope).
#
# Whole-Store bulk reads (`pull` / `info`) live under `agnes admin store`
# because operationally they're backup tooling for operators. This stays
# in user namespace because every authenticated user is allowed to grab
# a backup of their own creations (offline archive, leaving the org,
# moving to another instance).
# ---------------------------------------------------------------------------


@store_app.command("mine")
def pull_my_entities(
    out: Path = typer.Option(
        Path("my-store-entities.zip"), "-o", "--out",
        help="Where to save the ZIP (default: ./my-store-entities.zip)",
    ),
    unpack: Optional[Path] = typer.Option(
        None, "--unpack",
        help="Instead of saving the ZIP, unpack it into this directory.",
    ),
):
    """Download a bundle of every Flea Market entity you own (created).

    Server-side this is the same ``GET /api/store/bundle.zip`` endpoint
    that `agnes admin store pull` uses, scoped to the caller via
    ``?owner=me`` (the server resolves the magic value to your user_id).
    """
    import shutil as _shutil
    import tempfile as _tempfile
    import zipfile as _zipfile

    if unpack:
        scratch = Path(_tempfile.mkdtemp(prefix="agnes_store_mine_"))
        zip_path = scratch / "bundle.zip"
        try:
            try:
                api_get_stream("/api/store/bundle.zip", str(zip_path), owner="me")
            except V2ClientError as e:
                typer.echo(str(e), err=True)
                raise typer.Exit(1)
            if unpack.exists():
                _shutil.rmtree(unpack)
            unpack.mkdir(parents=True, exist_ok=True)
            with _zipfile.ZipFile(zip_path, "r") as zf:
                zf.extractall(unpack)
        finally:
            _shutil.rmtree(scratch, ignore_errors=True)
        typer.echo(f"Unpacked your Store entities → {unpack}")
        return

    out.parent.mkdir(parents=True, exist_ok=True)
    try:
        size = api_get_stream("/api/store/bundle.zip", str(out), owner="me")
    except V2ClientError as e:
        typer.echo(str(e), err=True)
        raise typer.Exit(1)
    typer.echo(f"Wrote {size:,} bytes → {out}")
