# -*- coding: utf-8 -*-
"""
전국 공동주택 공시가격 수집기 — 재개 · 429 백오프 · 일일한도 대응
=================================================================
입력 : 결과/전국/분석_실거래가_매칭키.csv (PNU, 거래연도)
대상 : 아파트·연립 매매, 2019~2024, 거래가 있었던 (PNU, 연도)만
캐시 : 결과/전국/공시가격캐시/{유형}_{연도}.csv  (+ _done_{유형}_{연도}.txt)
- VWorld NED getApartHousingPriceAttr (gongsi.fetch_apt_price_pnu 재사용, 페이지네이션)
- 429(Too Many Requests): 지수 백오프 재시도, 연속 지속 시 중단(다음날 재개)
"""
from __future__ import annotations

import sys
import time
from pathlib import Path

import pandas as pd
import requests

import realestate as re
import gongsi

try:
    sys.stdout.reconfigure(encoding="utf-8")
except Exception:
    pass

NAT = Path(__file__).with_name("결과") / "전국"
CACHE = NAT / "공시가격캐시"
CACHE.mkdir(parents=True, exist_ok=True)

MATCH = NAT / "분석_실거래가_매칭키.csv"
YEARS = list(range(2019, 2027))   # 2019~2026
JOBS = {"아파트": ["아파트_매매"], "연립다세대": ["연립다세대_매매"]}

STOP_AFTER_429 = 20          # 연속 429 이만큼이면 일일한도로 보고 중단


def _done_path(name, y): return CACHE / f"_done_{name}_{y}.txt"
def _csv_path(name, y): return CACHE / f"{name}_{y}.csv"


def pending_count() -> int:
    """아직 수집 안 된 (PNU, 연도) 쌍 개수. 0이면 전국 공시가격 수집 완료."""
    if not MATCH.exists():
        return -1
    df = pd.read_csv(MATCH, usecols=["자료구분", "PNU", "거래연도"], dtype=str)
    df = df[df["PNU"].notna()]
    df["거래연도"] = pd.to_numeric(df["거래연도"], errors="coerce")
    remaining = 0
    for name, types in JOBS.items():
        sub = df[df["자료구분"].isin(types)]
        for y in YEARS:
            pnus = set(sub[sub["거래연도"] == y]["PNU"].unique())
            if not pnus:
                continue
            done = load_done(name, y)
            remaining += len(pnus - done)
    return remaining


def load_done(name, y) -> set:
    p = _done_path(name, y)
    return set(p.read_text(encoding="utf-8").split()) if p.exists() else set()


def append_rows(name, y, rows: list[dict]):
    if not rows:
        return
    df = pd.DataFrame(rows)
    p = _csv_path(name, y)
    df.to_csv(p, mode="a", index=False, header=not p.exists(), encoding="utf-8-sig")


def fetch_one(pnu, y, session, key, ref) -> tuple[str, list]:
    """반환: ('ok', rows) | ('limit', []) — 429 지속 시 limit."""
    consec = 0
    for attempt in range(7):
        try:
            rows = gongsi.fetch_apt_price_pnu(pnu, y, session=session, key=key, ref=ref)
            for r in rows:
                r["조회PNU"] = pnu
            return "ok", rows
        except requests.HTTPError as e:
            if getattr(e.response, "status_code", None) == 429:
                consec += 1
                time.sleep(min(2 ** attempt, 60))
                continue
            return "ok", []          # 기타 HTTP 오류: 자료없음 취급(완료)
        except requests.RequestException:
            time.sleep(3)
            continue
    return "limit", []               # 재시도 소진 → 한도로 간주


def main() -> None:
    if not MATCH.exists():
        print("매칭키 파일 없음. match_national.py 먼저 실행 필요.")
        return
    df = pd.read_csv(MATCH, usecols=["자료구분", "PNU", "거래연도"], dtype=str)
    df = df[df["PNU"].notna()]
    df["거래연도"] = pd.to_numeric(df["거래연도"], errors="coerce")

    key = re.get_vworld_key()
    ref = gongsi._referer()
    session = requests.Session()
    total_new = 0

    for name, types in JOBS.items():
        sub = df[df["자료구분"].isin(types)]
        for y in YEARS:
            pnus = sorted(sub[sub["거래연도"] == y]["PNU"].unique())
            if not pnus:
                continue
            done = load_done(name, y)
            todo = [p for p in pnus if p not in done]
            if not todo:
                continue
            print(f"\n■ {name} {y}: 대상 {len(pnus)} / 남은 {len(todo)} (완료 {len(done)})", flush=True)
            fh = open(_done_path(name, y), "a", encoding="utf-8")
            buf = []
            n = 0
            try:
                for i, pnu in enumerate(todo, 1):
                    status, rows = fetch_one(pnu, y, session, key, ref)
                    if status == "limit":
                        print(f"  [한도추정] {name} {y}: 429 지속 → 중단(다음날 재개)", flush=True)
                        append_rows(name, y, [r for rs in buf for r in rs])
                        fh.close()
                        print(f"\n[중단] 이번 실행 신규 {total_new + n}건")
                        return
                    buf.append(rows)
                    fh.write(pnu + "\n")
                    n += 1
                    if i % 300 == 0:
                        flat = [r for rs in buf for r in rs]
                        append_rows(name, y, flat); buf = []
                        fh.flush()
                        print(f"    {name} {y} {i}/{len(todo)}", flush=True)
                flat = [r for rs in buf for r in rs]
                append_rows(name, y, flat)
            finally:
                fh.close()
            total_new += n
            print(f"  {name} {y} 완료분 {n}건")

    print(f"\n[전체 완료] 이번 실행 신규 {total_new}건")


if __name__ == "__main__":
    main()
