#!/usr/bin/env python3
# -*- coding: utf-8 -*-
"""
탑에듀프렙 스터디 HTML 파일 정리 스크립트
1. 파일명에서 날짜 제거
2. 내용(title/meta keywords) 기반 카테고리 분류 → 폴더 이동
3. 각 HTML <head>에 <link rel="canonical"> 자동 삽입
4. 카테고리별 index.html(목록 페이지) 자동 생성
5. 카테고리별로 쪼갠 sitemap.xml 생성 (+ sitemap_index.xml)

사용법:
    python3 organize_topeduprep.py

설정값(아래 CONFIG 영역)만 환경에 맞게 바꾸면 됩니다.
"""

import os
import re
import shutil
import html
from datetime import datetime
from pathlib import Path

# ========================= CONFIG =========================
INPUT_DIR = "./input_html"        # 원본 html들이 모여있는 폴더 (분류 전, 평평한 구조)
OUTPUT_DIR = "./output_site"      # 분류 결과가 저장될 폴더
BASE_URL = "https://study.topeduprep.uk"  # 실제 배포 도메인 (서브도메인)

# 분류 규칙: {카테고리 슬러그: [매칭 키워드 목록]}
# 위에서부터 순서대로 검사하며, 먼저 매칭되는 카테고리로 분류됩니다.
CATEGORY_RULES = {
    "ssat":    ["SSAT"],
    "sat":     ["Digital SAT", "College Board", "SAT"],
    "act":     ["ACT"],
    "toefl":   ["TOEFL"],
    "toeic":   ["TOEIC"],
    "ielts":   ["IELTS"],
    "ap":      ["Advanced Placement", "AP"],
    "suneung": ["수능", "모의고사", "suneung"],
    "naesin":  ["내신"],
    "math":    ["수학", "Math", "math", "calculus", "precalc", "precalculus",
                "algebra", "geometry", "trig"],
    "english": ["영어", "English", "english", "문법", "독해"],
    "korean":  ["국어"],
    "science": ["과학", "물리", "화학", "생물", "지구과학"],
    "social":  ["사회", "한국사", "역사"],
}
FALLBACK_CATEGORY = "etc"

CATEGORY_LABELS = {
    "sat": "SAT", "ssat": "SSAT", "act": "ACT", "toefl": "TOEFL", "toeic": "TOEIC", "ielts": "IELTS",
    "ap": "AP", "suneung": "수능/모의고사", "naesin": "내신",
    "math": "수학", "english": "영어", "korean": "국어",
    "science": "과학", "social": "사회", "etc": "기타",
}

# 파일명 앞의 날짜 패턴: 예) 260628-SAT_English_Mastery.html
DATE_PREFIX_RE = re.compile(r"^\d{6}-")
# ============================================================


def extract_meta(html_text: str):
    """title, meta keywords/description, 파일명 속 날짜를 추출"""
    title_m = re.search(r"<title>(.*?)</title>", html_text, re.S | re.I)
    title = html.unescape(title_m.group(1).strip()) if title_m else ""

    kw_m = re.search(
        r'<meta\s+name=["\']keywords["\']\s+content=["\'](.*?)["\']',
        html_text, re.I,
    )
    keywords = html.unescape(kw_m.group(1)) if kw_m else ""

    desc_m = re.search(
        r'<meta\s+name=["\']description["\']\s+content=["\'](.*?)["\']',
        html_text, re.I,
    )
    description = html.unescape(desc_m.group(1)) if desc_m else ""

    return title, keywords, description


def classify(filename: str, title: str, keywords: str) -> str:
    """파일명 + title + keywords 를 합쳐 카테고리 슬러그 결정"""
    haystack = f"{filename} {title} {keywords}"
    for category, kw_list in CATEGORY_RULES.items():
        for kw in kw_list:
            if kw.lower() in haystack.lower():
                return category
    return FALLBACK_CATEGORY


def strip_date_prefix(filename: str) -> str:
    """260628-SAT_English_Mastery.html -> SAT_English_Mastery.html"""
    return DATE_PREFIX_RE.sub("", filename)


def parse_date_from_filename(filename: str):
    """YYMMDD 추출 -> ISO 날짜 문자열, 실패시 None"""
    m = re.match(r"^(\d{2})(\d{2})(\d{2})-", filename)
    if not m:
        return None
    yy, mm, dd = m.groups()
    try:
        # 2000년대로 가정 (사이트 데이터가 2025~2026년대)
        year = 2000 + int(yy)
        dt = datetime(year, int(mm), int(dd))
        return dt.strftime("%Y-%m-%d")
    except ValueError:
        return None


def inject_canonical(html_text: str, canonical_url: str) -> str:
    """<head> 안에 canonical 태그 삽입 (기존 canonical 있으면 교체)"""
    canonical_tag = f'<link rel="canonical" href="{canonical_url}">'

    # 이미 canonical 태그가 있으면 교체
    if re.search(r'<link\s+rel=["\']canonical["\']', html_text, re.I):
        return re.sub(
            r'<link\s+rel=["\']canonical["\'][^>]*>',
            canonical_tag,
            html_text,
            flags=re.I,
        )

    # 없으면 </head> 바로 앞에 삽입
    if "</head>" in html_text:
        return html_text.replace("</head>", f"  {canonical_tag}\n</head>")

    # head 태그 자체가 없는 비정상 파일이면 그냥 맨 앞에 추가
    return canonical_tag + "\n" + html_text


def build_index_page(category: str, entries: list) -> str:
    """카테고리별 목록(index) 페이지 HTML 생성"""
    label = CATEGORY_LABELS.get(category, category)
    row_list = []
    for e in sorted(entries, key=lambda x: x["filename"]):
        link_text = html.escape(e["title"] or e["filename"])
        desc = html.escape(e["description"]) if e["description"] else ""
        desc_part = f" — {desc}" if desc else ""
        row_list.append(
            f'      <li><a href="./{e["filename"]}">{link_text}</a>{desc_part}</li>'
        )
    rows = "\n".join(row_list)
    return f"""<!DOCTYPE html>
<html lang="ko">
<head>
<meta charset="UTF-8">
<meta name="viewport" content="width=device-width, initial-scale=1.0">
<title>{label} 학습지 모음 | TopEduPrep</title>
<meta name="description" content="TopEduPrep {label} 학습지 전체 목록입니다.">
<link rel="canonical" href="{BASE_URL}/{category}/">
</head>
<body>
  <h1>{label} 학습지 모음</h1>
  <p><a href="{BASE_URL}/">← 전체 카테고리로 돌아가기</a></p>
  <ul>
{rows}
  </ul>
</body>
</html>
"""


def build_search_index_page(category_entries: dict) -> str:
    """
    전체 학습지 링크가 실제 <a> 태그로 박혀있는 검색 가능한 메인 페이지.
    JS 검색은 이미 렌더링된 링크를 보이기/숨기기만 하므로,
    구글 크롤러는 JS 실행 여부와 무관하게 모든 링크를 그대로 읽을 수 있다.
    """
    total = sum(len(v) for v in category_entries.values())

    cat_buttons = "\n".join(
        f'      <button class="cat-btn" data-cat="{c}">{html.escape(CATEGORY_LABELS.get(c, c))} '
        f'<span class="cnt">{len(entries)}</span></button>'
        for c, entries in sorted(category_entries.items())
    )

    list_items = []
    for category, entries in sorted(category_entries.items()):
        label = CATEGORY_LABELS.get(category, category)
        for e in sorted(entries, key=lambda x: x["filename"]):
            title = html.escape(e["title"] or e["filename"])
            desc = html.escape(e["description"]) if e["description"] else ""
            href = f"./{category}/{e['filename']}"
            searchtext = html.escape(f"{label} {e['filename']} {e['title']} {e['keywords']}".lower())
            desc_html = f'<p class="desc">{desc}</p>' if desc else ""
            list_items.append(
                f'      <li class="item" data-cat="{category}" data-search="{searchtext}">'
                f'<a href="{href}"><span class="tag">{label}</span>{title}</a>'
                f'{desc_html}</li>'
            )
    list_html = "\n".join(list_items)

    return f"""<!DOCTYPE html>
<html lang="ko">
<head>
<meta charset="UTF-8">
<meta name="viewport" content="width=device-width, initial-scale=1.0">
<title>TopEduPrep 학습지 전체 모음 ({total}개) | study.topeduprep.uk</title>
<meta name="description" content="TopEduPrep SAT·SSAT·ACT·TOEFL·AP·수능·내신·수학·영어 학습지 전체 {total}개를 검색하고 바로 풀어보세요.">
<link rel="canonical" href="{BASE_URL}/">
<style>
  body{{font-family:-apple-system,'Noto Sans KR',sans-serif;max-width:880px;margin:0 auto;padding:24px 18px 80px;color:#1c2530;}}
  h1{{font-size:1.6rem;margin-bottom:6px;}}
  .sub{{color:#7a7a8a;font-size:.9rem;margin-bottom:20px;}}
  #searchBox{{width:100%;padding:12px 14px;font-size:1rem;border:1px solid #ddd;border-radius:10px;margin-bottom:14px;box-sizing:border-box;}}
  .cats{{display:flex;flex-wrap:wrap;gap:8px;margin-bottom:20px;}}
  .cat-btn{{font-size:.8rem;padding:6px 12px;border-radius:20px;border:1px solid #ddd;background:#fff;cursor:pointer;}}
  .cat-btn.active{{background:#1a3a5c;color:#fff;border-color:#1a3a5c;}}
  .cat-btn .cnt{{opacity:.6;font-size:.75em;}}
  ul.list{{list-style:none;padding:0;margin:0;}}
  li.item{{border-bottom:1px solid #eee;padding:12px 4px;}}
  li.item a{{font-weight:600;color:#1a3a5c;text-decoration:none;display:block;}}
  li.item a:hover{{text-decoration:underline;}}
  .tag{{display:inline-block;font-size:.7rem;background:#eaf2ec;color:#3f6b4f;padding:2px 8px;border-radius:10px;margin-right:8px;vertical-align:middle;}}
  .desc{{font-size:.82rem;color:#7a7a8a;margin:4px 0 0;}}
  .hidden{{display:none !important;}}
  #countInfo{{font-size:.85rem;color:#7a7a8a;margin-bottom:10px;}}
</style>
</head>
<body>
  <h1>TopEduPrep 학습지 전체 모음</h1>
  <p class="sub">총 {total}개 학습지 · 과목/시험별로 검색하고 바로 풀어보세요.</p>
  <input id="searchBox" type="text" placeholder="과목, 시험, 키워드로 검색 (예: SAT, calculus, 내신)">
  <div class="cats">
    <button class="cat-btn active" data-cat="all">전체 <span class="cnt">{total}</span></button>
{cat_buttons}
  </div>
  <p id="countInfo"></p>
  <ul class="list" id="list">
{list_html}
  </ul>

<script>
const searchBox = document.getElementById('searchBox');
const items = Array.from(document.querySelectorAll('#list .item'));
const catBtns = Array.from(document.querySelectorAll('.cat-btn'));
const countInfo = document.getElementById('countInfo');
let activeCat = 'all';

function applyFilter(){{
  const q = searchBox.value.trim().toLowerCase();
  let visible = 0;
  items.forEach(li => {{
    const matchesCat = activeCat === 'all' || li.dataset.cat === activeCat;
    const matchesSearch = !q || li.dataset.search.includes(q);
    const show = matchesCat && matchesSearch;
    li.classList.toggle('hidden', !show);
    if(show) visible++;
  }});
  countInfo.textContent = visible + '개 표시 중 (전체 {total}개)';
}}

catBtns.forEach(btn => {{
  btn.addEventListener('click', () => {{
    catBtns.forEach(b => b.classList.remove('active'));
    btn.classList.add('active');
    activeCat = btn.dataset.cat;
    applyFilter();
  }});
}});

searchBox.addEventListener('input', applyFilter);
applyFilter();
</script>
</body>
</html>
"""


def build_sitemap_xml(urls_with_dates: list) -> str:
    items = "\n".join(
        f"""  <url>
    <loc>{loc}</loc>
    <lastmod>{lastmod}</lastmod>
  </url>"""
        for loc, lastmod in urls_with_dates
    )
    return f'''<?xml version="1.0" encoding="UTF-8"?>
<urlset xmlns="http://www.sitemaps.org/schemas/sitemap/0.9">
{items}
</urlset>
'''


def build_sitemap_index(sitemap_files: list) -> str:
    items = "\n".join(
        f"""  <sitemap>
    <loc>{BASE_URL}/{name}</loc>
  </sitemap>"""
        for name in sitemap_files
    )
    return f'''<?xml version="1.0" encoding="UTF-8"?>
<sitemapindex xmlns="http://www.sitemaps.org/schemas/sitemap/0.9">
{items}
</sitemapindex>
'''


def main():
    input_dir = Path(INPUT_DIR)
    output_dir = Path(OUTPUT_DIR)
    output_dir.mkdir(parents=True, exist_ok=True)

    html_files = sorted(input_dir.glob("*.html"))
    if not html_files:
        print(f"⚠ {INPUT_DIR} 안에 .html 파일이 없습니다. INPUT_DIR 설정을 확인하세요.")
        return

    category_entries = {}   # category -> [ {filename, title, description}, ... ]
    today = datetime.now().strftime("%Y-%m-%d")

    for filepath in html_files:
        original_name = filepath.name
        html_text = filepath.read_text(encoding="utf-8", errors="ignore")

        title, keywords, description = extract_meta(html_text)
        category = classify(original_name, title, keywords)

        new_name = strip_date_prefix(original_name)
        file_date = parse_date_from_filename(original_name) or today

        category_dir = output_dir / category
        category_dir.mkdir(parents=True, exist_ok=True)
        new_path = category_dir / new_name

        # 같은 이름 파일이 이미 있으면 번호를 붙여 충돌 방지
        counter = 2
        while new_path.exists():
            stem, ext = os.path.splitext(new_name)
            new_path = category_dir / f"{stem}-{counter}{ext}"
            counter += 1

        canonical_url = f"{BASE_URL}/{category}/{new_path.name}"
        html_text = inject_canonical(html_text, canonical_url)
        new_path.write_text(html_text, encoding="utf-8")

        category_entries.setdefault(category, []).append({
            "filename": new_path.name,
            "title": title,
            "description": description,
            "keywords": keywords,
            "date": file_date,
        })

        print(f"[{category}] {original_name} -> {category}/{new_path.name}")

    # ---- 카테고리별 index.html 생성 ----
    for category, entries in category_entries.items():
        index_html = build_index_page(category, entries)
        (output_dir / category / "index.html").write_text(index_html, encoding="utf-8")

    # ---- 검색 가능한 전체 학습지 인덱스 페이지 (study.topeduprep.uk 루트용) ----
    search_index = build_search_index_page(category_entries)
    (output_dir / "index.html").write_text(search_index, encoding="utf-8")

    # ---- 카테고리별 sitemap.xml (+ index 페이지 url 포함) ----
    sitemap_files = []
    for category, entries in category_entries.items():
        urls = [(f"{BASE_URL}/{category}/", today)]
        urls += [
            (f"{BASE_URL}/{category}/{e['filename']}", e["date"])
            for e in entries
        ]
        sitemap_name = f"sitemap-{category}.xml"
        sitemap_xml = build_sitemap_xml(urls)
        (output_dir / sitemap_name).write_text(sitemap_xml, encoding="utf-8")
        sitemap_files.append(sitemap_name)

    # ---- 루트(검색 인덱스) 페이지용 sitemap ----
    root_sitemap_xml = build_sitemap_xml([(f"{BASE_URL}/", today)])
    (output_dir / "sitemap-home.xml").write_text(root_sitemap_xml, encoding="utf-8")
    sitemap_files.insert(0, "sitemap-home.xml")

    # ---- sitemap_index.xml ----
    sitemap_index_xml = build_sitemap_index(sitemap_files)
    (output_dir / "sitemap_index.xml").write_text(sitemap_index_xml, encoding="utf-8")

    # ---- robots.txt ----
    robots_txt = f"""User-agent: *
Allow: /

Sitemap: {BASE_URL}/sitemap_index.xml
"""
    (output_dir / "robots.txt").write_text(robots_txt, encoding="utf-8")

    print("\n=== 완료 ===")
    for category, entries in sorted(category_entries.items()):
        print(f"  {CATEGORY_LABELS.get(category, category)} ({category}): {len(entries)}개")
    print(f"\n결과물 위치: {output_dir.resolve()}")
    print("GSC에는 sitemap_index.xml 의 URL을 제출하세요.")
    print(f"예: {BASE_URL}/sitemap_index.xml")


if __name__ == "__main__":
    main()
