"""매물 수집: 국토부 실거래가(합법, market_deals) + 네이버부동산 호가(회색·폴백, listings)."""
import os
import re
import time
import logging
import requests
from datetime import date

from .lawd_codes import LAWD_CODES
from .db import (get_listing_criteria, upsert_listing, upsert_market_deal,
                 save_listing_collect_log)

logger = logging.getLogger("realestate-lab")

MOLIT_KEY = os.getenv("DATA_GO_KR_API_KEY", "")
NAVER_BASE = "https://new.land.naver.com/api"
# MOLIT operation ID (스펙 §3, 라이브 확인 필요). house_type → (rent_op, trade_op)
MOLIT_OPS = {
    "아파트": ("getRTMSDataSvcAptRent", "getRTMSDataSvcAptTradeDev"),
    "오피스텔": ("getRTMSDataSvcOffiRent", "getRTMSDataSvcOffiTrade"),
    "연립다세대": ("getRTMSDataSvcRHRent", "getRTMSDataSvcRHTrade"),
}
_HOUSE_TYPE_ALIAS = {"빌라": "연립다세대"}


def _won_to_manwon(s) -> int | None:
    """'43,000' 또는 '2억 9,000' → 만원 정수."""
    if s is None:
        return None
    t = str(s).replace(",", "").strip()
    if not t:
        return None
    m = re.match(r"(?:(\d+)억)?\s*(\d+)?", t)
    if m and (m.group(1) or m.group(2)):
        eok = int(m.group(1)) if m.group(1) else 0
        man = int(m.group(2)) if m.group(2) else 0
        return eok * 10000 + man
    try:
        return int(float(t))
    except ValueError:
        return None


def _parse_molit_rent(raw: dict, house_type: str) -> dict:
    deposit = _won_to_manwon(raw.get("deposit"))
    rent = _won_to_manwon(raw.get("monthlyRent"))
    return {"house_type": house_type, "complex_name": (raw.get("aptNm") or raw.get("offiNm") or "").strip(),
            "area": float(raw.get("excluUseAr") or 0) or None, "deal_type": "전세" if not rent else "월세",
            "deposit": deposit, "monthly_rent": rent, "sale_amount": None,
            "deal_ym": f"{raw.get('dealYear','')}{str(raw.get('dealMonth','')).zfill(2)}",
            "floor": str(raw.get("floor") or ""), "source": "molit"}


def _parse_molit_trade(raw: dict, house_type: str) -> dict:
    return {"house_type": house_type, "complex_name": (raw.get("aptNm") or raw.get("offiNm") or "").strip(),
            "area": float(raw.get("excluUseAr") or 0) or None, "deal_type": "매매",
            "deposit": None, "monthly_rent": None,
            "sale_amount": _won_to_manwon(raw.get("dealAmount")),
            "deal_ym": f"{raw.get('dealYear','')}{str(raw.get('dealMonth','')).zfill(2)}",
            "floor": str(raw.get("floor") or ""), "source": "molit"}


def _parse_naver_article(raw: dict) -> dict:
    dt = raw.get("tradeTypeName") or ""
    prc = _won_to_manwon(raw.get("dealOrWarrantPrc"))
    rent = _won_to_manwon(raw.get("rentPrc"))
    is_sale = dt == "매매"
    return {"article_no": str(raw.get("articleNo") or ""), "source": "naver", "deal_type": dt,
            "deposit": None if is_sale else prc, "monthly_rent": rent,
            "sale_price": prc if is_sale else None,
            "area_exclusive": float(raw.get("area2") or raw.get("areaName") or 0) or None,
            "floor": raw.get("floorInfo"), "complex_name": raw.get("articleName") or raw.get("buildingName"),
            "dong_code": (raw.get("cortarNo") or "")[:5] or None,
            "url": f"https://new.land.naver.com/houses?articleNo={raw.get('articleNo')}",
            "raw_json": None}


def _molit_call(op: str, lawd: str, ym: str) -> list:
    """국토부 실거래 단건 호출 (기존 collector 재시도 관용). 실패 시 []."""
    if not MOLIT_KEY:
        return []
    url = f"https://apis.data.go.kr/1613000/RTMSDataSvc{op[len('getRTMSDataSvc'):]}/{op}"
    for attempt in range(3):
        try:
            resp = requests.get(url, params={"serviceKey": MOLIT_KEY, "LAWD_CD": lawd,
                                             "DEAL_YMD": ym, "numOfRows": 200, "_type": "json"}, timeout=30)
            resp.raise_for_status()
            body = resp.json()
            items = (((body.get("response") or {}).get("body") or {}).get("items") or {}).get("item") or []
            return items if isinstance(items, list) else [items]
        except requests.RequestException as e:
            if attempt < 2:
                time.sleep(2 ** attempt)
            else:
                logger.error("MOLIT %s 실패: %s", op, e)
    return []


def _fetch_molit_all() -> int:
    """대상 동 시군구코드 × 유형 × 최근 2개월 전월세·매매 실거래 수집→market_deals. 저장건수 반환."""
    saved = 0
    lawds = set(LAWD_CODES.values())
    today = date.today()
    yms = [today.strftime("%Y%m"),
           (today.replace(day=1) - __import__("datetime").timedelta(days=1)).strftime("%Y%m")]
    for lawd in lawds:
        for htype, (rent_op, trade_op) in MOLIT_OPS.items():
            for ym in yms:
                for op, parser in ((rent_op, _parse_molit_rent), (trade_op, _parse_molit_trade)):
                    for raw in _molit_call(op, lawd, ym):
                        try:
                            d = parser(raw, htype)
                            d["dong_code"] = lawd
                            d["dong"] = (raw.get("umdNm") or "").strip() or None
                            upsert_market_deal(d)
                            saved += 1
                        except Exception as e:
                            logger.warning("MOLIT 파싱 실패: %s", e)
                    time.sleep(0.3)
    return saved


def _fetch_naver_all() -> tuple:
    """대상 동 네이버 호가 매물 수집→listings. (new_count, total_count) 반환. 예외는 상위로 전파."""
    new_count = total = 0
    crit = get_listing_criteria()
    for dong in crit.get("dongs", []):
        code = LAWD_CODES.get(dong)
        if not code:
            continue
        # 저빈도: 동당 1회, UA/Referer 현실값. 응답 articleList 순회.
        resp = requests.get(f"{NAVER_BASE}/articles",
                            params={"cortarNo": code, "order": "dateDesc", "page": 1},
                            headers={"User-Agent": "Mozilla/5.0", "Referer": "https://new.land.naver.com/"},
                            timeout=20)
        resp.raise_for_status()
        for raw in (resp.json().get("articleList") or []):
            try:
                d = _parse_naver_article(raw)
                if not d["article_no"]:
                    continue
                d["dong"] = dong
                _, is_new = upsert_listing(d)
                total += 1
                if is_new:
                    new_count += 1
            except Exception as e:
                logger.warning("네이버 파싱 실패: %s", e)
        time.sleep(1.5)
    return new_count, total


def collect_listings() -> dict:
    naver_ok = True
    new_count = total = 0
    try:
        _fetch_molit_all()   # 실거래(합법)는 항상 우선 — 안전마진 baseline
    except Exception as e:
        logger.error("MOLIT 수집 오류(안전마진 baseline): %s", e)
    try:
        new_count, total = _fetch_naver_all()
    except requests.RequestException as e:
        naver_ok = False
        logger.warning("네이버 매물수집 skip(차단/오류) — 안전마진·실거래는 유지: %s", e)
    save_listing_collect_log(new_count, total, naver_ok)
    return {"new_count": new_count, "total_count": total, "naver_ok": naver_ok}
