#!/usr/bin/env python3
"""Dependency-free APIMART discovery CLI and verified Seedance client helpers."""
from __future__ import annotations
import argparse,json,os,pathlib,sys,urllib.error,urllib.parse,urllib.request
ROOT=pathlib.Path(__file__).resolve().parent
REGISTRY=json.loads((ROOT/'apimart_registry.json').read_text())
AGENT_CATALOG=json.loads((ROOT/'agent_catalog.json').read_text())
MODEL='seedance-2.5'; BASE_URL='https://api.apimart.ai'; DISCOVERY_ALIASES={'seedance-2.5':'seedance-2-5'}
class ApiMartError(RuntimeError): pass

def discovery_info(model:str)->dict:
 try:return AGENT_CATALOG['models'][DISCOVERY_ALIASES.get(model,model)]
 except KeyError as exc:raise ApiMartError(f'Unknown model: {model}') from exc

def model_info(model:str=MODEL)->dict:
 discovery=discovery_info(model); execution_id=discovery.get('execution_model_id'); verified=REGISTRY['models'].get(execution_id or model)
 return {'model':model,**discovery,'execution':verified,'execution_status':'verified' if verified else 'discovery_only'}

def list_models()->dict:
 items=[{'model':slug,'display_name':info['display_name'],'model_url':info['model_url'],'verified_execution':info['verified_execution']} for slug,info in sorted(AGENT_CATALOG['models'].items())]
 return {'items':items,**AGENT_CATALOG['summary'],'generated_at':AGENT_CATALOG['generated_at']}

def find_guides(model:str,intent:str|None=None)->dict:
 info=discovery_info(model); guides=[g for g in info['guides'] if intent is None or g['intent']==intent]
 return {'model':model,'intent':intent,'count':len(guides),'guides':guides}

def estimate_cost(resolution:str,output_seconds:int,input_video_seconds:float=0)->dict:
 info=REGISTRY['models'][MODEL]
 if resolution not in info['resolutions']:raise ApiMartError(f"resolution must be one of: {', '.join(info['resolutions'])}")
 if output_seconds==-1:billable_output,estimate_kind=info['duration']['max'],'maximum_hold'
 elif info['duration']['min']<=output_seconds<=info['duration']['max']:billable_output,estimate_kind=output_seconds,'estimated_final'
 else:raise ApiMartError('duration must be 4-30 seconds, or -1 for auto')
 if input_video_seconds<0:raise ApiMartError('input video seconds must be non-negative')
 rates=info['pricing_usd_per_second'][resolution]; output_cost=billable_output*rates['output']; input_cost=input_video_seconds*rates['input']
 return {'model':MODEL,'resolution':resolution,'estimate_kind':estimate_kind,'billable_output_seconds':billable_output,'input_video_seconds':input_video_seconds,'output_cost_usd':round(output_cost,6),'input_cost_usd':round(input_cost,6),'estimated_total_usd':round(output_cost+input_cost,6),'pricing_observed_at':REGISTRY['observed_at'],'pricing_source':REGISTRY['source']}

def _request(method:str,path:str,api_key:str,payload:dict|None=None)->dict:
 if not api_key:raise ApiMartError('Set APIMART_API_KEY for live requests')
 data=json.dumps(payload).encode() if payload is not None else None
 req=urllib.request.Request(BASE_URL+path,data=data,method=method,headers={'Authorization':f'Bearer {api_key}','Content-Type':'application/json'})
 try:
  with urllib.request.urlopen(req,timeout=90) as response:return json.loads(response.read())
 except urllib.error.HTTPError as exc:raise ApiMartError(f"HTTP {exc.code}: {exc.read().decode(errors='replace')}") from exc

def build_generate_payload(args)->dict:
 info=REGISTRY['models'][MODEL]
 if args.resolution not in info['resolutions']:raise ApiMartError(f"resolution must be one of: {', '.join(info['resolutions'])}")
 if args.size not in info['sizes']:raise ApiMartError(f"size must be one of: {', '.join(info['sizes'])}")
 if args.duration!=-1 and not info['duration']['min']<=args.duration<=info['duration']['max']:raise ApiMartError('duration must be 4-30 seconds, or -1 for auto')
 if len(args.image_url)>30 or len(args.video_url)>10 or len(args.audio_url)>10:raise ApiMartError('reference maximums are 30 images, 10 videos and 10 audios')
 payload={'model':MODEL,'prompt':args.prompt,'resolution':args.resolution,'size':args.size,'duration':args.duration,'output_format':args.output_format,'generate_audio':not args.silent,'nsfw_check':args.moderate}
 for field,values in (('image_urls',args.image_url),('video_urls',args.video_url),('audio_urls',args.audio_url)):
  if values:payload[field]=values
 return payload

def generate(payload:dict,api_key:str)->dict:return _request('POST','/v1/videos/generations',api_key,payload)
def task_status(task_id:str,api_key:str,language:str='en')->dict:return _request('GET',f"/v1/tasks/{urllib.parse.quote(task_id,safe='')}?language={urllib.parse.quote(language)}",api_key)

def parser()->argparse.ArgumentParser:
 p=argparse.ArgumentParser(prog='apimart',description='Discover APIMART models and call verified model APIs'); sub=p.add_subparsers(dest='command',required=True)
 sub.add_parser('models',help='list all discoverable models and execution status')
 info=sub.add_parser('model',help='show model hub, evidence guides and verified execution metadata'); info.add_argument('model',nargs='?',default=MODEL)
 guides=sub.add_parser('guides',help='find pricing, limits or commercial-policy evidence guides'); guides.add_argument('model'); guides.add_argument('--intent',choices=['pricing','limits','commercial_policy'])
 est=sub.add_parser('estimate',help='estimate verified Seedance 2.5 video cost'); est.add_argument('--resolution',choices=REGISTRY['models'][MODEL]['resolutions'],default='720p'); est.add_argument('--duration',type=int,default=5); est.add_argument('--input-video-seconds',type=float,default=0)
 gen=sub.add_parser('generate',help='submit a verified Seedance 2.5 task'); gen.add_argument('--prompt',required=True); gen.add_argument('--resolution',default='720p'); gen.add_argument('--size',default='adaptive'); gen.add_argument('--duration',type=int,default=5); gen.add_argument('--output-format',choices=['mp4','mov'],default='mp4'); gen.add_argument('--image-url',action='append',default=[]); gen.add_argument('--video-url',action='append',default=[]); gen.add_argument('--audio-url',action='append',default=[]); gen.add_argument('--silent',action='store_true'); gen.add_argument('--moderate',action='store_true'); gen.add_argument('--execute',action='store_true')
 status=sub.add_parser('status',help='query an asynchronous task'); status.add_argument('task_id'); status.add_argument('--language',default='en'); return p

def main(argv:list[str]|None=None)->int:
 try:
  args=parser().parse_args(argv)
  if args.command=='models':result=list_models()
  elif args.command=='model':result=model_info(args.model)
  elif args.command=='guides':result=find_guides(args.model,args.intent)
  elif args.command=='estimate':result=estimate_cost(args.resolution,args.duration,args.input_video_seconds)
  elif args.command=='generate':
   payload=build_generate_payload(args); result=generate(payload,os.getenv('APIMART_API_KEY','')) if args.execute else {'dry_run':True,'request':{'method':'POST','url':BASE_URL+'/v1/videos/generations','json':payload},'cost':estimate_cost(args.resolution,args.duration),'next':'Add --execute with APIMART_API_KEY to submit a paid task'}
  else:result=task_status(args.task_id,os.getenv('APIMART_API_KEY',''),args.language)
  print(json.dumps(result,ensure_ascii=False,indent=2,sort_keys=True)); return 0
 except ApiMartError as exc:print(json.dumps({'error':str(exc)},ensure_ascii=False),file=sys.stderr); return 2
if __name__=='__main__':raise SystemExit(main())
