"""E:/proj의 공개용 코드·자료만 별도 묶음으로 복사. 원본 실행/수정은 하지 않는다."""
import argparse
import csv
import hashlib
import json
import re
from collections import Counter
from datetime import datetime, timezone
from pathlib import Path

DENIED = {'.git', '.venv', '__pycache__', 'cache', '.pytest_cache', 'artifacts', 'vendor'}
SOURCE_EXTENSIONS = {'.py', '.html', '.css', '.js'}
SECRET_PATTERNS = [
    re.compile(r'-----BEGIN (?:RSA |EC |OPENSSH )?PRIVATE KEY-----'),
    re.compile(r'\b(?:sk-|gw_)[A-Za-z0-9_\-]{25,}'),
    re.compile(r'''(?i)(?:api[_-]?key|password|passwd|secret[_-]?key|access[_-]?token)\s*[=:]\s*["']([^"'\n]{8,})["']'''),
]


def candidates(root):
    chosen = [(p, 'code') for p in root.glob('*.py')]
    chosen += [(p, 'map' if p.suffix == '.html' else 'image' if p.suffix == '.png' else 'csv' if p.suffix == '.csv' else 'text')
               for p in (root / 'output').iterdir() if p.is_file() and p.suffix in {'.html', '.png', '.csv', '.txt'}]
    chosen += [(p, 'csv' if p.suffix == '.csv' else 'text') for p in (root / 'data').iterdir() if p.is_file() and p.suffix in {'.csv', '.geojson'}]
    for folder in ('startup_app', 'tests', 'tools'):
        chosen += [(p, 'code') for p in (root / 'cc' / folder).rglob('*') if p.is_file() and p.suffix in SOURCE_EXTENSIONS and not DENIED.intersection(p.relative_to(root).parts)]
    chosen += [(root / 'cc' / name, 'text') for name in ('requirements.txt', 'requirements-dev.txt')]
    def order(item):
        parts = item[0].relative_to(root).parts
        group = 0 if len(parts) == 1 else {'output': 1, 'data': 2, 'cc': 3}.get(parts[0], 4)
        return group, item[0].as_posix()
    return sorted(chosen, key=order)


def publish(root, output):
    root, output = root.resolve(), output.resolve()
    selected = candidates(root)
    entries, contents = [], []
    for source, kind in selected:
        if source.is_symlink() or not source.resolve().is_relative_to(root):
            raise ValueError(f'허용되지 않은 경로: {source.name}')
        raw = source.read_bytes()
        if kind != 'image':
            text = raw.decode('utf-8-sig')
            if any(pattern.search(text) for pattern in SECRET_PATTERNS):
                # 값은 출력하지 않는다. 검토 후 별도로 공개해야 한다.
                raise ValueError(f'자격증명 가능성이 있어 공개 중단: {source.relative_to(root)}')
        relative = source.relative_to(root).as_posix()
        digest = hashlib.sha256(raw).hexdigest()
        entry = {'id': hashlib.sha256((relative + '\0' + digest).encode()).hexdigest()[:20], 'path': relative, 'kind': kind, 'size': len(raw), 'sha256': digest}
        if kind == 'csv':
            with source.open(encoding='utf-8-sig', newline='') as stream:
                reader = csv.reader(stream)
                entry['columns'] = len(next(reader, []))
                entry['rows'] = sum(1 for _ in reader)
        entries.append(entry)
        contents.append(raw)
    for entry, raw in zip(entries, contents):
        destination = output / 'files' / entry['path']
        destination.parent.mkdir(parents=True, exist_ok=True)
        destination.write_bytes(raw)
    manifest = {'created': datetime.now(timezone.utc).isoformat(), 'files': entries}
    output.mkdir(parents=True, exist_ok=True)
    temporary = output / 'manifest.next.json'
    temporary.write_text(json.dumps(manifest, ensure_ascii=False, indent=2), encoding='utf-8')
    temporary.replace(output / 'manifest.json')
    return {'count': len(entries), 'types': dict(Counter(e['kind'] for e in entries)), 'bytes': sum(e['size'] for e in entries)}


if __name__ == '__main__':
    parser = argparse.ArgumentParser()
    parser.add_argument('--root', type=Path, default=Path('E:/proj'))
    parser.add_argument('--output', type=Path, default=Path(__file__).resolve().parents[1] / 'public_materials')
    args = parser.parse_args()
    print(json.dumps(publish(args.root, args.output), ensure_ascii=False))
