# -*- coding: utf-8 -*-
"""
============================================================
GTS 지상(SYNOP) 도시별 기온 비교 프로그램
- APIHUB 세계기상 > 1.2 지상관측 조회(BUFR 자료를 TAC 형태로 변환)
- 응답 형식: TEXT
- 응답 예시:
    #START7777
    # 설명 줄 ...
    # YYMMDDHHMI STN IW IR ...
    202211300900 47108 ...
    202211301000 47108 ...
    ...
    #7777END

이 프로그램의 목적
1) 여러 도시의 GTS 지상관측 자료를 API로 받기
2) 응답 텍스트에서 실제 데이터 줄만 추출하기
3) 기온(TA)만 정리하기
4) CSV 저장 및 그래프 그리기

필요 라이브러리:
    pip install requests pandas matplotlib
============================================================
"""
import requests
import pandas as pd
import matplotlib
matplotlib.use("Agg")   # 화면에 띄우지 않고 파일로 저장
import matplotlib.pyplot as plt

from io import StringIO
from datetime import datetime, timedelta, timezone


# ============================================================
# 1. 사용자 설정
# ============================================================

# 본인의 API 인증키 입력
AUTH_KEY = "SEQk-aWQSbKEJPmlkPmy0w"

# 사용할 API 주소
# 1.2 지상관측 조회(BUFR 자료를 TAC 형태로 변환)
BASE_URL = "https://apihub.kma.go.kr/api/typ01/url/gts_bufr_syn1.php"

# ------------------------------------------------------------
# 비교할 도시와 GTS 지점번호
# ※ 아래 지점번호는 예시입니다.
#   실제 사용 전 반드시 직접 확인해서 수정하세요.
# ------------------------------------------------------------
STATIONS = {
    "Seoul": 47108,
    "Tokyo": 47662,
    "Beijing": 54511,
}

# ------------------------------------------------------------
# 조회 종료 시각(UTC)
# 예: "202211301200"
#
# None 으로 두면 "현재 UTC 정시"를 자동 사용
# ------------------------------------------------------------
END_TM_UTC = "202211301200"

# ------------------------------------------------------------
# 과거 몇 시간 전까지 가져올지
# 예: dtm=24 이면 종료시각 포함 과거 24시간 범위 조회
# ------------------------------------------------------------
DTM_HOURS = 24

# ------------------------------------------------------------
# 그래프에는 몇 시간 간격 자료만 사용할지
# 예: 3 -> 3시간 간격만 사용
#     1 -> 모든 시간 사용
# ------------------------------------------------------------
PLOT_INTERVAL_HOURS = 3

# API 옵션
HELP = 1

# 디버그용: 원문 저장 여부
SAVE_RAW_TEXT = True

# 그래프 저장
OUTPUT_PNG = "gts_temperature_compare.png"

# ============================================================
# 2. 응답 데이터 컬럼 정의
#    사용자가 보여준 실제 응답 형식 기준
# ============================================================

COLUMNS = [
    "YYMMDDHHMI", "STN", "IW", "IR", "IX", "CH", "VV", "CA",
    "WD", "WS", "TA", "TD", "HM", "PA", "PS", "PT", "PR", "RN",
    "RH", "WC", "WP", "CD", "CL", "CM", "CT", "S19", "TM", "Tm",
    "S33", "S34", "S35", "S36", "S37", "S28", "S29"
]

# 결측값 후보
MISSING_VALUES = [
    -99, -99.0, -99.9, -999, -999.0, -999.9,
    -9, -9.0
]


# ============================================================
# 3. 종료시각 처리 함수
# ============================================================

def get_end_tm_utc():
    """
    조회 종료시각(UTC)을 문자열 yyyyMMddHHmm 형식으로 반환
    - END_TM_UTC가 None이면 현재 UTC 정시를 사용
    """
    if END_TM_UTC is not None:
        return END_TM_UTC

    now_utc = datetime.now(timezone.utc).replace(minute=0, second=0, microsecond=0)
    return now_utc.strftime("%Y%m%d%H%M")


# ============================================================
# 4. API 호출 함수
# ============================================================

def fetch_station_text(stn_id, tm_str, dtm_hours, help_flag=1):
    """
    1개 지점에 대해 GTS 지상관측 텍스트를 조회

    Parameters
    ----------
    stn_id : int
        지점번호
    tm_str : str
        종료시각(UTC), yyyyMMddHHmm
    dtm_hours : int
        과거 몇 시간 전까지 조회할지
    help_flag : int
        1이면 도움말/헤더 포함

    Returns
    -------
    text : str
        API 응답 원문(TEXT)
    """
    params = {
        "tm": tm_str,
        "dtm": dtm_hours,
        "stn": stn_id,
        "help": help_flag,
        "authKey": AUTH_KEY,
    }

    response = requests.get(BASE_URL, params=params, timeout=60)
    response.raise_for_status()

    return response.text


# ============================================================
# 5. 응답 텍스트에서 실제 데이터 줄만 추출
# ============================================================

def extract_data_lines(text):
    """
    API 응답 원문에서 실제 데이터 줄만 추출

    현재 응답 특징:
    - 설명 줄은 '#' 로 시작
    - 시작/끝 마커도 '#' 로 시작
    - 실제 데이터 줄은 숫자(YYYYMMDDHHMI)로 시작

    따라서,
    '숫자로 시작하는 줄만 추출' 하면 됨
    """
    data_lines = []

    for raw_line in text.splitlines():
        line = raw_line.strip()

        if not line:
            continue

        # 실제 데이터 줄은 첫 글자가 숫자
        if line[0].isdigit():
            data_lines.append(line)

    return data_lines


# ============================================================
# 6. 데이터 줄을 DataFrame으로 변환
# ============================================================

def parse_text_to_dataframe(text):
    """
    응답 텍스트 -> pandas DataFrame 변환

    처리 순서
    1) 실제 데이터 줄만 추출
    2) 공백 구분 텍스트로 읽기
    3) 컬럼명 부여
    4) 숫자형 변환
    5) 시간 컬럼(datetime_utc, datetime_kst) 생성
    """
    data_lines = extract_data_lines(text)

    if len(data_lines) == 0:
        return pd.DataFrame()

    # pandas가 읽을 수 있도록 문자열 버퍼로 만듦
    table_text = "\n".join(data_lines)

    try:
        df = pd.read_csv(
            StringIO(table_text),
            sep=r"\s+",
            header=None,
            names=COLUMNS,
            engine="python"
        )
    except Exception as e:
        print("[파싱 오류] DataFrame 변환 실패:", e)
        return pd.DataFrame()

    # 숫자형 컬럼 변환
    for col in COLUMNS:
        if col == "YYMMDDHHMI":
            continue
        df[col] = pd.to_numeric(df[col], errors="coerce")

    # 시간 컬럼 생성 (UTC 기준)
    df["datetime_utc"] = pd.to_datetime(
        df["YYMMDDHHMI"].astype(str),
        format="%Y%m%d%H%M",
        errors="coerce"
    )

    # KST 컬럼도 같이 생성
    # naive datetime 이므로 UTC timezone 부여 후 KST 변환
    kst = timezone(timedelta(hours=9))
    df["datetime_utc"] = df["datetime_utc"].dt.tz_localize("UTC")
    df["datetime_kst"] = df["datetime_utc"].dt.tz_convert(kst)

    return df


# ============================================================
# 7. 결측값 처리
# ============================================================

def apply_missing_value_rules(df):
    """
    결측값(-99, -999, -9 등)을 NaN 처리

    주의:
    - 모든 변수에서 무조건 동일 의미는 아닐 수 있음
    - 교육용 예제에서는 TA(기온) 위주 사용이므로 단순 처리
    """
    if df.empty:
        return df

    df = df.copy()

    for col in COLUMNS:
        if col == "YYMMDDHHMI":
            continue
        df[col] = df[col].replace(MISSING_VALUES, pd.NA)

    return df


# ============================================================
# 8. 도시별 자료 수집
# ============================================================

def collect_all_cities(stations, tm_str, dtm_hours):
    """
    여러 도시에 대해 자료를 수집하고 하나의 DataFrame으로 결합
    """
    all_frames = []

    for city, stn_id in stations.items():
        print(f"\n[도시 처리 시작] {city} (stn={stn_id})")

        try:
            text = fetch_station_text(stn_id, tm_str, dtm_hours, help_flag=HELP)

            # 디버그용 원문 저장
            if SAVE_RAW_TEXT:
                raw_filename = f"raw_{city}_{stn_id}.txt"
                with open(raw_filename, "w", encoding="utf-8") as f:
                    f.write(text)
                print(f"  - 원문 저장: {raw_filename}")

            df = parse_text_to_dataframe(text)

            if df.empty:
                print("  - 데이터 파싱 결과가 비어 있음")
                continue

            df = apply_missing_value_rules(df)

            # 혹시 다른 지점이 섞일 가능성 방지
            df = df[df["STN"] == stn_id].copy()

            if df.empty:
                print("  - 요청 지점번호와 일치하는 자료 없음")
                continue

            df["city"] = city

            print(f"  - 수집 성공: {len(df)}행")

            all_frames.append(df)

        except Exception as e:
            print(f"  - 오류 발생: {e}")

    if len(all_frames) == 0:
        return pd.DataFrame()

    result = pd.concat(all_frames, ignore_index=True)
    result = result.sort_values(["city", "datetime_utc"]).reset_index(drop=True)

    return result


# ============================================================
# 9. 원하는 시간 간격만 남기기
# ============================================================

def select_plot_interval(df, end_tm_str, interval_hours=3):
    """
    응답이 1시간 간격 자료일 수 있으므로,
    예를 들어 3시간 간격만 남기고 싶을 때 사용

    기준:
    종료시각(end_tm_str)으로부터 몇 시간 차이인지 계산하여
    interval_hours의 배수인 시각만 선택
    """
    if df.empty:
        return df

    end_dt = datetime.strptime(end_tm_str, "%Y%m%d%H%M").replace(tzinfo=timezone.utc)

    df = df.copy()

    delta_hours = (end_dt - df["datetime_utc"]).dt.total_seconds() / 3600.0
    df["delta_hours"] = delta_hours

    # 정수 시간차이만 사용
    df = df[df["delta_hours"].notna()].copy()

    # interval_hours의 배수만 선택
    df = df[(df["delta_hours"] % interval_hours) == 0].copy()

    # 다시 시간 오름차순 정렬
    df = df.sort_values(["city", "datetime_utc"]).reset_index(drop=True)

    return df


# ============================================================
# 10. 기온 비교용 DataFrame 만들기
# ============================================================

def build_temperature_table(df):
    """
    도시/시간/기온(TA)만 남긴 테이블 생성
    """
    if df.empty:
        return df

    out = df[["city", "STN", "datetime_utc", "datetime_kst", "TA"]].copy()
    out = out.sort_values(["datetime_utc", "city"]).reset_index(drop=True)

    return out


# ============================================================
# 11. 요약 출력
# ============================================================

def print_summary(temp_df):
    """
    수집 결과를 간단히 출력
    """
    if temp_df.empty:
        print("\n[안내] 수집된 데이터가 없습니다.")
        return

    print("\n================ 원본 기온 데이터 ================")
    print(temp_df)

    print("\n================ 도시별 기온 통계 ================")
    summary = temp_df.groupby("city")["TA"].agg(["count", "mean", "min", "max"])
    print(summary.round(2))


# ============================================================
# 12. CSV 저장
# ============================================================

def save_csv(df, filename):
    """
    CSV 저장
    """
    if df.empty:
        print(f"[안내] 저장할 데이터가 없어 {filename} 저장 생략")
        return

    df.to_csv(filename, index=False, encoding="utf-8-sig")
    print(f"[저장 완료] {filename}")


# ============================================================
# 13. 그래프 그리기
# ============================================================

def plot_temperature(temp_df):
    """
    도시별 기온 비교 그래프
    """
    if temp_df.empty:
        print("[안내] 그래프로 그릴 데이터가 없습니다.")
        return

    plot_df = temp_df.pivot_table(
        index="datetime_kst",
        columns="city",
        values="TA",
        aggfunc="first"
    )

    plt.figure(figsize=(12, 6))

    for city in plot_df.columns:
        plt.plot(
            plot_df.index,
            plot_df[city],
            marker="o",
            linewidth=2,
            label=city
        )

    plt.title("City Temperature Comparison using GTS SYNOP")
    plt.xlabel("Time (KST)")
    plt.ylabel("Temperature (°C)")
    plt.grid(True, linestyle="--", alpha=0.5)
    plt.legend()
    plt.xticks(rotation=45)
    plt.tight_layout()

    plt.savefig(OUTPUT_PNG, dpi=150, bbox_inches="tight")
    plt.close()

    print(f"[저장 완료] 그래프 파일: {OUTPUT_PNG}")


# ============================================================
# 14. 메인 함수
# ============================================================

def main():
    end_tm_str = get_end_tm_utc()

    print("=" * 60)
    print("GTS 지상(SYNOP) 도시별 기온 비교")
    print("사용 API:", BASE_URL)
    print("조회 종료시각(UTC):", end_tm_str)
    print("조회 범위(dtm, 시간):", DTM_HOURS)
    print("그래프 간격(시간):", PLOT_INTERVAL_HOURS)
    print("=" * 60)

    # 1) 여러 도시 자료 수집
    raw_df = collect_all_cities(STATIONS, end_tm_str, DTM_HOURS)

    if raw_df.empty:
        print("\n[안내] 모든 도시에서 자료 수집에 실패했습니다.")
        return

    # 2) 전체 원자료 저장
    save_csv(raw_df, "gts_all_columns_raw.csv")

    # 3) 그래프용 간격 추출
    filtered_df = select_plot_interval(raw_df, end_tm_str, PLOT_INTERVAL_HOURS)

    # 4) 기온용 테이블 생성
    temp_df = build_temperature_table(filtered_df)

    # 5) 기온 테이블 저장
    save_csv(temp_df, "gts_temperature_only.csv")

    # 6) 요약 출력
    print_summary(temp_df)

    # 7) 그래프 출력
    plot_temperature(temp_df)


# ============================================================
# 15. 실행
# ============================================================

if __name__ == "__main__":
    main()
