"""Korean operator entrypoint. Default is inspection/plan; mutations need --apply."""
from __future__ import annotations
import argparse
import json
import os
from pathlib import Path
import shlex
import sys
from . import bundle, diagnostics, host, models, release, site as sites, storage
from .core import KitError, approved, require, read_json, sha256, canonical, atomic_write

ROOT=Path(__file__).resolve().parents[1]
GUIDES={'quick':'QUICK_START.ko.md','bootstrap':'HOST_BOOTSTRAP.ko.md','docker-permission':'HOST_BOOTSTRAP.ko.md',
        'storage':'STORAGE.ko.md','offline':'OFFLINE_UPDATE.ko.md','rollback':'ROLLBACK.ko.md',
        'model':'MODEL_TOOLING.ko.md','maintenance':'ROLLBACK.ko.md','errors':'ERROR_CATALOG.ko.md'}


def parser():
    shared=argparse.ArgumentParser(add_help=False)
    shared.add_argument('--site',default=argparse.SUPPRESS,help='HOST site.json 절대경로')
    shared.add_argument('--apply',action='store_true',default=argparse.SUPPRESS,help='계획 확인 후 실제 변경 (sudo 필요)')
    shared.add_argument('--yes',action='store_true',default=argparse.SUPPRESS,help='이미 검토한 계획의 APPLY 입력 생략')
    p=argparse.ArgumentParser(prog='deployctl',description=__doc__,parents=[shared])
    sub=p.add_subparsers(dest='command')
    def cmd(name): return sub.add_parser(name,parents=[shared])
    q=cmd('configure'); q.add_argument('--input',help='미리 검토한 site JSON; 생략하면 한국어 질문'); q.add_argument('--expected')
    q=cmd('bootstrap'); q.add_argument('--package-lock')
    q=cmd('gpu-smoke'); q.add_argument('--image',required=True); q.add_argument('--gpu',type=int,default=0)
    q=cmd('recovery'); ss=q.add_subparsers(dest='action',required=True)
    r=ss.add_parser('pack'); r.add_argument('packages'); r.add_argument('output')
    r=ss.add_parser('install',parents=[shared]); r.add_argument('directory')
    for n in ('status','doctor'): cmd(n)
    q=cmd('guide'); q.add_argument('topic',choices=list(GUIDES))
    for n in ('install','update'):
        q=cmd(n); q.add_argument('archive'); q.add_argument('--sha256',required=True)
    q=cmd('rollback'); q.add_argument('transaction')
    q=cmd('recover'); q.add_argument('--expected')
    q=cmd('collect-support'); q.add_argument('--output',required=True)
    q=cmd('bundle'); ss=q.add_subparsers(dest='action',required=True)
    r=ss.add_parser('create'); r.add_argument('--repo',required=True); r.add_argument('--spec',required=True); r.add_argument('--output',required=True)
    r=ss.add_parser('verify'); r.add_argument('archive'); r.add_argument('--sha256')
    q=cmd('storage'); ss=q.add_subparsers(dest='action',required=True)
    ss.add_parser('show',parents=[shared]); ss.add_parser('prepare',parents=[shared])
    r=ss.add_parser('move',parents=[shared]); r.add_argument('name'); r.add_argument('destination')
    q=cmd('model'); ss=q.add_subparsers(dest='action')
    r=ss.add_parser('profile'); r.add_argument('name',choices=list(models.PROFILES)); r.add_argument('--instances',type=int,default=8); r.add_argument('--batch',type=int,default=4); r.add_argument('--workspace',type=int,default=1024); r.add_argument('--pbtxt',action='store_true')
    for name in ('build','all'):
        r=ss.add_parser(name,parents=[shared]); r.add_argument('name' if name=='build' else 'mapping');
        if name=='build': r.add_argument('--onnx',required=True)
        r.add_argument('--output',required=True); r.add_argument('--image',required=True); r.add_argument('--gpu',type=int,default=0)
        if name=='build':
            r.add_argument('--instances',type=int,default=8); r.add_argument('--batch',type=int,default=4); r.add_argument('--workspace',type=int,default=1024); r.add_argument('--allow-new-onnx',action='store_true')
    r=ss.add_parser('verify',parents=[shared]); r.add_argument('directory'); r.add_argument('--image',required=True); r.add_argument('--gpu',type=int,default=0)
    r=ss.add_parser('install',parents=[shared]); r.add_argument('directory'); r.add_argument('--destination',required=True)
    r=ss.add_parser('mode'); r.add_argument('container')
    r=ss.add_parser('load',parents=[shared]); r.add_argument('container'); r.add_argument('name'); r.add_argument('--maintenance-ack',action='store_true')
    r=ss.add_parser('export',parents=[shared]); r.add_argument('name');
    for option in ('weight','output','image','adapter','adapter-hash'): r.add_argument('--'+option,required=True)
    r=ss.add_parser('manifest'); r.add_argument('directory')
    return p


def emit(value):
    print(canonical(value).decode() if not isinstance(value,str) else value)


def execute(a):
    apply=getattr(a,'apply',False); yes=getattr(a,'yes',False)
    sitepath=getattr(a,'site',os.environ.get('CONTEXTRA_DEPLOY_SITE','/opt/contextra/state/site.json'))
    def getsite(): return sites.load(sitepath)
    def mutation(fn, *args, **kw):
        plan=fn(*args,apply=False,**kw); emit(plan)
        if approved(apply,yes): return fn(*args,apply=True,**kw)
        return None
    if a.command=='guide':
        return (ROOT/'docs/deployment'/GUIDES[a.topic]).read_text()
    if a.command=='configure':
        v=sites.validate(read_json(a.input)) if a.input else sites.wizard()
        emit(sites.show(v))
        path=Path(v['install_root'])/'state/site.json'
        if approved(apply,yes): return sites.save(v,path,expected=a.expected)
        return {'scope':'PLAN_ONLY','save_path':str(path),'next':'같은 configure 명령에 --apply 추가'}
    if a.command=='gpu-smoke': return mutation(models.gpu_smoke,a.image,gpu=a.gpu)
    if a.command=='bootstrap': return mutation(host.bootstrap,a.package_lock)
    if a.command=='recovery':
        return host.recovery_pack(a.packages,a.output) if a.action=='pack' else mutation(host.recovery_install,a.directory)
    if a.command=='bundle':
        if a.action=='create': return bundle.create(a.repo,a.spec,a.output)
        m=bundle.verify(a.archive,expected_hash=a.sha256)
        return {k:m[k] for k in ('release_id','kind','source_sha','compatibility_id')} | {'integrity':'PASS','publisher_identity':'OUT_OF_BAND_SHA_REQUIRED'}
    if a.command=='doctor':
        s=getsite() if Path(sitepath).exists() else None
        result=diagnostics.doctor(s); emit(result)
        require(not any(x.get('status')=='BLOCKED' for x in result['checks']) and result['HOST_READY']!='BLOCKED',
                'DOCTOR_BLOCKED','차단 항목을 해결한 뒤 다시 점검하세요.')
        return None
    if a.command=='status':
        s=getsite(); astate=release.active(s)
        return {'release':astate['release_id'] if astate else None,'project':s['project'],
                'containers':release.containers(s['project']),'history':release.history(s),'external':'NOT_CHECKED'}
    if a.command in ('install','update'): return mutation(release.deploy,sitepath,a.archive,a.sha256)
    if a.command=='rollback': return mutation(release.rollback,sitepath,a.transaction)
    if a.command=='recover': return mutation(release.recover,getsite(),expected=a.expected)
    if a.command=='collect-support': return diagnostics.collect(getsite() if Path(sitepath).exists() else None,a.output)
    if a.command=='storage':
        s=getsite()
        if a.action=='show': return storage.checks(s)
        if a.action=='move':
            require(not apply,'MOVE_MANUAL_REQUIRED','storage move는 안전한 이전 계획만 출력합니다. 일반 update와 분리합니다.')
            return storage.move_plan(s,a.name,a.destination)
        emit(sites.show(s))
        if approved(apply,yes):
            from .core import lock
            with lock(Path(s['install_root'])/'state'):
                storage.prepare(s,bind_views=True)
            return {'storage':'PREPARED','fstab':'NOT_CHANGED','next':'재부팅 후 storage prepare로 검증/재연결'}
        return {'scope':'PLAN_ONLY','mount':'Linux bind view; Docker는 physical source 사용'}
    if a.command=='model':
        if a.action is None:
            return {'profile':'instance8 / batch1,4,4 / FP16 input / FP16 optimized / FP32 output',
                'commands':['profile medium --pbtxt','build medium --onnx ... --output ... --image sha256:...',
                            'all mapping.json --output ... --image sha256:...','verify ...','install ...','mode <container>'],
                'guide':'deployctl guide model'}
        if a.action=='profile':
            v=models.profile(a.name,instances=a.instances,batch=a.batch,workspace=a.workspace)
            return models.pbtxt(v) if a.pbtxt else v
        if a.action=='build': return mutation(models.build,a.name,a.onnx,a.output,a.image,gpu=a.gpu,instances=a.instances,batch=a.batch,workspace=a.workspace,allow_new_onnx=a.allow_new_onnx)
        if a.action=='all': return mutation(models.prepare_all,a.mapping,a.output,a.image,gpu=a.gpu)
        if a.action=='verify': return mutation(models.verify_candidate,a.directory,a.image,gpu=a.gpu)
        if a.action=='install': return mutation(models.install_candidate,a.directory,a.destination)
        if a.action=='mode': return models.control_mode(a.container)
        if a.action=='load':
            require(not apply or a.maintenance_ack,'MAINTENANCE_REQUIRED','live model load는 별도 승인된 정지/유지보수 확인 --maintenance-ack가 필요합니다.')
            return mutation(models.load_model,a.container,a.name)
        if a.action=='export': return mutation(models.export_weight,a.name,a.weight,a.output,a.image,a.adapter,a.adapter_hash)
        if a.action=='manifest':
            m=read_json(Path(a.directory).absolute()/'manifest.json')
            return {k:m.get(k) for k in ('status','profile','onnx_sha256','engine_sha256','image_id','smoke','accuracy','runtime')}
    raise KitError('COMMAND_INVALID','지원하지 않는 명령입니다. deployctl --help를 확인하세요.')


def main(argv=None):
    a=parser().parse_args(argv)
    try:
        if a.command is None:
            if not sys.stdin.isatty(): parser().print_help(); return 0
            print('Contextra 배포 도우미\n1. 호스트 점검  2. 설치 설정  3. 설치/USB 패치 안내\n4. 상태  5. 저장소  6. 모델  7. rollback 안내  8. 장애자료 안내\n0. 종료')
            n=input('번호: ').strip()
            choices={'1':['doctor'],'2':['configure'],'3':['guide','offline'],'4':['status'],
                     '5':['storage','show'],'6':['model'],'7':['guide','rollback'],'8':['guide','errors']}
            if n=='0': return 0
            require(n in choices,'MENU_SELECTION','표시된 번호를 선택하세요.')
            prefix=['--site',a.site] if hasattr(a,'site') else []
            return main(prefix+choices[n])
        result=execute(a)
        if result is not None: emit(result)
        return 0
    except KitError as exc:
        print(exc.explain(),file=sys.stderr); return 2
    except (OSError,ValueError,TypeError,KeyError) as exc:
        print(f'[BLOCKED] INPUT_OR_ENVIRONMENT_ERROR ({type(exc).__name__})\n비밀값이 섞일 수 있어 원문 오류는 출력하지 않습니다. 입력 형식/권한과 ERROR_CATALOG를 확인하세요.',file=sys.stderr)
        return 2
    except (KeyboardInterrupt,EOFError):
        print('[STOPPED] 중단했습니다. apply 도중이었다면 recover로 원장을 먼저 확인하세요.',file=sys.stderr); return 130

if __name__=='__main__': raise SystemExit(main())
