#!/usr/bin/env python3
"""Create an explicitly private center or standalone bundle; Python 3.11+.

The source is this public package, not a user profile directory. The only user
files consumed are the explicit profile/catalog and optionally referenced SSH
private-key files. Source files are never changed and secrets are never printed.
"""
import argparse
import json
import os
from pathlib import Path
import tomllib
from urllib.parse import urlsplit

from private_bundle import verified_members, write_zip


def bounded(path):
    if not path.is_file() or path.stat().st_size > 16 * 1024 * 1024:
        raise ValueError('credential input must be a regular file smaller than 16 MiB')
    return path.read_bytes()


def scalar(value):
    if isinstance(value, (str, bool, int, float)):
        return json.dumps(value, ensure_ascii=False, allow_nan=False)
    if isinstance(value, list):
        return '[' + ', '.join(scalar(item) for item in value) + ']'
    raise ValueError('unsupported catalog value type; only SSHMCP server fields are supported')


def standalone_entries(catalog_path, include_keys):
    from private_bundle import digest
    catalog = tomllib.loads(bounded(catalog_path).decode('utf-8-sig'))
    if set(catalog) != {'ssh_servers'} or not isinstance(catalog['ssh_servers'], dict):
        raise ValueError('catalog must contain an ssh_servers table only')
    entries = {}
    lines = ['[ssh_servers]', '']
    for name, fields in catalog['ssh_servers'].items():
        if not isinstance(fields, dict):
            raise ValueError('server entry must be a table')
        fields = fields.copy()
        aliases = [alias for alias in ['key_path', 'keypath', 'ssh_key'] if fields.get(alias)]
        if include_keys and aliases:
            if len({fields[alias] for alias in aliases}) != 1:
                raise ValueError('server has conflicting key path aliases')
            path = Path(fields[aliases[0]]).expanduser()
            if not path.is_absolute():
                raise ValueError('explicit key inclusion requires absolute or home-relative key paths')
            content = bounded(path)
            if b'PRIVATE KEY' not in content:
                raise ValueError('referenced key input is not a supported private-key file')
            relative = 'keys/' + digest(content) + '.key'
            entries['private/' + relative] = content
            for alias in aliases:
                fields.pop(alias)
            fields['key_path'] = '__SSHMCP_PRIVATE_DIR__/' + relative
        lines.append('[ssh_servers.' + json.dumps(name, ensure_ascii=False) + ']')
        lines.extend(json.dumps(key) + ' = ' + scalar(value) for key, value in fields.items())
        lines.append('')
    encoded = ('\n'.join(lines) + '\n').encode()
    tomllib.loads(encoded.decode())
    entries['private/ssh-config.toml'] = encoded
    return entries


def main():
    parser = argparse.ArgumentParser(description=__doc__)
    parser.add_argument('--mode', choices=['center', 'standalone'], required=True)
    parser.add_argument('--profile', type=Path, help='Center connection.json containing endpoint and API key')
    parser.add_argument('--catalog', type=Path, help='Explicit standalone ssh-config.toml')
    parser.add_argument('--include-ssh-keys', action='store_true', help='Explicitly include all key files referenced by the standalone catalog')
    parser.add_argument('--output', type=Path, required=True)
    args = parser.parse_args()
    os.umask(0o077)
    root = Path(__file__).resolve().parents[1]
    entries = verified_members(root)
    if any(name.startswith('private/') for name in entries):
        raise ValueError('start from the public package, not an already private bundle')
    if args.mode == 'center':
        if args.profile is None or args.catalog is not None or args.include_ssh_keys:
            raise ValueError('center mode requires --profile only')
        profile = json.loads(bounded(args.profile))
        endpoint, key = profile.get('endpoint'), profile.get('api_key')
        if not isinstance(endpoint, str) or not isinstance(key, str) or not key.strip():
            raise ValueError('invalid center connection profile')
        url = urlsplit(endpoint)
        if url.scheme != 'https' and not (url.scheme == 'http' and url.hostname in ['127.0.0.1', 'localhost', '::1']):
            raise ValueError('center endpoint requires HTTPS; HTTP is limited to loopback')
        if not url.hostname or url.username or url.password or url.query or url.fragment or url.path not in ['', '/']:
            raise ValueError('endpoint must not embed credentials, query or fragment')
        entries['private/ENDPOINT'] = (endpoint + '\n').encode()
        entries['private/API_KEY'] = (key.strip() + '\n').encode()
    else:
        if args.catalog is None or args.profile is not None:
            raise ValueError('standalone mode requires --catalog, without --profile')
        entries.update(standalone_entries(args.catalog, args.include_ssh_keys))
    entries['PUBLIC_START_HERE.md'] = entries['START_HERE.md']
    entries['START_HERE.md'] = entries['docs/PRIVATE_START_HERE.md'].replace(b'../', b'')
    entries['AGENTS.md'] = b'This is a credential-bearing private SSHMCP bundle. Read START_HERE.md, ask the installer for installation mode and client selection before changes, and inspect only PRIVATE_SETUP.json metadata. Never print or upload private/ files. A mode change must skip incompatible private data.\n'
    entries['PRIVATE_SETUP.json'] = json.dumps({'schema': 'sshmcp-private-bundle-v1', 'prepared_mode': args.mode,
        'ssh_key_files_included': args.include_ssh_keys, 'private': True}, indent=2).encode()
    digest = write_zip(args.output.absolute(), entries)
    print(json.dumps({'success': True, 'private': True, 'mode': args.mode, 'path': str(args.output.absolute()), 'sha256': digest}))


if __name__ == '__main__':
    main()
