"""Root-owned local installation identity, separate from editable environment profiles."""
import hashlib
import json
import os
from pathlib import Path
import pwd
import re

CONFIG = Path('/etc/contextra-maintenance/server.json')
ROLES = ('analytics', 'ai-control-sidecar', 'ai-gateway', 'redis', 'postgresql')


def safe(path):
    p = Path(path)
    if not p.is_absolute() or '..' in p.parts or any(x.is_symlink() for x in (p, *p.parents)):
        raise ValueError('Absolute non-symlink installation path required')
    return p


def machine():
    return hashlib.sha256(Path('/etc/machine-id').read_bytes().strip()).hexdigest()


def validate(d):
    required = {'version','server','machine','operator','backend','project','services','paths',
                'compose_files','env_files','working_dir','disks','capabilities','ssh_port','firewall'}
    if not isinstance(d, dict) or set(d) != required or d['version'] != 1:
        raise ValueError('Unsupported server definition')
    for key in ('server','project','operator'):
        if not isinstance(d[key], str) or not re.fullmatch(r'[a-zA-Z0-9][a-zA-Z0-9_.-]{0,63}', d[key]):
            raise ValueError('Invalid server identity: '+key)
    pwd.getpwnam(d['operator'])
    if d['backend'] not in ('compose','transaction') or set(d['services']) != set(ROLES):
        raise ValueError('Unsupported backend/service contract')
    if len(set(d['services'].values())) != len(ROLES):
        raise ValueError('Duplicate service roles')
    for value in d['services'].values():
        if not re.fullmatch(r'[a-zA-Z0-9][a-zA-Z0-9_.-]{0,63}', value):
            raise ValueError('Invalid Compose service')
    if set(d['paths']) != {'setup','state','runtime','data','source','tools'}:
        raise ValueError('Installation paths incomplete')
    for p in (*d['paths'].values(), d['working_dir'], *d['compose_files'], *d['env_files']):
        safe(p)
    if type(d['ssh_port']) is not int or not 1 <= d['ssh_port'] <= 65535:
        raise ValueError('Invalid SSH port')
    if not isinstance(d['capabilities'], list) or set(d['capabilities']) - {'usb','resources','vscode','network','patch','llm','gui','access','observe'}:
        raise ValueError('Unknown capability')
    for disk in d['disks']:
        if set(disk) != {'path','device','mountpoint'}:
            raise ValueError('Invalid disk contract')
        safe(disk['path']); safe(disk['mountpoint'])
    if d['firewall'] is not None:
        if set(d['firewall']) != {'script','chain','unit'}:
            raise ValueError('Invalid firewall contract')
        safe(d['firewall']['script'])
        if not re.fullmatch(r'[A-Z0-9_-]{1,28}', d['firewall']['chain']):
            raise ValueError('Invalid owned firewall chain')
    return d


def load():
    if not CONFIG.exists():
        return None  # Existing field installations keep their original backend until explicitly migrated.
    safe(CONFIG)
    for p in (CONFIG, *CONFIG.parents):
        if p.stat().st_uid != 0 or p.stat().st_mode & 0o022:
            raise ValueError('Server definition and parents must be root-owned, not writable by others')
    d = validate(json.loads(CONFIG.read_text()))
    if d['machine'] != machine():
        raise ValueError('This server definition belongs to another machine; no action taken')
    return d


# Repository imports/tests never inherit a host installation. Installed launchers have an inventory.
CURRENT = load() if (Path(__file__).parent/'installed-files.json').is_file() else None


def path(name, legacy):
    return Path(CURRENT['paths'][name]) if CURRENT else Path(legacy)


def operator():
    return CURRENT['operator'] if CURRENT else 'si'


def project():
    return CURRENT['project'] if CURRENT else 'contextra-icerti'


def portable():
    return bool(CURRENT and CURRENT['backend'] == 'compose')


def service(role):
    return CURRENT['services'][role] if CURRENT else role


def require(capability):
    if CURRENT and capability not in CURRENT['capabilities']:
        raise ValueError('Capability not enabled for this server: '+capability)


def banner():
    if CURRENT:
        print('Server:', CURRENT['server'], 'backend:', CURRENT['backend'],
              'project:', project(), 'operator:', operator(), 'LOCAL ONLY')
        print('Protected installation definition:', CONFIG)


def guard_disks():
    for disk in (CURRENT or {}).get('disks', []):
        p = safe(disk['path']); mount = safe(disk['mountpoint'])
        if not p.exists() or p.stat().st_dev != disk['device'] or not os.path.ismount(mount):
            raise ValueError('Required data filesystem missing/changed: '+str(p))


def verify_tools():
    if not CURRENT:return
    root=path('tools','');inventory=root/'installed-files.json'
    spec=json.loads(safe(inventory).read_text())
    for name,expected in spec.items():
        relative=Path(name)
        if relative.is_absolute() or '..' in relative.parts:raise ValueError('Invalid installed tool inventory')
        target=safe(root/name)
        if target.stat().st_uid!=0 or target.stat().st_mode & 0o022 or hashlib.sha256(target.read_bytes()).hexdigest()!=expected:
            raise ValueError('Tool code changed: '+name+'; apply a reviewed ct patch, not a manual SHA edit')


def logical(doc):
    """Translate physical Compose service names only at the backend boundary."""
    import copy
    result = copy.deepcopy(doc)
    mapping = {service(r): r for r in ROLES}
    if set(result['services']) != set(mapping):
        raise ValueError('Compose project contains unexpected services')
    result['services'] = {mapping[k]: v for k, v in result['services'].items()}
    for cfg in result['services'].values():
        if 'depends_on' in cfg:
            cfg['depends_on'] = {mapping[k]: v for k, v in cfg['depends_on'].items()}
    return result


def physical(doc):
    import copy
    result = copy.deepcopy(doc)
    result['services'] = {service(k): v for k, v in result['services'].items()}
    for cfg in result['services'].values():
        if 'depends_on' in cfg:
            cfg['depends_on'] = {service(k): v for k, v in cfg['depends_on'].items()}
    return result
