# -*- coding: utf-8 -*-
"""
실거래가 → 매칭키(PNU) + 세그먼트 생성
=======================================
수집한 실거래가 CSV를 읽어, 공시가격과 결합할 수 있는 매칭키(PNU)와
비교분석용 세그먼트(유형/면적구간/시점/평단가 등)를 붙여 분석용 파일로 저장.

유형별 매칭 정밀도:
  - 아파트     : sggCd+umdCd+landCd+본번+부번 → PNU 100% 조립
  - 연립다세대 : umdCd가 없어, 아파트 데이터에서 (sggCd,umdNm)→umdCd 사전을
                 만들어 보완 + jibun("1195-8") 파싱 → PNU 조립
  - 단독다가구 : jibun이 "1***"로 마스킹 → 필지 PNU 불가(집계비교용으로만)
"""
from __future__ import annotations

import re
import sys
from pathlib import Path

import numpy as np
import pandas as pd

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

RESULT_DIR = Path(__file__).with_name("결과")

TRADE_FILES = {
    "아파트_매매":       "실거래가_아파트_매매.csv",
    "아파트_전월세":     "실거래가_아파트_전월세.csv",
    "연립다세대_매매":   "실거래가_연립다세대_매매.csv",
    "연립다세대_전월세": "실거래가_연립다세대_전월세.csv",
    "단독다가구_매매":   "실거래가_단독다가구_매매.csv",
    "단독다가구_전월세": "실거래가_단독다가구_전월세.csv",
    "오피스텔_매매":     "실거래가_오피스텔_매매.csv",
    "오피스텔_전월세":   "실거래가_오피스텔_전월세.csv",
}


def _load(name: str) -> pd.DataFrame:
    path = RESULT_DIR / TRADE_FILES[name]
    if not path.exists():
        return pd.DataFrame()
    df = pd.read_csv(path, encoding="utf-8-sig", dtype=str)
    df["자료구분"] = name
    return df


def build_umd_lookup(*dfs: pd.DataFrame) -> dict:
    """(sggCd, umdNm) -> umdCd 사전. umdCd가 채워진 행(주로 아파트)에서 학습."""
    lookup: dict = {}
    for df in dfs:
        if df.empty or "umdCd" not in df or "umdNm" not in df:
            continue
        sub = df[df["umdCd"].notna() & df["umdNm"].notna()]
        for sgg, umd, cd in zip(sub["sggCd"], sub["umdNm"], sub["umdCd"]):
            try:
                lookup[(str(sgg), str(umd))] = f"{int(cd):05d}"
            except Exception:
                pass
    return lookup


def _parse_jibun(jibun: str) -> tuple[int, int] | None:
    """'1195-8' -> (1195, 8), '525' -> (525, 0). 마스킹('*') 포함 시 None."""
    if not isinstance(jibun, str) or "*" in jibun:
        return None
    m = re.match(r"^\s*(\d+)(?:-(\d+))?\s*$", jibun)
    if not m:
        return None
    return int(m.group(1)), int(m.group(2) or 0)


def build_pnu(row: pd.Series, umd_lookup: dict) -> str | None:
    """행에서 PNU(19) 조립. 불가하면 None."""
    sgg = row.get("sggCd")
    if pd.isna(sgg):
        return None
    try:
        sgg = f"{int(sgg):05d}"
    except Exception:
        return None

    # 읍면동코드: umdCd 직접 사용, 없으면 (sggCd,umdNm) 사전 보완
    umd = row.get("umdCd")
    if pd.notna(umd):
        try:
            umd = f"{int(umd):05d}"
        except Exception:
            umd = None
    else:
        umd = None
    if umd is None:
        umd = umd_lookup.get((sgg, str(row.get("umdNm"))))
    if umd is None:
        return None

    # 대장구분: landCd 있으면 사용(대개 1), 없으면 지번에 '산' 없으니 1 가정
    land = row.get("landCd")
    try:
        land = str(int(land)) if pd.notna(land) else "1"
    except Exception:
        land = "1"

    # 본번/부번: bonbun/bubun 직접, 없으면 jibun 파싱
    bon, bub = row.get("bonbun"), row.get("bubun")
    if pd.notna(bon):
        try:
            bon, bub = int(bon), int(bub) if pd.notna(bub) else 0
        except Exception:
            bon = None
    else:
        bon = None
    if bon is None:
        parsed = _parse_jibun(row.get("jibun"))
        if parsed is None:
            return None
        bon, bub = parsed

    return f"{sgg}{umd}{land}{int(bon):04d}{int(bub):04d}"


# ---------------------------------------------------------------------------
# 세그먼트
# ---------------------------------------------------------------------------

def _to_won(amount: str) -> float:
    """'110,000'(만원) -> 원. 실거래가 dealAmount는 만원 단위."""
    if not isinstance(amount, str):
        return np.nan
    a = amount.replace(",", "").strip()
    return float(a) * 10000 if a.isdigit() else np.nan


AREA_BINS = [0, 40, 60, 85, 135, np.inf]
AREA_LABELS = ["~40㎡", "40-60㎡", "60-85㎡", "85-135㎡", "135㎡~"]


def add_segments(df: pd.DataFrame) -> pd.DataFrame:
    df = df.copy()
    df["거래금액_원"] = df["dealAmount"].map(_to_won) if "dealAmount" in df else np.nan
    df["전용면적_㎡"] = pd.to_numeric(df.get("excluUseAr"), errors="coerce")
    df["면적구간"] = pd.cut(df["전용면적_㎡"], bins=AREA_BINS, labels=AREA_LABELS, right=False)
    # 평단가(만원/평): 거래금액(만원) / (전용면적/3.3058)
    amt_manwon = pd.to_numeric(df.get("dealAmount", "").astype(str).str.replace(",", ""), errors="coerce")
    pyeong = df["전용면적_㎡"] / 3.3058
    df["평단가_만원"] = (amt_manwon / pyeong).round(1)
    df["거래연도"] = pd.to_numeric(df.get("dealYear"), errors="coerce").astype("Int64")
    mon = pd.to_numeric(df.get("dealMonth"), errors="coerce")
    df["거래분기"] = np.where(mon.notna(), "Q" + ((mon - 1) // 3 + 1).astype("Int64").astype(str), None)
    df["공시기준연도"] = df["거래연도"]  # 공시가격은 해당 거래연도(1/1 기준)와 매칭
    return df


def main() -> None:
    frames = {name: _load(name) for name in TRADE_FILES}

    # umdCd 사전은 아파트/오피스텔 등 umdCd가 채워진 데이터에서 학습
    umd_lookup = build_umd_lookup(*frames.values())
    print(f"읍면동코드 사전: {len(umd_lookup)}개 (sggCd,umdNm) 매핑 학습")

    out = []
    for name, df in frames.items():
        if df.empty:
            continue
        df = add_segments(df)
        df["PNU"] = df.apply(lambda r: build_pnu(r, umd_lookup), axis=1)
        n = len(df)
        ok = df["PNU"].notna().sum()
        print(f"  {name:16} {n:6}건 | PNU {ok:6}건 ({100*ok/n:5.1f}%)")
        out.append(df)

    full = pd.concat(out, ignore_index=True)
    keep = [
        "자료구분", "PNU", "sggCd", "umdNm", "jibun", "aptNm", "mhouseNm", "offiNm",
        "전용면적_㎡", "면적구간", "floor", "거래금액_원", "평단가_만원",
        "거래연도", "거래분기", "공시기준연도", "buildYear", "dealDay",
    ]
    keep = [c for c in keep if c in full.columns]
    analysis = full[keep]

    RESULT_DIR.mkdir(exist_ok=True)
    analysis.to_csv(RESULT_DIR / "분석_실거래가_매칭키.csv", index=False, encoding="utf-8-sig")
    print(f"\n저장: 결과/분석_실거래가_매칭키.csv ({len(analysis)}건, {len(keep)}컬럼)")

    # 유형별 PNU 커버리지 요약
    cov = full.groupby("자료구분").apply(
        lambda g: pd.Series({"건수": len(g), "PNU조립": g["PNU"].notna().sum(),
                             "커버리지%": round(100 * g["PNU"].notna().mean(), 1)})
    )
    print("\n[유형별 PNU 커버리지]")
    print(cov.to_string())


if __name__ == "__main__":
    main()
