"""jsonl I/O 및 회전."""
from __future__ import annotations

import json
import os
from datetime import datetime, timezone
from pathlib import Path
from typing import Callable, List, Optional


def append_jsonl(
    path: Path, row: dict, *, ensure_ascii: bool = True, compact: bool = True
) -> None:
    """jsonl 파일 끝에 한 줄 append. 파일이 없으면 생성."""
    path.parent.mkdir(parents=True, exist_ok=True)
    line = json.dumps(
        row, separators=(",", ":") if compact else None,
        ensure_ascii=ensure_ascii,
    ) + "\n"
    with path.open("a") as f:
        f.write(line)


def rewrite_jsonl(path: Path, rows: List[dict], *, ensure_ascii: bool = True) -> None:
    """JSONL 행 전체를 임시 파일에 쓴 뒤 원자 교체한다."""
    path.parent.mkdir(parents=True, exist_ok=True)
    temporary = path.with_suffix(path.suffix + ".tmp")
    with temporary.open("w", encoding="utf-8") as handle:
        for row in rows:
            handle.write(json.dumps(
                row, separators=(",", ":"), ensure_ascii=ensure_ascii
            ) + "\n")
    os.replace(temporary, path)


def read_jsonl(path: Path) -> List[dict]:
    """jsonl 파일을 모두 읽어 dict 목록으로 반환. 파싱 실패 라인은 스킵."""
    if not path.is_file():
        return []
    rows: List[dict] = []
    with path.open() as f:
        for line in f:
            line = line.strip()
            if not line:
                continue
            try:
                rows.append(json.loads(line))
            except json.JSONDecodeError:
                continue
    return rows


def remove_jsonl_row(path: Path, match: Callable[[dict], bool]) -> Optional[dict]:
    """match 가 True 인 첫 행을 제거하고 반환. 없으면 None.
    임시 파일 + os.replace 로 원자적으로 교체한다.
    """
    if not path.is_file():
        return None
    removed: Optional[dict] = None
    tmp = path.with_suffix(path.suffix + ".tmp")
    with path.open() as src, tmp.open("w") as dst:
        for line in src:
            stripped = line.strip()
            if not stripped:
                continue
            try:
                row = json.loads(stripped)
            except json.JSONDecodeError:
                dst.write(line)
                continue
            if removed is None and match(row):
                removed = row
                continue
            dst.write(line)
    os.replace(tmp, path)
    return removed


def rotate_recent_if_needed(home: Path, max_rows: int = 2000,
                            max_bytes: int = 5 * 1024 * 1024) -> Optional[Path]:
    """recent.jsonl 이 임계를 넘으면 archive/YYYY/YYYY-MM.jsonl 로 이동.
    이동 후 recent.jsonl 을 비운다. 회전이 발생했으면 archive 경로 반환.
    """
    recent = home / "recent.jsonl"
    if not recent.is_file():
        return None
    with recent.open() as f:
        rows = list(f)
    size_ok = recent.stat().st_size < max_bytes
    rows_ok = len(rows) < max_rows
    if size_ok and rows_ok:
        return None
    last_archive: Optional[Path] = None
    for line in rows:
        archive = _archive_path_for_line(home, line)
        archive.parent.mkdir(parents=True, exist_ok=True)
        with archive.open("a") as out:
            out.write(line)
        last_archive = archive
    recent.write_text("")
    return last_archive


def _archive_path_for_line(home: Path, line: str) -> Path:
    """행의 finishedAt 월(YYYY-MM)로 archive/YYYY/YYYY-MM.jsonl 경로 결정. 없으면 현재 월."""
    try:
        finished = json.loads(line).get("finishedAt")
    except json.JSONDecodeError:
        finished = None
    finished = finished or datetime.now(timezone.utc).replace(
        tzinfo=None).strftime("%Y-%m-%dT%H:%M:%SZ")
    return home / "archive" / finished[:4] / f"{finished[:7]}.jsonl"
