"""
kis_trader/utils/logger.py — 공통 로거 + JSON Atomic Save + 텔레그램/매터모스트 알림
=============================================================================
- get_logger(name): 표준 포맷(시:분:초 + 메시지)과 ANSI 컬러 로거 반환.
- atomic_save_json / atomic_load_json: 프로세스 동시 실행에도 파일 파손 없이 쓰기.
- msg_mm / msg_tg: 메시지 채널 분리.  서버 부하 방지를 위해 호출 전 짧은 jitter.
"""
from __future__ import annotations

import json
import logging
import os
import random
import tempfile
import threading
import time
from pathlib import Path
from typing import Any, Iterable, Optional

import requests

from .env import get_env_bool, get_env_from_db

# ── 로깅 ─────────────────────────────────────────────────────────────
# 루트 로거 한 번만 설정 (여러 모듈에서 import되어도 중복 핸들러 안 생기게)
_ROOT_CONFIGURED = False


def _configure_root() -> None:
    global _ROOT_CONFIGURED
    if _ROOT_CONFIGURED:
        return
    logging.basicConfig(
        format="[%(asctime)s] [%(name)s] %(message)s",
        datefmt="%H:%M:%S",
        level=logging.INFO,
    )
    _ROOT_CONFIGURED = True


def get_logger(name: str) -> logging.Logger:
    """프로젝트 표준 로거."""
    _configure_root()
    return logging.getLogger(name)


# ── ANSI 컬러 (기존 봇 관례 유지) ──────────────────────────────────
LOG_RED = "\033[91m"
LOG_YELLOW = "\033[93m"
LOG_GREEN = "\033[92m"
LOG_CYAN = "\033[96m"
LOG_RESET = "\033[0m"


# ── Atomic JSON I/O ─────────────────────────────────────────────────
# json 파일 손상 방지: 임시파일에 쓰고 os.replace 로 원자적 교체.
# 이벤트 발생 시 즉시 저장(재시작해도 최신 상태 유지).
_IO_LOCK = threading.Lock()


def atomic_save_json(path: str | os.PathLike, data: Any) -> bool:
    """임시파일 → rename 으로 원자적 저장. 실패해도 기존 파일 보존."""
    p = Path(path)
    try:
        p.parent.mkdir(parents=True, exist_ok=True)
        with _IO_LOCK:
            with tempfile.NamedTemporaryFile(
                "w", delete=False, dir=str(p.parent),
                prefix=p.name + ".", suffix=".tmp", encoding="utf-8",
            ) as tf:
                json.dump(data, tf, ensure_ascii=False, indent=2)
                tf.flush()
                os.fsync(tf.fileno())
                tmp_name = tf.name
            os.replace(tmp_name, p)
        return True
    except Exception as e:
        logging.getLogger("kis_trader.logger").error(
            "atomic_save_json 실패 (%s): %s", p, e
        )
        return False


def atomic_load_json(path: str | os.PathLike, default: Any = None) -> Any:
    p = Path(path)
    if not p.exists():
        return default
    try:
        with _IO_LOCK:
            with open(p, "r", encoding="utf-8") as f:
                return json.load(f)
    except Exception:
        return default


# ── Mattermost / Telegram 알림 ─────────────────────────────────────
_MM_CONFIG_PATH_CACHE: Optional[Path] = None


def _mm_config_path() -> Path:
    """mm_config.json 위치 탐색 (프로젝트 루트 기준)."""
    global _MM_CONFIG_PATH_CACHE
    if _MM_CONFIG_PATH_CACHE is not None:
        return _MM_CONFIG_PATH_CACHE
    here = Path(__file__).resolve()
    # kis_trader/utils/logger.py → kis_bot/
    project_root = here.parent.parent.parent
    _MM_CONFIG_PATH_CACHE = project_root / "mm_config.json"
    return _MM_CONFIG_PATH_CACHE


def _load_mm_channel_id(channel_alias: str) -> Optional[str]:
    cfg = atomic_load_json(_mm_config_path(), default={})
    try:
        return (cfg.get("channels") or {}).get(channel_alias)
    except Exception:
        return None


def msg_mm_multi(
    text: str,
    channel_aliases: Iterable[str],
    *,
    jitter: bool = True,
) -> bool:
    """
    동일 메시지를 여러 MM alias 로 발송 (중복 alias 는 1회만).
    jitter=True 이면 첫 채널에만 슬립 — 체결 알림은 jitter=False 권장.
    """
    seen: set[str] = set()
    any_ok = False
    first = True
    for raw in channel_aliases:
        alias = str(raw or "").strip()
        if not alias or alias in seen:
            continue
        seen.add(alias)
        if msg_mm(text, channel_alias=alias, jitter=jitter and first):
            any_ok = True
        first = False
    return any_ok


def msg_mm_strategy(
    text: str,
    strategy_channel_alias: str,
    *,
    jitter: bool = False,
) -> bool:
    """
    전략 채널 + (옵션) 통합 채널 이중 발송.
    MM_DUAL_CHANNEL_ENABLED=true 일 때 MATTERMOST_CHANNEL 도 함께 전송 (alias 같으면 1회).
    """
    strat_ch = str(strategy_channel_alias or "").strip() or str(
        get_env_from_db("MATTERMOST_CHANNEL", "stock")
    )
    aliases = [strat_ch]
    if get_env_bool("MM_DUAL_CHANNEL_ENABLED", False):
        unified = str(get_env_from_db("MATTERMOST_CHANNEL", "stock") or "stock").strip()
        if unified and unified != strat_ch:
            aliases.append(unified)
    return msg_mm_multi(text, aliases, jitter=jitter)


def msg_mm(text: str, channel_alias: Optional[str] = None, jitter: bool = True) -> bool:
    """
    Mattermost 메시지 전송.
    Args:
        channel_alias: mm_config.json 의 channels 키. None 이면 MATTERMOST_CHANNEL 사용.
        jitter: True 면 0.3~1.2s 랜덤 슬립 (버스트 차단 방지). 실시간 매매 로직에서는 False 권장.
    """
    alias = channel_alias or get_env_from_db("MATTERMOST_CHANNEL", "stock")
    server_url = get_env_from_db("MM_SERVER_URL", "https://mattermost.hoonfam.org")
    bot_token = get_env_from_db("MM_BOT_TOKEN_", "").strip()
    cid = _load_mm_channel_id(alias)
    if not cid or not bot_token:
        logging.getLogger("kis_trader.logger").debug(
            "[MM 스킵] alias=%s cid=%s", alias, cid
        )
        return False
    if jitter:
        time.sleep(random.uniform(0.3, 1.2))
    try:
        r = requests.post(
            f"{server_url}/api/v4/posts",
            headers={
                "Authorization": f"Bearer {bot_token}",
                "Content-Type": "application/json",
            },
            json={"channel_id": cid, "message": text},
            timeout=5,
        )
        return r.status_code < 400
    except Exception as e:
        logging.getLogger("kis_trader.logger").debug("MM 발송 실패: %s", e)
        return False


def msg_tg(text: str, jitter: bool = True) -> bool:
    """
    Telegram Bot 알림. TELEGRAM_BOT_TOKEN / TELEGRAM_CHAT_ID 둘 다 설정돼 있을 때만 전송.
    실시간 매매 루프에서는 jitter=False 로 호출.
    """
    token = get_env_from_db("TELEGRAM_BOT_TOKEN", "").strip()
    chat_id = get_env_from_db("TELEGRAM_CHAT_ID", "").strip()
    if not token or not chat_id:
        return False
    if jitter:
        time.sleep(random.uniform(0.3, 1.2))
    try:
        r = requests.post(
            f"https://api.telegram.org/bot{token}/sendMessage",
            json={"chat_id": chat_id, "text": text, "parse_mode": "Markdown"},
            timeout=5,
        )
        return r.status_code < 400
    except Exception as e:
        logging.getLogger("kis_trader.logger").debug("TG 발송 실패: %s", e)
        return False
