"""Single-process teaching lab. Run this file from the Cursor terminal."""
import json
from pathlib import Path

from tinydb import Query, TinyDB

BASE = Path(__file__).resolve().parent
FIELDS = {"job_id", "title", "owner", "status"}
STATUSES = {"대기", "완료"}


def validate_rows(rows):
    if not isinstance(rows, list):
        raise ValueError("샘플은 작업 목록이어야 합니다.")
    seen = set()
    for row in rows:
        if not isinstance(row, dict) or set(row) != FIELDS:
            raise ValueError("작업에는 job_id, title, owner, status가 필요합니다.")
        if any(not isinstance(v, str) or not v.strip() or v != v.strip() for v in row.values()):
            raise ValueError("각 항목은 앞뒤 공백 없는 비어 있지 않은 문자열이어야 합니다.")
        if row["status"] not in STATUSES:
            raise ValueError("상태는 대기 또는 완료여야 합니다.")
        if row["job_id"] in seen:
            raise ValueError("작업 ID가 중복되어 있습니다.")
        seen.add(row["job_id"])


def open_db(base=BASE):
    path = base / "data" / "db.json"
    if path.exists():
        # Validate before TinyDB opens the file: never silently reset corrupt data.
        raw = json.loads(path.read_text(encoding="utf-8"))
        if not isinstance(raw, dict) or set(raw) - {"_default"}:
            raise ValueError("TinyDB 저장 구조가 아닙니다. 기존 파일을 보존합니다.")
        table = raw.get("_default", {})
        if not isinstance(table, dict) or any(
            not key.isascii() or not key.isdecimal() or int(key) < 1 or str(int(key)) != key
            for key in table
        ):
            raise ValueError("TinyDB 문서 번호가 올바르지 않습니다.")
        validate_rows(list(table.values()))
        rows = None
    else:
        rows = json.loads((base / "sample.json").read_text(encoding="utf-8"))
        validate_rows(rows)
        path.parent.mkdir(parents=True, exist_ok=True)
    db = TinyDB(path, encoding="utf-8", ensure_ascii=False, indent=2)
    try:
        if rows is not None:
            db.insert_multiple(rows)
        return db
    except Exception:
        db.close()
        raise


def add_job(db, job_id, title, owner, status):
    row = {"job_id": job_id.strip(), "title": title.strip(), "owner": owner.strip(), "status": status.strip()}
    validate_rows([row])
    # This is an application rule, not a database UNIQUE constraint.
    if db.contains(Query().job_id == row["job_id"]):
        raise ValueError("이미 있는 작업 ID입니다. 다른 ID를 입력하세요.")
    db.insert(row)


def update_status(db, job_id, status):
    if status not in STATUSES:
        raise ValueError("상태는 대기 또는 완료여야 합니다.")
    if not db.update({"status": status}, Query().job_id == job_id):
        raise ValueError("해당 작업 ID가 없습니다.")


def show(rows):
    if not rows:
        print("결과가 없습니다.")
    for row in rows:
        print(f"{row['job_id']} | {row['title']} | {row['owner']} | {row['status']}")


def menu(db):
    while True:
        print("\n1 전체 목록 · 2 담당자 검색 · 3 작업 추가 · 4 상태 수정 · 5 작업 삭제 · 0 종료")
        choice = input("선택: ").strip()
        try:
            if choice == "0":
                return
            if choice == "1":
                show(db.all())
            elif choice == "2":
                owner = input("담당자 (예: mina): ").strip()
                if not owner:
                    raise ValueError("담당자를 입력하세요.")
                show(db.search(Query().owner == owner))
            elif choice == "3":
                add_job(db, input("작업 ID: "), input("작업명: "), input("담당자: "), input("상태 (대기/완료): "))
                print("추가했습니다. Cursor에서 data/db.json을 확인하세요.")
            elif choice == "4":
                update_status(db, input("작업 ID: ").strip(), input("새 상태 (대기/완료): ").strip())
                print("상태를 수정했습니다.")
            elif choice == "5":
                job_id = input("삭제할 작업 ID: ").strip()
                row = db.get(Query().job_id == job_id)
                if row is None:
                    raise ValueError("해당 작업 ID가 없습니다.")
                show([row])
                if input("이 작업을 삭제할까요? 삭제하려면 yes 입력: ").strip() == "yes":
                    db.remove(Query().job_id == job_id)
                    print("삭제했습니다.")
                else:
                    print("삭제를 취소했습니다.")
            else:
                print("메뉴 번호 0~5를 입력하세요.")
        except ValueError as error:
            print(f"입력 확인: {error}")


def main():
    print("작업 목록 관리 · TinyDB 실습\n프로그램은 하나만 실행하세요. 실행 중 DB 파일은 직접 수정하지 마세요.")
    try:
        with open_db() as db:
            print(f"저장 위치: {BASE / 'data' / 'db.json'}")
            menu(db)
    except (EOFError, KeyboardInterrupt):
        print("\n종료합니다. 저장한 변경은 남아 있습니다.")
    except (OSError, ValueError) as error:
        print(f"파일 읽기·저장 오류: {error}\n자동 초기화하지 않습니다. 프로그램을 종료하고 파일 경로·권한·JSON 내용을 확인하세요.")
        return 1
    return 0


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