# -*- coding: utf-8 -*-
"""
kakao_to_me.py : 카카오톡 '나에게 보내기' 알림 스크립트
표준 라이브러리만 사용 (pip install 불필요)

사용법
  최초 1회:  python kakao_to_me.py auth
  이후 계속: python kakao_to_me.py send "보낼 메시지"

준비물: 아래 REST_API_KEY 자리에 카카오 디벨로퍼스 콘솔의
       [내 애플리케이션 > 앱 설정 > 앱 키 > REST API 키] 값을 붙여넣으세요.
       (Redirect URI는 콘솔에 http://localhost:3000/oauth 로 등록되어 있어야 합니다)
"""
import json
import os
import re
import sys
import time
import urllib.parse
import urllib.request
import urllib.error
import webbrowser

# 한글 Windows 콘솔(cp949) 환경에서도 print()가 깨지지 않도록 강제로 UTF-8 사용
for _stream in (sys.stdout, sys.stderr):
    try:
        _stream.reconfigure(encoding="utf-8", errors="replace")
    except Exception:
        pass

REST_API_KEY = "여기에_본인_REST_API_키_붙여넣기"
REDIRECT_URI = "http://localhost:3000/oauth"

TOKEN_FILE = os.path.join(os.path.dirname(os.path.abspath(__file__)), "kakao_token.json")

KAUTH_AUTHORIZE = "https://kauth.kakao.com/oauth/authorize"
KAUTH_TOKEN = "https://kauth.kakao.com/oauth/token"
KAPI_SEND = "https://kapi.kakao.com/v2/api/talk/memo/default/send"


def check_key():
    if not REST_API_KEY or "여기에" in REST_API_KEY:
        sys.exit(
            "REST API 키가 설정되지 않았습니다.\n"
            "카카오 디벨로퍼스 콘솔에서 REST API 키를 복사해 "
            "kakao_to_me.py 상단의 REST_API_KEY 값에 붙여넣은 뒤 다시 실행하세요."
        )


def post_form(url, data, headers=None):
    body = urllib.parse.urlencode(data).encode("utf-8")
    req = urllib.request.Request(url, data=body, headers=headers or {})
    try:
        with urllib.request.urlopen(req, timeout=15) as r:
            return json.loads(r.read().decode("utf-8"))
    except urllib.error.HTTPError as e:
        return json.loads(e.read().decode("utf-8"))


def save_tokens(tokens):
    tokens["obtained_at"] = time.time()
    with open(TOKEN_FILE, "w", encoding="utf-8") as f:
        json.dump(tokens, f, ensure_ascii=False, indent=2)


def load_tokens():
    if not os.path.exists(TOKEN_FILE):
        return None
    with open(TOKEN_FILE, encoding="utf-8") as f:
        return json.load(f)


def do_auth():
    check_key()
    params = {
        "client_id": REST_API_KEY,
        "redirect_uri": REDIRECT_URI,
        "response_type": "code",
        "scope": "talk_message",
    }
    url = KAUTH_AUTHORIZE + "?" + urllib.parse.urlencode(params)
    print("브라우저에서 카카오 로그인 화면을 엽니다...")
    print("(자동으로 안 열리면 이 주소를 브라우저에 직접 붙여넣으세요)\n" + url + "\n")
    webbrowser.open(url)

    print('로그인 후 "사이트에 연결할 수 없음" 화면이 뜨는 게 정상입니다.')
    pasted = input("주소창에 뜬 전체 주소(또는 code= 뒤의 값)를 붙여넣으세요: ").strip()

    m = re.search(r"[?&]code=([^&\s]+)", pasted)
    code = m.group(1) if m else pasted
    if not code:
        sys.exit("인가 코드를 찾지 못했습니다. 다시 시도해 주세요.")

    result = post_form(KAUTH_TOKEN, {
        "grant_type": "authorization_code",
        "client_id": REST_API_KEY,
        "redirect_uri": REDIRECT_URI,
        "code": code,
    })
    if "access_token" not in result:
        sys.exit("토큰 발급 실패: " + json.dumps(result, ensure_ascii=False))

    save_tokens(result)
    print("토큰 발급 완료! (" + TOKEN_FILE + "에 저장됨)")
    print("이제부터는 python kakao_to_me.py send \"메시지\" 로 바로 알림을 보낼 수 있습니다.")


def refresh_tokens(tokens):
    result = post_form(KAUTH_TOKEN, {
        "grant_type": "refresh_token",
        "client_id": REST_API_KEY,
        "refresh_token": tokens["refresh_token"],
    })
    if "access_token" not in result:
        return None
    tokens["access_token"] = result["access_token"]
    if "refresh_token" in result:
        tokens["refresh_token"] = result["refresh_token"]
    save_tokens(tokens)
    return tokens


def send_message(text, _retried=False):
    check_key()
    tokens = load_tokens()
    if not tokens:
        sys.exit("토큰이 없습니다. 먼저 python kakao_to_me.py auth 를 실행하세요.")

    if len(text) > 200:
        text = text[:197] + "..."

    template = {
        "object_type": "text",
        "text": text,
        "link": {"web_url": ""},
    }
    headers = {"Authorization": "Bearer " + tokens["access_token"]}
    result = post_form(KAPI_SEND, {"template_object": json.dumps(template)}, headers)

    if result.get("result_code") == 0:
        print("전송 완료! 카카오톡 '나와의 채팅'을 확인하세요.")
        return

    # 액세스 토큰 만료로 보이면 리프레시 후 1회만 재시도
    if not _retried and "refresh_token" in tokens:
        refreshed = refresh_tokens(tokens)
        if refreshed:
            send_message(text, _retried=True)
            return

    sys.exit("전송 실패: " + json.dumps(result, ensure_ascii=False))


def main():
    if len(sys.argv) < 2 or sys.argv[1] not in ("auth", "send"):
        print(__doc__)
        sys.exit(1)

    if sys.argv[1] == "auth":
        do_auth()
    else:
        if len(sys.argv) < 3:
            sys.exit('보낼 메시지를 입력하세요. 예) python kakao_to_me.py send "작업 완료"')
        send_message(sys.argv[2])


if __name__ == "__main__":
    main()
