"""顧客リスト（CSV）の表記ゆれをそろえ、重複を取り除くスクリプト（制作サンプル）。

使い方:
    python clean.py before.csv after.csv

やること:
    1. 全角・半角のゆれをそろえる（英数字は半角、カタカナは全角）
    2. 氏名の空白を「姓 名」の半角スペース1つにそろえる
    3. 電話番号を「090-1234-5678」の形にそろえる
    4. メールアドレスを小文字にする
    5. 住所を「都道府県」と「それ以降」に分ける
    6. 日付を「2026-04-01」の形にそろえる
    7. 電話番号かメールアドレスが同じ行を重複とみなし、新しい日付の行を残す
    8. 直せなかった値は「要確認」の列に理由を書く（勝手に消さない）
"""

import csv
import re
import sys
import unicodedata
from datetime import datetime

PREFS = [
    "北海道", "青森県", "岩手県", "宮城県", "秋田県", "山形県", "福島県", "茨城県", "栃木県", "群馬県",
    "埼玉県", "千葉県", "東京都", "神奈川県", "新潟県", "富山県", "石川県", "福井県", "山梨県", "長野県",
    "岐阜県", "静岡県", "愛知県", "三重県", "滋賀県", "京都府", "大阪府", "兵庫県", "奈良県", "和歌山県",
    "鳥取県", "島根県", "岡山県", "広島県", "山口県", "徳島県", "香川県", "愛媛県", "高知県", "福岡県",
    "佐賀県", "長崎県", "熊本県", "大分県", "宮崎県", "鹿児島県", "沖縄県",
]


def norm(text: str) -> str:
    """NFKC で全角英数字・半角カナのゆれをそろえ、前後の空白を取る。"""
    return unicodedata.normalize("NFKC", text or "").strip()


def clean_name(name: str) -> str:
    return re.sub(r"\s+", " ", norm(name))


def clean_phone(phone: str) -> tuple[str, str]:
    raw = norm(phone)
    digits = re.sub(r"\D", "", raw)
    if re.fullmatch(r"0[789]0\d{8}", digits):  # 携帯電話
        return f"{digits[:3]}-{digits[3:7]}-{digits[7:]}", ""
    if re.fullmatch(r"0\d{9}", digits):  # 固定電話（市外局番の長さは地域で違う）
        parts = re.split(r"[^\d]+", raw)
        if len(parts) == 3:  # 元の区切りを生かす
            return "-".join(parts), ""
        if digits[:2] in ("03", "06"):
            return f"{digits[:2]}-{digits[2:6]}-{digits[6:]}", ""
        return digits, "市外局番の区切りを確認"
    return raw, "電話番号の桁数が合わない" if phone else ""


def clean_email(email: str) -> tuple[str, str]:
    e = norm(email).lower().replace(" ", "")
    if e and not re.fullmatch(r"[\w.+-]+@[\w-]+(\.[\w-]+)+", e):
        return e, "メールアドレスの形が不正"
    return e, ""


def split_address(addr: str) -> tuple[str, str, str]:
    a = norm(addr)
    for p in PREFS:
        if a.startswith(p):
            return p, a[len(p):].strip(), ""
    return "", a, "都道府県が書かれていない" if a else ""


def clean_date(text: str) -> tuple[str, str]:
    t = norm(text)
    t = re.sub(r"[年月/.]", "-", t).replace("日", "")
    try:
        return datetime.strptime(t, "%Y-%m-%d").strftime("%Y-%m-%d"), ""
    except ValueError:
        return norm(text), "日付の形が読み取れない"


def main(src: str, dst: str) -> None:
    with open(src, encoding="utf-8-sig", newline="") as f:
        rows = list(csv.DictReader(f))

    cleaned = []
    for r in rows:
        notes = []
        phone, n1 = clean_phone(r["電話番号"])
        email, n2 = clean_email(r["メール"])
        pref, rest, n3 = split_address(r["住所"])
        date, n4 = clean_date(r["登録日"])
        notes += [n for n in (n1, n2, n3, n4) if n]
        cleaned.append({
            "氏名": clean_name(r["氏名"]),
            "電話番号": phone,
            "メール": email,
            "都道府県": pref,
            "住所": rest,
            "登録日": date,
            "要確認": "／".join(notes),
        })

    # 新しい登録日の行を先に並べ、電話番号かメールが既に出てきた行を重複として外す
    cleaned.sort(key=lambda r: r["登録日"], reverse=True)
    seen, result, dup = {}, [], 0
    for r in cleaned:
        keys = {k for k in (r["電話番号"], r["メール"]) if k}
        kept = next((seen[k] for k in keys if k in seen), None)
        if kept:
            dup += 1
            # 残す行の氏名に姓名の区切りがなく、重複側にあれば、そちらを使う
            if " " not in kept["氏名"] and kept["氏名"] == r["氏名"].replace(" ", ""):
                kept["氏名"] = r["氏名"]
            continue
        for k in keys:
            seen[k] = r
        result.append(r)
    for r in result:
        if " " not in r["氏名"]:
            r["要確認"] = "／".join(x for x in (r["要確認"], "姓と名の区切りがない") if x)
    result.sort(key=lambda r: r["登録日"])

    with open(dst, "w", encoding="utf-8-sig", newline="") as f:
        w = csv.DictWriter(f, fieldnames=list(result[0].keys()))
        w.writeheader()
        w.writerows(result)

    flagged = sum(1 for r in result if r["要確認"])
    print(f"元の件数 {len(rows)} 件 → 重複 {dup} 件を除いて {len(result)} 件（要確認 {flagged} 件）")


if __name__ == "__main__":
    sys.stdout.reconfigure(encoding="utf-8")
    main(sys.argv[1], sys.argv[2])
