"""Operator-owned site settings. Secrets stay in separately owned private files."""
from __future__ import annotations
import ipaddress
import os
from pathlib import Path
from . import storage, host
from .core import absolute, relative, identifier, require, read_json, write_json, sha256, lock, KitError

SERVICES = ('postgresql', 'redis', 'analytics', 'ai-control-sidecar', 'ai-gateway')
MODELS = ('RFDETRMedium', 'RFDETRKeypointPreview', 'RFDETRFireSmoke', 'Awiros_person_and_head_detection')


def validate(value):
    storage.validate_map(value)
    require(set(value) <= {'schema','install_root','data_root','storage','ai_ip','project',
            'compose_files','env_files','services','required_models'}, 'SITE_FIELDS', 'site에는 경로/배포 설정만 기록하세요. 인증정보는 private env가 소유합니다.')
    for name,e in value['storage'].items():
        physical=Path(e['physical'])
        for other,v in value['storage'].items():
            logical=Path(v['logical'])
            require(name==other or not (physical.is_relative_to(logical) or logical.is_relative_to(physical)),
                    'STORAGE_ALIAS_OVERLAP','다른 저장소의 통합경로 아래에 실제 저장소를 두지 마세요.')
    identifier(value['project'])
    import re
    require(re.fullmatch(r'[a-z0-9][a-z0-9_-]*',value['project']),'PROJECT_NAME','Compose project는 소문자/숫자/_/-를 사용하세요.')
    require(str(ipaddress.IPv4Address(value['ai_ip'])) == value['ai_ip'], 'AI_IP', 'AI의 IPv4 주소를 확인하세요.')
    require(isinstance(value.get('services'), list) and len(value['services']) == len(set(value['services']))
            and set(value['services']) <= set(SERVICES) and {'analytics','postgresql','redis'} <= set(value['services']),
            'SITE_SERVICES', '기본 3개 서비스와 선택 Sidecar/Gateway만 관리합니다.')
    require(isinstance(value.get('required_models'), list) and value['required_models']
            and len(value['required_models']) == len(set(value['required_models']))
            and set(value['required_models']) <= set(MODELS), 'SITE_MODELS', '활성화할 운영 모델을 명시하세요.')
    for key in ('compose_files','env_files'):
        paths=value.get(key)
        require(isinstance(paths,list) and paths and len(paths) <= 12 and len(paths)==len(set(paths)),
                'SITE_LAYERS', 'env와 Compose 순서를 중복 없이 명시하세요.')
        for path in paths:
            if Path(path).is_absolute():
                p=absolute(path,no_symlinks=True)
                require(not p.is_relative_to(Path(value['install_root'])/'releases'),
                        'PRIVATE_RELEASE_PATH', 'private 파일을 특정 release 안에 저장하지 마세요.')
            else:
                relative(path)
                require(key=='compose_files' and path.startswith('source/'),
                        'SITE_LAYERS', 'env는 호스트 절대경로, 배포 Compose는 source/ 기준 상대경로를 사용하세요.')
    require(value['compose_files'][0]=='source/docker-compose.yml','COMPOSE_BASE', '첫 Compose는 배포물의 기본 docker-compose.yml이어야 합니다.')
    return value


def load(path):
    return validate(read_json(path))


def wizard():
    """Returns a proposal only; no mount, mkdir, network configuration or secret reads."""
    def ask(label, default=''):
        return input(f'{label} [{default}]: ').strip() or default
    install=str(absolute(ask('HOST 설치 root', '/opt/contextra'),no_symlinks=True))
    data=str(absolute(ask('HOST 통합 데이터 root', '/data/contextra'),no_symlinks=True))
    print('현재 NIC/IP 후보:',host.addresses())
    ip=ask('Web에서 접근할 AI IPv4 주소 (직접 확인)')
    require(ip, 'AI_IP', 'IP를 임의로 선택하지 않았습니다.')
    entries={}
    for name in storage.LAYOUT:
        default=(f'/training/contextra/{name}' if name=='models' else
                 f'/backup/contextra/{name}' if name in ('artifacts','debug','offline') else f'{data}/{name}')
        physical=ask(f'{name}: HOST 실제 저장 경로',default)
        info=storage.mount_info(physical)
        print('실제 filesystem:',info)
        allow=False
        if info['target']=='/':
            allow=ask('시스템 SSD 사용을 의도했으면 ROOT 입력')=='ROOT'
        entries[name]=storage.capture(name,physical,data,allow_root=allow)
    project=ask('고정 Compose project 이름 (업데이트에도 동일)', 'contextra')
    envs=ask('기존 private env 절대경로들 (쉼표 구분, 적용 순서대로)').split(',')
    overlays=ask('private overlay와 추가 source/deploy/*.yml (쉼표 구분, 순서대로)').split(',')
    services=ask('관리 서비스 (쉼표 구분)', ','.join(SERVICES)).split(',')
    models=ask('활성 모델 (쉼표 구분; 모두 자동 로드하지 않음)', 'RFDETRMedium').split(',')
    return validate(dict(schema=1,install_root=install,data_root=data,storage=entries,ai_ip=ip,project=project,
        env_files=[x.strip() for x in envs if x.strip()],compose_files=['source/docker-compose.yml']+
        [x.strip() for x in overlays if x.strip()],services=[x.strip() for x in services],required_models=[x.strip() for x in models]))


def save(value, path, *, expected=None):
    validate(value)
    p=absolute(path,no_symlinks=True)
    require(p == Path(value['install_root'])/'state/site.json', 'SITE_PATH', 'site 설정은 선택한 INSTALL_ROOT/state/site.json에 저장하세요.')
    storage.checks(value)
    with lock(Path(value['install_root'])/'state'):
        require(not (p.exists() and expected is None), 'STATE_CHANGED', '기존 설정 변경에는 --expected 기존파일SHA256이 필요합니다.')
        write_json(p,value,new=not p.exists(),expected=expected)
    return {'site_file':str(p),'sha256':sha256(p),'mounts_changed':False,'services_changed':False}


def show(value):
    return {'project':value['project'],'install_root':value['install_root'],'data_root':value['data_root'],
            'ai_ip':value['ai_ip'],'paths':[dict(name=k,**{s:e[s] for s in ('physical','logical','container')})
                                       for k,e in value['storage'].items()]}
