#
#   Imandra Inc.
#
#   codelogician/commands/server/cmd_strategy.py
#

from __future__ import annotations

import json
import urllib.parse
from typing import Any

import httpx
import typer

app = typer.Typer(name='strategy', help='CLI for CodeLogician strategy endpoints.')


# -------------------------
# Helpers
# -------------------------


def _print_json(data: Any) -> None:
    typer.echo(json.dumps(data, indent=2, ensure_ascii=False))


def _handle_http_error(resp: httpx.Response) -> None:
    try:
        payload = resp.json()
    except Exception:
        payload = None

    if isinstance(payload, dict) and 'detail' in payload:
        raise typer.BadParameter(f'HTTP {resp.status_code}: {payload["detail"]}')
    raise typer.BadParameter(f'HTTP {resp.status_code}: {resp.text}')


def _get(
    base_url: str,
    path: str,
    *,
    params: dict[str, Any] | None = None,
    timeout: float,
) -> Any:
    url = base_url.rstrip('/') + path
    with httpx.Client(timeout=timeout) as client:
        resp = client.get(url, params=params)
    if resp.status_code >= 400:
        _handle_http_error(resp)
    return resp.json()


def _post(
    base_url: str,
    path: str,
    *,
    params: dict[str, Any] | None = None,
    json_body: Any | None = None,
    timeout: float,
) -> Any:
    url = base_url.rstrip('/') + path
    with httpx.Client(timeout=timeout) as client:
        resp = client.post(url, params=params, json=json_body)
    if resp.status_code >= 400:
        _handle_http_error(resp)

    ctype = resp.headers.get('content-type', '')
    if 'application/json' in ctype:
        return resp.json()
    return resp.text


def _patch(
    base_url: str,
    path: str,
    *,
    json_body: Any | None = None,
    timeout: float,
) -> Any:
    url = base_url.rstrip('/') + path
    with httpx.Client(timeout=timeout) as client:
        resp = client.patch(url, json=json_body)
    if resp.status_code >= 400:
        _handle_http_error(resp)

    ctype = resp.headers.get('content-type', '')
    if 'application/json' in ctype:
        return resp.json()
    return resp.text


def _cfg(ctx: typer.Context) -> dict[str, Any]:
    root = ctx.find_root()
    assert isinstance(root.obj, dict)
    return root.obj


# -------------------------
# Strategy selection / lifecycle
# -------------------------


@app.command('setcws')
def set_cws(
    ctx: typer.Context,
    strat_id: str = typer.Argument(
        ..., help='Strategy ID to set as current working strategy.'
    ),
) -> None:
    """POST /strategy/setcws/{strat_id}"""
    cfg = _cfg(ctx)
    sid = urllib.parse.quote(strat_id, safe='')
    out = _post(cfg['base_url'], f'/strategy/setcws/{sid}', timeout=cfg['timeout'])
    typer.echo(out if isinstance(out, str) else json.dumps(out))


@app.command('cws')
def get_cws(ctx: typer.Context) -> None:
    """GET /strategy/cws"""
    cfg = _cfg(ctx)
    data = _get(cfg['base_url'], '/strategy/cws', timeout=cfg['timeout'])
    if cfg['json_out']:
        _print_json(data)
    else:
        typer.echo('' if data is None else str(data))


@app.command('list')
def list_strategies(ctx: typer.Context) -> None:
    """GET /strategy/list"""
    cfg = _cfg(ctx)
    data = _get(cfg['base_url'], '/strategy/list', timeout=cfg['timeout'])
    if cfg['json_out']:
        _print_json(data)
        return

    # Expect list[{'strat_id':..., 'path':..., 'type':...}]
    if isinstance(data, list):
        for item in data:
            if isinstance(item, dict):
                typer.echo(
                    f'{item.get("strat_id")}  {item.get("type")}  {item.get("path")}'
                )
            else:
                typer.echo(str(item))
    else:
        typer.echo(str(data))


@app.command('states')
def all_states(ctx: typer.Context) -> None:
    """GET /strategy/states"""
    cfg = _cfg(ctx)
    data = _get(cfg['base_url'], '/strategy/states', timeout=cfg['timeout'])
    _print_json(data) if cfg['json_out'] else typer.echo(str(data))


@app.command('create')
def create_strategy(
    ctx: typer.Context,
    strat_type: str = typer.Option(..., '--type', help='Strategy type (e.g., PyIML).'),
    strat_path: str = typer.Option(
        ..., '--path', help='Directory path for this strategy.'
    ),
) -> None:
    """
    POST /strategy/create?strat_type=...&strat_path=...

    Note: endpoint signature is `create_strategy(strat_type: str, strat_path: str)`
    (no Body annotation), so FastAPI typically treats these as query parameters.
    """
    cfg = _cfg(ctx)
    out = _post(
        cfg['base_url'],
        '/strategy/create',
        params={'strat_type': strat_type, 'strat_path': strat_path},
        timeout=cfg['timeout'],
    )
    _print_json(out) if cfg['json_out'] else typer.echo(str(out))


@app.command('delete')
def delete_strategy(
    ctx: typer.Context,
    strat_id: str = typer.Argument(..., help='Strategy ID to delete.'),
) -> None:
    """POST /strategy/delete/{strat_id}"""
    cfg = _cfg(ctx)
    sid = urllib.parse.quote(strat_id, safe='')
    out = _post(cfg['base_url'], f'/strategy/delete/{sid}', timeout=cfg['timeout'])
    typer.echo(out if isinstance(out, str) else json.dumps(out))


# -------------------------
# Current strategy data
# -------------------------


@app.command('state')
def get_state(ctx: typer.Context) -> None:
    """GET /strategy/state"""
    cfg = _cfg(ctx)
    data = _get(cfg['base_url'], '/strategy/state', timeout=cfg['timeout'])
    _print_json(data) if cfg['json_out'] else typer.echo(str(data))


@app.command('summary')
def get_summary(ctx: typer.Context) -> None:
    """GET /strategy/summary"""
    cfg = _cfg(ctx)
    data = _get(cfg['base_url'], '/strategy/summary', timeout=cfg['timeout'])
    _print_json(data) if cfg['json_out'] else typer.echo(str(data))


@app.command('config')
def get_config(ctx: typer.Context) -> None:
    """GET /strategy/config"""
    cfg = _cfg(ctx)
    data = _get(cfg['base_url'], '/strategy/config', timeout=cfg['timeout'])
    _print_json(data) if cfg['json_out'] else typer.echo(str(data))


@app.command('config-set')
def config_set(
    ctx: typer.Context,
    update_json: str = typer.Option(
        ...,
        '--update-json',
        help='JSON object matching StratConfigUpdate; only provided fields will be set.',
    ),
) -> None:
    """
    PATCH /strategy/config/set

    Body: StratConfigUpdate (partial update model).
    """
    cfg = _cfg(ctx)
    try:
        obj = json.loads(update_json)
    except json.JSONDecodeError as e:
        raise typer.BadParameter(f'--update-json must be valid JSON: {e}') from e

    out = _patch(
        cfg['base_url'],
        '/strategy/config/set',
        json_body=obj,
        timeout=cfg['timeout'],
    )
    typer.echo(out if isinstance(out, str) else json.dumps(out, indent=2))


if __name__ == '__main__':
    app()
