#!/usr/bin/env python3
"""Candidate-bound, event-driven lifecycle observer for the v8 rehearsal."""
from __future__ import annotations

import argparse
import ctypes
import hashlib
import json
import os
import select
import time
from datetime import UTC, datetime
from pathlib import Path

CANDIDATE = Path('/home/cube/projects/richard/traning coach/.omo/evidence/task26/task26-clarification-root-final-st_01a013d9')
FULL = 'f32a836571b403663da3510d930fd04c1a84c5514cb6345fc575f5a42b580da5'
PRODUCT = '5795a78f7830760b10a4b4d909ca44b9142b92e86083d7d47e9c787f5e580d97'
MANIFEST_SHA = '3639c41eb6e76eb7f8b4ae98fe76a644c2c0574ac6d94bf3c395bf16ec0115f9'
MAX_SECONDS = 6 * 60 * 60
IN_MASK = 0x00000002 | 0x00000008 | 0x00000040 | 0x00000080 | 0x00000100 | 0x00000200 | 0x00000400


def sha(path: Path) -> str:
    return hashlib.sha256(path.read_bytes()).hexdigest()


def append(path: Path, payload: dict[str, object]) -> None:
    raw = (json.dumps(payload, sort_keys=True, separators=(',', ':')) + '\n').encode()
    fd = os.open(path, os.O_WRONLY | os.O_APPEND | os.O_CREAT | os.O_CLOEXEC, 0o600)
    try:
        os.fchmod(fd, 0o600)
        os.write(fd, raw)
        os.fsync(fd)
    finally:
        os.close(fd)


def main() -> int:
    parser = argparse.ArgumentParser()
    parser.add_argument('mode', choices=('dry-run', 'observe'))
    parser.add_argument('--profile', type=Path)
    parser.add_argument('--events', type=Path)
    parser.add_argument('--ready', type=Path)
    args = parser.parse_args()
    manifest = json.loads((CANDIDATE / 'candidate-manifest.json').read_text())
    if sha(CANDIDATE / 'candidate-manifest.json') != MANIFEST_SHA or manifest.get('candidate_digest') != FULL or manifest.get('product_digest') != PRODUCT:
        raise SystemExit('BLOCKED candidate drift')
    contract: dict[str, object] = {
        'schema': 'task26-v8-event-driven-lifecycle-observer-v1',
        'status': 'PASS_DRY_RUN' if args.mode == 'dry-run' else 'ARMED',
        'candidate': FULL,
        'product': PRODUCT,
        'event_driven': True,
        'polling': False,
        'sleeping': False,
        'subscribe_before_snapshot': True,
        'max_seconds': MAX_SECONDS,
        'pid': os.getpid(),
    }
    if args.mode == 'dry-run':
        print(json.dumps(contract, sort_keys=True))
        return 0
    if not args.profile or not args.events or not args.ready:
        raise SystemExit('BLOCKED observe paths required')
    args.events.parent.mkdir(parents=True, exist_ok=True)
    if args.events.exists() or args.ready.exists():
        raise SystemExit('BLOCKED observer receipt paths already exist')
    libc = ctypes.CDLL('libc.so.6', use_errno=True)
    fd = libc.inotify_init1(os.O_CLOEXEC | os.O_NONBLOCK)
    if fd < 0:
        raise SystemExit('BLOCKED inotify init failed')
    watches: dict[int, str] = {}
    for path in (args.profile, args.profile / 'data'):
        wd = libc.inotify_add_watch(fd, os.fsencode(path), IN_MASK)
        if wd < 0:
            os.close(fd)
            raise SystemExit(f'BLOCKED inotify watch failed: {path}')
        watches[wd] = str(path)
    append(args.events, {'event': 'SUBSCRIBED', 'at_utc': datetime.now(UTC).isoformat(), 'candidate': FULL, 'product': PRODUCT, 'pid': os.getpid(), 'watch_roots': sorted(watches.values())})
    args.ready.write_text(json.dumps({**contract, 'watch_roots': sorted(watches.values())}, sort_keys=True) + '\n')
    os.chmod(args.ready, 0o600)
    poller = select.poll()
    poller.register(fd, select.POLLIN)
    deadline = time.monotonic() + MAX_SECONDS
    try:
        while True:
            left = deadline - time.monotonic()
            if left <= 0:
                append(args.events, {'event': 'DEADLINE', 'at_utc': datetime.now(UTC).isoformat()})
                return 2
            if not poller.poll(max(1, int(left * 1000))):
                append(args.events, {'event': 'DEADLINE', 'at_utc': datetime.now(UTC).isoformat()})
                return 2
            raw = os.read(fd, 65536)
            append(args.events, {'event': 'PROFILE_CHANGE', 'at_utc': datetime.now(UTC).isoformat(), 'bytes': len(raw), 'candidate': FULL})
    finally:
        os.close(fd)


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