# -*- coding: utf-8 -*-
"""
============================================================
[교육용 예제]
최근 1년 지진 진앙 분포도 그리기

기능
1. APIHUB 지진목록 API 호출
2. 응답(TEXT)에서 지진 목록 파싱
3. 최근 1년 지진의 진앙 위치를 전지구 지도에 표시
4. 규모 구간별 색상 표현
   - M2 : 파랑
   - M4 : 노랑
   - M6 : 주황
   - M8 : 빨강
5. 총 5개의 PNG 저장
   - 전체
   - M2
   - M4
   - M6
   - M8
6. CSV 저장

필요 라이브러리
    pip install requests pandas matplotlib cartopy
============================================================
"""

import os
from datetime import datetime, timedelta

import requests
import pandas as pd

import matplotlib
matplotlib.use("Agg")   # 서버/터미널 환경에서 창을 띄우지 않고 파일 저장
import matplotlib.pyplot as plt
from matplotlib import font_manager
from matplotlib.lines import Line2D

import cartopy.crs as ccrs
import cartopy.feature as cfeature


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

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

# API 주소
BASE_URL = "https://apihub.kma.go.kr/api/typ01/url/eqk_list.php"

# 조회 기간
# None이면 자동으로 "현재 시각 기준 최근 1년"
START_TM = None
END_TM = None

# API 옵션
DISP = 1
HELP = 1

# 결과 파일명
OUTPUT_CSV = "earthquake_list_last_1year.csv"
RAW_TEXT_FILE = "earthquake_raw_response.txt"

OUTPUT_PNG_ALL = "earthquake_epicenter_map_all.png"
OUTPUT_PNG_M2 = "earthquake_epicenter_map_m2.png"
OUTPUT_PNG_M4 = "earthquake_epicenter_map_m4.png"
OUTPUT_PNG_M6 = "earthquake_epicenter_map_m6.png"
OUTPUT_PNG_M8 = "earthquake_epicenter_map_m8.png"

# 점 스타일
POINT_SIZE = 32              # 모든 점 크기 동일 (기존 M2 수준으로 작게)
POINT_ALPHA = 0.85
POINT_EDGE_COLOR = "black"
POINT_EDGE_WIDTH = 0.25

# 규모 구간별 색상
COLOR_M2 = "#1E88E5"   # 파랑
COLOR_M4 = "#FDD835"   # 노랑
COLOR_M6 = "#FB8C00"   # 주황
COLOR_M8 = "#E53935"   # 빨강


# ============================================================
# 2. 컬럼 정의
# 실제 응답 형식 기준으로 필요한 컬럼만 사용
# TP, TM_FC, SEQ, TM_EQK.MSC, MT, LAT, LON, LOC
# ============================================================

COLUMNS = [
    "TP",
    "TM_FC",
    "SEQ",
    "TM_EQK_MSC",
    "MT",
    "LAT",
    "LON",
    "LOC",
]


# ============================================================
# 3. 한글 폰트 설정
# ============================================================

def set_korean_font():
    """
    matplotlib에서 한글이 네모로 깨지는 문제를 방지하기 위한 함수
    Linux / Windows에서 자주 쓰는 한글 폰트를 순서대로 탐색
    """
    candidate_paths = [
        "/usr/share/fonts/truetype/nanum/NanumGothic.ttf",
        "/usr/share/fonts/truetype/nanum/NanumBarunGothic.ttf",
        "/usr/share/fonts/opentype/noto/NotoSansCJK-Regular.ttc",
        "/usr/share/fonts/opentype/noto/NotoSansCJKkr-Regular.otf",
        "C:/Windows/Fonts/malgun.ttf",
    ]

    font_set = False

    for font_path in candidate_paths:
        if os.path.exists(font_path):
            font_prop = font_manager.FontProperties(fname=font_path)
            plt.rcParams["font.family"] = font_prop.get_name()
            font_set = True
            print(f"[폰트 설정] {font_prop.get_name()} / {font_path}")
            break

    if not font_set:
        font_names = [f.name for f in font_manager.fontManager.ttflist]
        for name in ["NanumGothic", "NanumBarunGothic", "Noto Sans CJK KR", "Malgun Gothic"]:
            if name in font_names:
                plt.rcParams["font.family"] = name
                font_set = True
                print(f"[폰트 설정] {name}")
                break

    if not font_set:
        print("[경고] 사용 가능한 한글 폰트를 찾지 못했습니다. 한글이 깨질 수 있습니다.")

    plt.rcParams["axes.unicode_minus"] = False


# ============================================================
# 4. 조회 기간 만들기
# ============================================================

def make_time_range():
    """
    조회 시작시각 / 종료시각을 yyyyMMddHHmm 형식 문자열로 반환
    """
    if START_TM is not None and END_TM is not None:
        return START_TM, END_TM

    now = datetime.now()
    one_year_ago = now - timedelta(days=365)

    tm1 = one_year_ago.strftime("%Y%m%d%H%M")
    tm2 = now.strftime("%Y%m%d%H%M")
    return tm1, tm2


# ============================================================
# 5. API 호출
# ============================================================

def fetch_earthquake_text(tm1, tm2):
    """
    지진목록 API 호출 후 원문(TEXT)을 반환
    """
    auth_key = AUTH_KEY.strip()

    if not auth_key:
        raise ValueError("AUTH_KEY가 비어 있습니다.")

    params = {
        "tm1": tm1,
        "tm2": tm2,
        "disp": DISP,
        "help": HELP,
        "authKey": auth_key,
    }

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

    text = response.text

    with open(RAW_TEXT_FILE, "w", encoding="utf-8") as f:
        f.write(text)

    print("[저장 완료] 원문 응답:", RAW_TEXT_FILE)
    print("[호출 URL]", response.url)

    return text


# ============================================================
# 6. 응답 텍스트 정리
# ============================================================

def extract_data_lines(text):
    """
    응답 텍스트에서 실제 데이터 줄만 추출
    """
    lines = []

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

        if not line:
            continue

        if line.startswith("#"):
            continue

        if line[0].isdigit():
            lines.append(line)

    return lines


def parse_text_to_dataframe(text):
    """
    API 응답 텍스트를 DataFrame으로 변환

    주의:
    LOC 뒤쪽 INT, REM, COR에는 쉼표가 섞일 수 있으므로
    이번 교육용 지도 예제에서는
    앞의 고정 7개 필드 + LOC만 사용한다.
    """
    lines = extract_data_lines(text)

    if len(lines) == 0:
        return pd.DataFrame(columns=COLUMNS)

    rows = []

    for line in lines:
        line = line.strip()

        # 맨 끝 ",=" 제거
        if line.endswith(",="):
            line = line[:-2]

        # 앞 7개는 고정 필드, 나머지는 통째로 받음
        parts = line.split(",", 7)

        if len(parts) < 8:
            continue

        tp = parts[0].strip()
        tm_fc = parts[1].strip()
        seq = parts[2].strip()
        tm_eqk_msc = parts[3].strip()
        mt = parts[4].strip()
        lat = parts[5].strip()
        lon = parts[6].strip()

        # 나머지 전체에서 LOC만 추출
        rest = parts[7].strip()
        loc = rest.split(",", 1)[0].strip()

        rows.append([tp, tm_fc, seq, tm_eqk_msc, mt, lat, lon, loc])

    if len(rows) == 0:
        return pd.DataFrame(columns=COLUMNS)

    df = pd.DataFrame(rows, columns=COLUMNS)
    return df


# ============================================================
# 7. 자료형 정리
# ============================================================

def clean_dataframe(df):
    """
    숫자형 컬럼 변환, 시간 컬럼 생성, 결측 제거
    """
    if df.empty:
        return df

    df = df.copy()

    numeric_cols = ["TP", "SEQ", "MT", "LAT", "LON"]
    for col in numeric_cols:
        df[col] = pd.to_numeric(df[col], errors="coerce")

    # 진앙시 예: 20250422191713.000
    df["datetime_eqk"] = pd.to_datetime(
        df["TM_EQK_MSC"],
        format="%Y%m%d%H%M%S.%f",
        errors="coerce"
    )

    # 발표시각 예: 202504221939
    df["datetime_fc"] = pd.to_datetime(
        df["TM_FC"],
        format="%Y%m%d%H%M",
        errors="coerce"
    )

    # 지도에 필요한 값 없는 행 제거
    df = df.dropna(subset=["MT", "LAT", "LON"]).copy()

    # 시간 기준 정렬
    df = df.sort_values("datetime_eqk").reset_index(drop=True)

    return df


# ============================================================
# 8. 규모 등급 분류
# ============================================================

def classify_magnitude(mt):
    """
    규모를 4개 그룹으로 분류

    M2 : 2.0 <= MT < 4.0
    M4 : 4.0 <= MT < 6.0
    M6 : 6.0 <= MT < 8.0
    M8 : 8.0 <= MT
    """
    if pd.isna(mt):
        return "UNKNOWN"

    mt = float(mt)

    if mt < 4.0:
        return "M2"
    elif mt < 6.0:
        return "M4"
    elif mt < 8.0:
        return "M6"
    else:
        return "M8"


def magnitude_class_to_color(cls):
    """
    규모 그룹별 색상 반환
    """
    if cls == "M2":
        return COLOR_M2
    elif cls == "M4":
        return COLOR_M4
    elif cls == "M6":
        return COLOR_M6
    elif cls == "M8":
        return COLOR_M8
    else:
        return "gray"


# ============================================================
# 9. 요약 출력
# ============================================================

def print_summary(df):
    """
    간단한 통계 출력
    """
    if df.empty:
        print("[안내] 수집된 지진 데이터가 없습니다.")
        return

    print("\n================ 지진자료 요약 ================")
    print("총 지진 개수:", len(df))
    print("규모 최솟값:", round(df["MT"].min(), 2))
    print("규모 최댓값:", round(df["MT"].max(), 2))
    print("평균 규모:", round(df["MT"].mean(), 2))

    print("\n================ 규모 구간별 개수 ================")
    tmp = df.copy()
    tmp["MAG_CLASS"] = tmp["MT"].apply(classify_magnitude)
    print(tmp["MAG_CLASS"].value_counts().sort_index())

    print("\n================ 규모 상위 5개 ================")
    top5 = df.sort_values("MT", ascending=False).head(5)
    print(top5[["datetime_eqk", "MT", "LAT", "LON", "LOC"]])


# ============================================================
# 10. CSV 저장
# ============================================================

def save_csv(df):
    """
    전체 결과를 CSV로 저장
    """
    if df.empty:
        print("[안내] 저장할 데이터가 없습니다.")
        return

    df.to_csv(OUTPUT_CSV, index=False, encoding="utf-8-sig")
    print("[저장 완료] CSV:", OUTPUT_CSV)


# ============================================================
# 11. 범례 추가
# ============================================================

def add_color_legend(ax):
    """
    전체 지도용 색상 범례 추가
    """
    legend_elements = [
        Line2D([0], [0], marker='o', color='w', label='M2 (2.0 ≤ MT < 4.0)',
               markerfacecolor=COLOR_M2, markeredgecolor=POINT_EDGE_COLOR,
               markersize=8),
        Line2D([0], [0], marker='o', color='w', label='M4 (4.0 ≤ MT < 6.0)',
               markerfacecolor=COLOR_M4, markeredgecolor=POINT_EDGE_COLOR,
               markersize=8),
        Line2D([0], [0], marker='o', color='w', label='M6 (6.0 ≤ MT < 8.0)',
               markerfacecolor=COLOR_M6, markeredgecolor=POINT_EDGE_COLOR,
               markersize=8),
        Line2D([0], [0], marker='o', color='w', label='M8 (MT ≥ 8.0)',
               markerfacecolor=COLOR_M8, markeredgecolor=POINT_EDGE_COLOR,
               markersize=8),
    ]

    ax.legend(
        handles=legend_elements,
        title="Magnitude Class",
        loc="center left",
        bbox_to_anchor=(1.02, 0.5),
        frameon=True,
        borderpad=1.0,
        labelspacing=1.1
    )


# ============================================================
# 12. 지도 배경 공통 함수
# ============================================================

def setup_map():
    """
    공통 지도 생성
    """
    fig = plt.figure(figsize=(16, 7))
    ax = plt.axes(projection=ccrs.PlateCarree())
    ax.set_global()

    ax.coastlines(linewidth=0.8)
    ax.add_feature(cfeature.BORDERS, linewidth=0.4)
    ax.add_feature(cfeature.LAND, alpha=0.3)
    ax.add_feature(cfeature.OCEAN, alpha=0.2)
    ax.gridlines(draw_labels=False, linewidth=0.4, linestyle="--", alpha=0.5)

    return fig, ax


# ============================================================
# 13. 전체 지도 저장
# ============================================================

def plot_all_map(df):
    """
    전체 지진 분포도 저장
    규모 구간별 색상을 다르게 사용
    """
    if df.empty:
        print("[안내] 전체 지도에 그릴 데이터가 없습니다.")
        return

    fig, ax = setup_map()

    tmp = df.copy()
    tmp["MAG_CLASS"] = tmp["MT"].apply(classify_magnitude)
    tmp["COLOR"] = tmp["MAG_CLASS"].apply(magnitude_class_to_color)

    ax.scatter(
        tmp["LON"],
        tmp["LAT"],
        s=POINT_SIZE,
        c=tmp["COLOR"],
        alpha=POINT_ALPHA,
        edgecolors=POINT_EDGE_COLOR,
        linewidths=POINT_EDGE_WIDTH,
        transform=ccrs.PlateCarree()
    )

    add_color_legend(ax)

    ax.set_title("Epicenter Distribution for the Last 1 Year (All)", fontsize=14)

    plt.subplots_adjust(right=0.82)
    plt.savefig(OUTPUT_PNG_ALL, dpi=150, bbox_inches="tight")
    plt.close()

    print("[저장 완료] PNG:", OUTPUT_PNG_ALL)


# ============================================================
# 14. 규모별 단일 지도 저장
# ============================================================

def plot_single_class_map(df, mag_class, output_png):
    """
    특정 규모 그룹만 따로 지도 저장
    """
    if df.empty:
        print(f"[안내] {mag_class} 지도에 그릴 데이터가 없습니다.")
        return

    tmp = df.copy()
    tmp["MAG_CLASS"] = tmp["MT"].apply(classify_magnitude)
    tmp = tmp[tmp["MAG_CLASS"] == mag_class].copy()

    if tmp.empty:
        print(f"[안내] {mag_class}에 해당하는 지진 데이터가 없습니다.")
        return

    color = magnitude_class_to_color(mag_class)

    fig, ax = setup_map()

    ax.scatter(
        tmp["LON"],
        tmp["LAT"],
        s=POINT_SIZE,
        color=color,
        alpha=POINT_ALPHA,
        edgecolors=POINT_EDGE_COLOR,
        linewidths=POINT_EDGE_WIDTH,
        transform=ccrs.PlateCarree()
    )

    ax.set_title(f"Epicenter Distribution for the Last 1 Year ({mag_class})", fontsize=14)

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

    print("[저장 완료] PNG:", output_png)


# ============================================================
# 15. 메인 함수
# ============================================================

def main():
    set_korean_font()

    tm1, tm2 = make_time_range()

    print("=" * 60)
    print("최근 1년 지진 진앙 분포도 그리기")
    print("조회 시작시각:", tm1)
    print("조회 종료시각:", tm2)
    print("=" * 60)

    # 1) API 호출
    text = fetch_earthquake_text(tm1, tm2)

    # 2) 텍스트 -> DataFrame
    df = parse_text_to_dataframe(text)

    # 3) 자료형 정리
    df = clean_dataframe(df)

    # 4) 요약 출력
    print_summary(df)

    # 5) CSV 저장
    save_csv(df)

    # 6) PNG 저장
    plot_all_map(df)
    plot_single_class_map(df, "M2", OUTPUT_PNG_M2)
    plot_single_class_map(df, "M4", OUTPUT_PNG_M4)
    plot_single_class_map(df, "M6", OUTPUT_PNG_M6)
    plot_single_class_map(df, "M8", OUTPUT_PNG_M8)


# ============================================================
# 16. 실행
# ============================================================

if __name__ == "__main__":
    main()
