#!/usr/bin/env python3
"""Create an isolated Logits Codex profile; never change the user's default config."""

import argparse
import hashlib
import json
import os
from pathlib import Path
import shutil
import stat
import subprocess
import sys
import tempfile
import urllib.parse
import urllib.request


DEFAULT_BINARY = '/Applications/ChatGPT.app/Contents/Resources/codex-cli/CodexCLI.app/Contents/MacOS/codex'
MODELS = ('deepseek/deepseek-v4.1-flash', 'logits/deepseek-v4.1-flash-abliterated', 'moonshotai/kimi-k3.1-pro')


class NoRedirect(urllib.request.HTTPRedirectHandler):
    def redirect_request(self, request, fp, code, msg, headers, target):
        return None


def digest(data):
    return hashlib.sha256(data).hexdigest()


def validate_catalog(raw, model, effort):
    listing = json.loads(raw)
    if not isinstance(listing, dict) or not isinstance(listing.get('models'), list):
        raise ValueError('Endpoint does not expose the native Codex catalog yet; no profile was created.')
    entries = [entry for entry in listing['models'] if isinstance(entry, dict) and entry.get('slug') == model]
    if len(entries) != 1:
        raise ValueError('Requested model is not uniquely enabled in the native Codex catalog.')
    entry = entries[0]
    required = {'display_name', 'supported_reasoning_levels', 'shell_type', 'visibility',
                'supported_in_api', 'priority', 'base_instructions', 'default_reasoning_summary',
                'support_verbosity', 'default_verbosity', 'apply_patch_tool_type',
                'truncation_policy', 'context_window', 'effective_context_window_percent',
                'input_modalities', 'use_responses_lite', 'experimental_supported_tools'}
    if not required.issubset(entry):
        raise ValueError('Native model metadata is incomplete for the supported Codex profile.')
    if (entry['shell_type'] != 'unified_exec' or entry['apply_patch_tool_type'] != 'freeform'
            or entry['use_responses_lite'] is not False or entry['base_instructions'] != ''
            or set(entry['input_modalities']) != {'text', 'image'}
            or entry['experimental_supported_tools'] != []
            or 'tool_mode' in entry or 'multi_agent_version' in entry
            or type(entry['context_window']) is not int or not 0 < entry['context_window'] <= 32000):
        raise ValueError('Native catalog capabilities do not match this reviewed coding profile.')
    levels = entry['supported_reasoning_levels']
    if not isinstance(levels, list) or not any(isinstance(level, dict) and level.get('effort') == effort for level in levels):
        raise ValueError('Requested reasoning effort is not advertised for this model.')
    return entry


def extract_instructions(binary):
    with tempfile.TemporaryDirectory(prefix='logits-codex-extract-') as fresh:
        env = dict(os.environ, CODEX_HOME=fresh)
        version = subprocess.run([str(binary), '--version'], check=True, capture_output=True,
                                 text=True, timeout=15, env=env).stdout.strip()
        result = subprocess.run([str(binary), 'debug', 'models'], check=True, capture_output=True,
                                timeout=30, env=env)
    catalog = json.loads(result.stdout)
    for entry in catalog.get('models', []):
        instructions = entry.get('base_instructions')
        if isinstance(instructions, str) and len(instructions.strip()) >= 1000:
            return instructions.encode('utf-8'), version, entry.get('slug')
    raise ValueError('Installed Codex did not expose complete bundled base instructions; refusing an empty override.')


def write_private(path, data):
    temporary = path.with_name('.' + path.name + '.tmp')
    descriptor = os.open(temporary, os.O_WRONLY | os.O_CREAT | os.O_EXCL, 0o600)
    with os.fdopen(descriptor, 'wb') as stream:
        stream.write(data)
        stream.flush()
        os.fsync(stream.fileno())
    os.replace(temporary, path)


def create_profile(args):
    binary = Path(args.codex_binary).expanduser().resolve(strict=True)
    key = Path(args.api_key_file).expanduser()
    if key.is_symlink():
        raise ValueError('Use a regular private API key file, not a symlink.')
    key = key.resolve(strict=True)
    info = key.stat()
    if not stat.S_ISREG(info.st_mode) or info.st_mode & 0o077:
        raise ValueError('API key file must be a regular file readable only by its owner (chmod 600).')
    output = Path(args.output_dir).expanduser().absolute()
    if output.exists() or output.is_symlink():
        raise ValueError('Output directory already exists; choose a new profile directory to refresh safely.')
    target = urllib.parse.urlsplit(args.base_url)
    if (target.scheme not in ('https', 'http') or not target.hostname or target.username
            or target.password or target.query or target.fragment
            or target.scheme == 'http' and target.hostname not in ('127.0.0.1', 'localhost', '::1')):
        raise ValueError('Base URL must be HTTPS, or explicit HTTP loopback for local qualification.')
    base_url = args.base_url.rstrip('/')
    instructions, version, instruction_model = extract_instructions(binary)
    request = urllib.request.Request(base_url + '/models', headers={
        'X-Gateway-Client': 'codex', 'Accept': 'application/json'})
    opener = urllib.request.build_opener(NoRedirect())
    with opener.open(request, timeout=20) as response:
        if response.headers.get_content_type() != 'application/json':
            raise ValueError('Model catalog returned a non-JSON response.')
        raw = response.read(2_000_001)
    if len(raw) > 2_000_000:
        raise ValueError('Model catalog exceeds the metadata size limit.')
    entry = validate_catalog(raw, args.model, args.reasoning_effort)
    context = entry['context_window']
    threshold = min(24000, context * 3 // 4)
    # JSON basic strings are also valid TOML basic strings for these paths/IDs.
    quote = lambda value: json.dumps(value, ensure_ascii=False)
    config = '\n'.join([
        'model = ' + quote(args.model), 'model_provider = "logits"',
        'model_reasoning_effort = ' + quote(args.reasoning_effort),
        'model_context_window = ' + str(context),
        'model_auto_compact_token_limit = ' + str(threshold),
        'model_instructions_file = ' + quote(str(output / 'instructions.txt')),
        'model_catalog_json = ' + quote(str(output / 'models.json')),
        'web_search = "disabled"', '', '[features]',
        'apps = false', 'plugins = false', 'multi_agent = false', '',
        '[model_providers.logits]', 'name = "Logits"',
        'base_url = ' + quote(base_url), 'wire_api = "responses"',
        'supports_websockets = false', 'request_max_retries = 0', 'stream_max_retries = 0',
        'http_headers = { "X-Gateway-Client" = "codex" }', '',
        '[model_providers.logits.auth]', 'command = "/bin/cat"',
        'args = [' + quote(str(key)) + ']', 'timeout_ms = 5000', '',
    ]).encode()
    manifest = {'format': 'logits-codex-profile-v1', 'model': args.model,
                'reasoning_effort': args.reasoning_effort, 'base_url': base_url,
                'codex_binary': str(binary), 'codex_version': version,
                'instruction_source_model': instruction_model,
                'instructions_sha256': digest(instructions), 'catalog_sha256': digest(raw),
                'config_sha256': digest(config), 'credential_read': False,
                'inference_calls': 0}
    output.parent.mkdir(parents=True, exist_ok=True, mode=0o700)
    output.mkdir(mode=0o700)
    try:
        for name, data in [('instructions.txt', instructions), ('models.json', raw),
                           ('config.toml', config),
                           ('setup.json', (json.dumps(manifest, indent=2) + '\n').encode())]:
            write_private(output / name, data)
    except BaseException:
        shutil.rmtree(output)
        raise
    return {'profile': str(output), **manifest}


def main():
    parser = argparse.ArgumentParser(description=__doc__)
    parser.add_argument('--codex-binary', default=DEFAULT_BINARY)
    parser.add_argument('--api-key-file', default='~/.config/logits-codex/api-key')
    parser.add_argument('--output-dir', required=True)
    parser.add_argument('--model', choices=MODELS, default=MODELS[0])
    parser.add_argument('--reasoning-effort', choices=('none', 'minimal', 'low', 'medium', 'high', 'xhigh', 'max'), default='low')
    parser.add_argument('--base-url', default='https://api.logits.com/v1')
    args = parser.parse_args()
    try:
        result = create_profile(args)
    except (OSError, ValueError, subprocess.SubprocessError) as error:
        # Exceptions contain paths/protocol errors, never the key contents.
        print('Setup failed: ' + str(error), file=sys.stderr)
        return 1
    print(json.dumps(result, indent=2))
    return 0


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