"""Explicit, bounded source work records. No shell history or credentials collected."""
import difflib
import hashlib
import json
import os
from pathlib import Path
import re
import shutil
import tarfile
import time
import server_context as server

STATE = Path.home() / ".local/state/contextra-work"
SUFFIXES = {".py", ".md", ".txt", ".json", ".sh", ".cpp", ".h", ".toml"}


def inventory(root):
    result = {}
    size = 0
    for path in sorted(root.rglob("*")):
        if path.is_symlink():
            raise ValueError("Symlink in source tree")
        relative = path.relative_to(root)
        if any(part in {".git", "__pycache__", ".venv", "node_modules"} for part in relative.parts):
            continue
        if not path.is_file() or path.suffix not in SUFFIXES:
            continue
        if re.search(r"password|token|credential|\.env|private[-_]?key", path.name, re.I):
            continue
        size += path.stat().st_size
        if len(result) >= 6000 or path.stat().st_size > 2*1024**2 or size > 100*1024**2:
            raise ValueError("Work record bound exceeded")
        result[str(relative)] = hashlib.sha256(path.read_bytes()).hexdigest()
    return result


def active():
    name = (STATE / "active").read_text().strip()
    if not re.fullmatch(r"[0-9]{8}T[0-9]{6}-[0-9]+", name):
        raise ValueError("Invalid work record")
    return STATE / name


def begin(source):
    source = Path(source).resolve(strict=True)
    if not source.is_relative_to(server.path('source','/workspace/contextra/source')):
        raise ValueError("Use the approved Contextra source directory")
    STATE.mkdir(parents=True, exist_ok=True, mode=0o700)
    if (STATE / "active").exists():
        raise ValueError("Finish the current work record first")
    name = time.strftime("%Y%m%dT%H%M%S") + "-" + str(os.getpid())
    root = STATE / name
    root.mkdir(mode=0o700)
    files = inventory(source)
    for relative in files:
        target = root / "before" / relative
        target.parent.mkdir(parents=True, exist_ok=True)
        shutil.copyfile(source / relative, target)
    if inventory(root / "before") != files or inventory(source) != files:
        raise ValueError("Source changed during snapshot; repeat after edits stop")
    (root / "baseline.json").write_text(json.dumps(dict(source=str(source), files=files, started=time.time()), indent=2))
    (root / "notes.jsonl").touch(mode=0o600)
    (STATE / "active").write_text(name)
    print("작업 기록 시작:", root)


def note(text):
    if len(text) > 4000:
        raise ValueError("Note too long")
    with (active() / "notes.jsonl").open("a") as stream:
        stream.write(json.dumps(dict(time=time.time(), note=text), ensure_ascii=False) + "\n")
    print("기록했습니다. 비밀번호나 token은 메모하지 마세요.")


def end():
    root = active()
    baseline = json.loads((root / "baseline.json").read_text())
    source = Path(baseline["source"])
    after = inventory(source)
    changed = [p for p in sorted(set(after) | set(baseline["files"])) if after.get(p) != baseline["files"].get(p)]
    diff = []
    for relative in changed:
        before = root / "before" / relative
        current = source / relative
        old = before.read_text().splitlines(keepends=True) if before.exists() else []
        new = current.read_text().splitlines(keepends=True) if relative in after else []
        diff.extend(difflib.unified_diff(old, new, fromfile="before/"+relative, tofile="after/"+relative))
        if relative in after:
            target = root / "after" / relative
            target.parent.mkdir(parents=True, exist_ok=True)
            shutil.copyfile(current, target)
            if hashlib.sha256(target.read_bytes()).hexdigest() != after[relative]:
                raise ValueError("Source changed during work end; active record preserved")
    if inventory(source) != after:
        raise ValueError("Source changed during work end; active record preserved")
    (root / "changes.diff").write_text("".join(diff))
    result = dict(ended=time.time(), source=str(source), changed=changed, files=after,
                  commands_captured=False, export_review_required=True)
    (root / "result.json").write_text(json.dumps(result, indent=2))
    (root / "SUMMARY.ko.md").write_text("# 현장 작업 기록\n\n변경 이유·실행 명령·검증 결과·남은 문제를 기재하세요.\n\n"
        + "\n".join("- " + p for p in changed) + "\n")
    (STATE / "active").unlink()
    print("작업 기록 종료:", root, "변경 파일", len(changed))
    print("SUMMARY.ko.md와 diff를 확인한 뒤 USB로 가져오세요. 자동 비밀정보 제거를 보장하지 않습니다.")


def export(name, reviewed=False):
    if not reviewed or not re.fullmatch(r"[0-9]{8}T[0-9]{6}-[0-9]+", name):
        raise ValueError("Review summary/diff and pass --reviewed")
    root = STATE / name
    if not (root / "result.json").is_file():
        raise ValueError("Work record is incomplete")
    target = STATE / (name + ".tar.gz")
    if target.exists():
        raise ValueError("Existing export is preserved")
    with tarfile.open(target, "w:gz") as archive:
        for path in sorted(root.rglob("*")):
            if path.is_symlink():
                raise ValueError("Symlink refused")
            if path.is_file() and "before" not in path.relative_to(root).parts:
                archive.add(path, arcname=str(Path(name) / path.relative_to(root)), recursive=False)
    target.chmod(0o600)
    print(target)
    with target.open("rb") as stream:
        print("SHA256", hashlib.file_digest(stream, "sha256").hexdigest())
