# -*- coding: utf-8 -*-
"""
전국 실거래가(매매) 수집기 — 재개 가능 · 일일한도 대응
=====================================================
- 대상: 아파트 매매, 연립다세대 매매 / 전국 249 시군구 / 2019~2024
- (서비스, 시군구, 계약월) 단위로 진행상황(done)을 기록 → 재실행 시 건너뜀
- data.go.kr 일일 트래픽 한도(개발계정 1만/일) 도달 시 깔끔히 중단 → 다음날 재실행하면 이어서
결과: 결과/전국/실거래가_{서비스}.csv (증분 append)
진행: 결과/전국/_done_{서비스}.txt  ("시군구,계약월" 라인)
"""
from __future__ import annotations

import sys
import time
from pathlib import Path

import pandas as pd
import requests

import realestate as re

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

BASE = Path(__file__).with_name("결과") / "전국"
BASE.mkdir(parents=True, exist_ok=True)

SERVICES = ["아파트_매매", "연립다세대_매매"]
YMDS = re.month_range("201901", "202610")   # 2019~2026 (현재월까지)

# data.go.kr 한도초과/오류 코드 힌트
LIMIT_HINTS = ("22", "LIMIT", "초과", "한도", "EXCEED")


def _done_path(svc): return BASE / f"_done_{svc}.txt"
def _csv_path(svc): return BASE / f"실거래가_{svc}.csv"


def load_done(svc) -> set[str]:
    p = _done_path(svc)
    if not p.exists():
        return set()
    return set(p.read_text(encoding="utf-8").split())


def mark_done(svc, key, fh):
    fh.write(key + "\n"); fh.flush()


def append_rows(svc, df: pd.DataFrame):
    if df.empty:
        return
    p = _csv_path(svc)
    header = not p.exists()
    df.to_csv(p, mode="a", index=False, header=header, encoding="utf-8-sig")


def is_limit_error(e: re.ApiError) -> bool:
    s = f"{e.code} {e.message}".upper()
    return any(h.upper() in s for h in LIMIT_HINTS)


def main() -> None:
    sgg = pd.read_csv(Path(__file__).with_name("시군구코드.csv"), dtype=str)
    lawds = sgg["시군구코드"].tolist()
    print(f"전국 시군구 {len(lawds)}개 × 월 {len(YMDS)}개 × 서비스 {len(SERVICES)}개")

    session = requests.Session()
    for svc in SERVICES:
        done = load_done(svc)
        combos = [(l, y) for l in lawds for y in YMDS if f"{l},{y}" not in done]
        print(f"\n■ {svc}: 남은 조합 {len(combos)} (완료 {len(done)})")
        fh = open(_done_path(svc), "a", encoding="utf-8")
        n_calls = 0
        consec_429 = 0
        STOP_AFTER = 15          # 연속 429가 이만큼이면 일일한도로 보고 중단
        stopped = False
        try:
            for i, (lawd, ymd) in enumerate(combos, 1):
                # 429 백오프 재시도 루프
                for attempt in range(6):
                    try:
                        df = re.fetch_trade(svc, lawd, ymd, session=session, sleep_sec=0.1)
                        append_rows(svc, df)
                        mark_done(svc, f"{lawd},{ymd}", fh)
                        n_calls += 1
                        consec_429 = 0
                        break
                    except requests.HTTPError as e:
                        code = getattr(e.response, "status_code", None)
                        if code == 429:
                            consec_429 += 1
                            if consec_429 >= STOP_AFTER:
                                print(f"  [한도추정] {svc}: 429 연속 {consec_429}회 → 중단(다음날 재실행)")
                                stopped = True
                                break
                            time.sleep(min(2 ** attempt, 30))   # 2,4,8,16,30...
                            continue
                        # 기타 HTTP 오류: 건너뜀(미완료 유지)
                        print(f"  [HTTP{code}] {svc} {lawd} {ymd} → 건너뜀")
                        break
                    except re.ApiError as e:
                        if is_limit_error(e):
                            print(f"  [한도도달] {svc} {lawd} {ymd}: {e} → 중단")
                            stopped = True
                        else:
                            mark_done(svc, f"{lawd},{ymd}", fh)  # 자료없음 등
                        break
                    except requests.RequestException as e:
                        time.sleep(2)
                        if attempt >= 2:
                            print(f"  [통신오류] {svc} {lawd} {ymd}: {str(e)[:50]} → 건너뜀")
                            break
                        continue
                if stopped:
                    break
                if i % 200 == 0:
                    print(f"    {svc} {i}/{len(combos)} (이번 실행 {n_calls}건 수집)")
        finally:
            fh.close()
        print(f"  {svc} 이번 실행 완료분 {n_calls}건" + (" (한도중단)" if stopped else " (완료)"))
    print("\n[전체 완료] 모든 조합 수집됨" if all(
        len(load_done(s)) >= len(lawds) * len(YMDS) for s in SERVICES) else "\n[부분 완료] 재실행 필요")


if __name__ == "__main__":
    main()
