import hashlib
import json
import os
from datetime import datetime
from pathlib import Path
from zoneinfo import ZoneInfo


ROOT = Path(__file__).resolve().parents[1]
os.umask(0o077)
now = datetime.now(ZoneInfo('Asia/Seoul')).isoformat(timespec='seconds')
summary = json.loads((ROOT / 'shared/summary.json').read_text())
before = json.loads((ROOT / 'private/settings_before.json').read_text())


def fingerprint(path):
    return {
        'path': str(path.relative_to(ROOT)),
        'size_bytes': path.stat().st_size,
        'sha256': hashlib.sha256(path.read_bytes()).hexdigest(),
    }


files = sorted(p for p in (ROOT / 'shared').rglob('*')
               if p.is_file() and p.name != 'manifest.json')
manifest = {
    'schema_version': 'codex.artifact-manifest.v1',
    'created_at': now,
    'profile': 'ltb1021',
    'project': 'contextra',
    'task': 'camera24-fight-observe',
    'run_id': ROOT.name,
    'lifecycle': 'durable-large',
    'cleanup_candidate_at': 'manual-review',
    'read_only_observation_seconds': summary['duration_seconds'],
    'observations': summary['metadata_observations'],
    'source_sha256': before['source_sha256'],
    'files': [fingerprint(p) for p in files],
    'restricted_evidence_files': [fingerprint(p) for p in sorted((ROOT / 'private').glob('*')) if p.is_file()],
    'helpers': [fingerprint(p) for p in sorted((ROOT / 'helpers').glob('*')) if p.is_file()],
    'operational_changes': [],
    'limits': summary['limits'],
}
(ROOT / 'shared/manifest.json').write_text(json.dumps(manifest, ensure_ascii=False, indent=2) + '\n')

receipt_root = Path('/tmp/codex/ltb1021') / ROOT.name
receipt_root.mkdir(parents=True, exist_ok=True)
receipt = {
    'schema_version': 'ltb1021.codex_hub.report_receipt.v1',
    'receipt_id': '20261101-camera24-fight-observe-r01',
    'timestamp': now,
    'session_alias': 'work-session',
    'task': 'camera24 FIGHT 실효 설정과 Web Start 처리 대조 및 45초 읽기 전용 관측',
    'results': [
        'Web DB/Start 조립 소스와 AI 설정을 대조: R5, 사람 0.25, 관절 0.30, 외부 cooldown 10초. 구형 동작 필드 무시, R5 블록 생략 시 저장값 유지.',
        '45.074초 452관측의 게시 metadata cache 보존. FIGHT 빨강 표시 28관측은 유지 포함이며 현재-rule 통과율이 아님.',
        '새 FIGHT Event 1건 READY/SENT HTTP200, 기존 썸네일/6.034초 클립 복사 및 기술 표본 확인.',
        str(ROOT / 'shared/00_REPORT_ko.md'),
    ],
    'verify': [
        '설정/run/컨테이너 불변, Frame/PTS 증가, FIGHT state와 빨강 색상 불일치 0, 읽기 오류 0.',
        '아티팩트 복사 SHA256 일치, 원본 packet PTS/DTS 증가 및 source-timebase 전체 디코딩 통과.',
        '저장소 파일/운영 설정/Start/Stop/모델/Pose/Tracking/엔진 변경 없음.',
    ],
    'commits': [],
    'remaining': [
        '브라우저 실제 빨강/알람 팝업과 정오탐은 사용자 확인. HTTP200을 브라우저 표시 완료로 확대하지 않음.',
        '관절 0.30과 오프라인 0.10의 차이, 라벨 겹침은 후속 피드백 항목이며 자동 수정하지 않음.',
        'source anchor 시계 정합, 장기 성능/일반화 검증은 하지 않음.',
    ],
}
receipt_path = receipt_root / 'receipt.json'
receipt_path.write_text(json.dumps(receipt, ensure_ascii=False, indent=2) + '\n')
receipt_path.chmod(0o600)
for p in files:
    p.chmod(0o600)
print(json.dumps({'files': len(files), 'receipt': str(receipt_path), 'manifest': str(ROOT / 'shared/manifest.json')}))
