#!/usr/bin/env python3
# -*- coding: utf-8 -*-
"""photosort - 按拍摄日期整理照片

把散落在多个目录里的照片整理成  输出目录/YYYY/MM/IMG_YYYYMMDD_HHMMSS.ext
优先读取 EXIF 拍摄时间，读取不到时回退到文件修改时间。

用法:
    python photosort.py 源目录 -o 输出目录 [--dry-run] [--move]
示例:
    python photosort.py ~/Pictures/DCIM -o ~/Pictures/归档 --dry-run
    python photosort.py ~/Pictures/DCIM -o ~/Pictures/归档 --move
"""
import argparse
import os
import re
import shutil
import sys
from datetime import datetime

IMAGE_EXTS = {".jpg", ".jpeg", ".png", ".heic", ".dng", ".cr2", ".nef", ".arw"}

try:
    from PIL import Image
    from PIL.ExifTags import TAGS
    HAVE_PIL = True
except ImportError:
    HAVE_PIL = False


def exif_datetime(path):
    """返回 (datetime, 来源) 或 (None, 原因)。"""
    if not HAVE_PIL:
        return None, "no-PIL"
    try:
        img = Image.open(path)
        exif = img.getexif()
        if not exif:
            return None, "no-exif"
        for tag_id, value in exif.items():
            if TAGS.get(tag_id) == "DateTimeOriginal" and isinstance(value, str):
                dt = datetime.strptime(value, "%Y:%m:%d %H:%M:%S")
                return dt, "exif"
        return None, "no-datetime"
    except Exception as e:
        return None, f"err:{e}"


def safe_name(dt, path, used):
    stem, _ = os.path.splitext(os.path.basename(path))
    base = dt.strftime("IMG_%Y%m%d_%H%M%S")
    candidate = base
    n = 2
    while candidate in used:
        candidate = f"{base}_{n}"
        n += 1
    used.add(candidate)
    return candidate


def main():
    ap = argparse.ArgumentParser(description="按拍摄日期整理照片")
    ap.add_argument("src", help="源目录")
    ap.add_argument("-o", "--out", required=True, help="输出目录")
    ap.add_argument("--dry-run", action="store_true", help="只打印计划，不实际复制")
    ap.add_argument("--move", action="store_true", help="移动文件而不是复制（默认复制）")
    ap.add_argument("--min-age-days", type=int, default=0,
                    help="只处理修改时间早于 N 天前的文件（用于避免正在同步的照片）")
    args = ap.parse_args()

    if not os.path.isdir(args.src):
        print(f"错误：源目录不存在 {args.src}", file=sys.stderr)
        return 2

    now = datetime.now().timestamp()
    files = []
    for root, _dirs, names in os.walk(args.src):
        for n in names:
            ext = os.path.splitext(n)[1].lower()
            if ext not in IMAGE_EXTS:
                continue
            full = os.path.join(root, n)
            mtime = os.path.getmtime(full)
            if args.min_age_days and (now - mtime) < args.min_age_days * 86400:
                continue
            files.append((full, mtime))

    if not files:
        print("没有找到照片文件。")
        return 0

    files.sort(key=lambda x: x[1])
    used = set()
    copied = 0
    skipped = 0
    for full, mtime in files:
        dt, source = exif_datetime(full)
        if dt is None:
            dt = datetime.fromtimestamp(mtime)
            source = "mtime"
        name = safe_name(dt, full, used)
        rel = os.path.join(dt.strftime("%Y"), dt.strftime("%m"), name + os.path.splitext(full)[1].lower())
        dest = os.path.join(args.out, rel)
        if os.path.exists(dest):
            skipped += 1
            continue
        if not args.dry_run:
            os.makedirs(os.path.dirname(dest), exist_ok=True)
            if args.move:
                shutil.move(full, dest)
            else:
                shutil.copy2(full, dest)
        print(f"[{source:5s}] {os.path.basename(full)} -> {rel}")
        copied += 1

    print(f"\n完成：计划整理 {copied} 张，跳过 {skipped} 张。"
          f"{'（演练模式，未实际改动）' if args.dry_run else ''}")
    return 0


if __name__ == "__main__":
    sys.exit(main())
