"""
RMI 로그 시스템
===============
TASK에서 발생하는 로그를 중앙의 LogTask로 집중하여 통합 관리한다.

사용법:
    def run(self):
        log = self.runtime.log
        log.info("시작")
        log.exception("에러 발생")
"""

import os
import time
import queue
import traceback
from pathlib import Path
from dataclasses import dataclass, field
from datetime import datetime
from typing import List, Optional

from .task_decoration import rmi_task


# ─────────────────────────────────────────────────────────────────────────────
# 데이터 클래스
# ─────────────────────────────────────────────────────────────────────────────

LEVEL_MAP = {
    "DEBUG": 10,
    "INFO": 20,
    "WARNING": 30,
    "ERROR": 40,
    "CRITICAL": 50,
}


@dataclass
class LogRecord:
    """로그 레코드"""
    timestamp: float
    level: str
    task_name: str
    task_pid: int
    message: str

    @property
    def level_no(self) -> int:
        return LEVEL_MAP.get(self.level, 0)

    @property
    def date_str(self) -> str:
        return datetime.fromtimestamp(self.timestamp).strftime("%Y-%m-%d")

    @property
    def time_str(self) -> str:
        return datetime.fromtimestamp(self.timestamp).strftime("%H:%M:%S.%f")[:-3]

    def to_dict(self) -> dict:
        return {
            "ts": self.timestamp,
            "lv": self.level,
            "task": self.task_name,
            "pid": self.task_pid,
            "msg": self.message,
        }


# ─────────────────────────────────────────────────────────────────────────────
# RmiLogger (runtime.log)
# ─────────────────────────────────────────────────────────────────────────────

class RmiLogger:
    """TaskRuntime 내장 로거"""

    def __init__(self, runtime):
        self._runtime = runtime
        self._log_task = None
        self._buffer: List[LogRecord] = []
        self._buffer_size = 10
        self._last_flush = time.time()
        self._flush_interval = 1.0  # 초

    def _get_log_task(self):
        if self._log_task is None:
            try:
                self._log_task = self._runtime.get_client("log_task")
            except Exception:
                pass
        return self._log_task

    def _send(self, level: str, message: str):
        record = LogRecord(
            timestamp=time.time(),
            level=level,
            task_name=self._runtime.name,
            task_pid=os.getpid(),
            message=message,
        )
        self._buffer.append(record)

        # 버퍼 크기 또는 시간 기준 flush
        now = time.time()
        if len(self._buffer) >= self._buffer_size or (now - self._last_flush) > self._flush_interval:
            self._flush()

    def _flush(self):
        if not self._buffer:
            return

        log_task = self._get_log_task()
        if log_task:
            try:
                log_task.on_logs(self._buffer)
            except Exception:
                # 전송 실패 시 콘솔 출력
                for record in self._buffer:
                    self._print_local(record)
        else:
            # log_task 없으면 로컬 출력
            for record in self._buffer:
                self._print_local(record)

        self._buffer.clear()
        self._last_flush = time.time()

    def _print_local(self, record: LogRecord):
        """로컬 콘솔 출력 (fallback)"""
        print(f"[{record.date_str} {record.time_str}] [{record.level:5}] [{record.task_name}] {record.message}")

    def debug(self, msg: str):
        self._send("DEBUG", msg)

    def info(self, msg: str):
        self._send("INFO", msg)

    def warning(self, msg: str):
        self._send("WARNING", msg)

    def error(self, msg: str):
        self._send("ERROR", msg)

    def critical(self, msg: str):
        self._send("CRITICAL", msg)

    def exception(self, msg: str):
        """에러 + traceback 자동 첨부"""
        tb = traceback.format_exc()
        self._send("ERROR", f"{msg}\n{tb}")

    def flush(self):
        """수동 flush"""
        self._flush()


# ─────────────────────────────────────────────────────────────────────────────
# LogTask (중앙 로그 서비스)
# ─────────────────────────────────────────────────────────────────────────────

@rmi_task(name="log_task", mode="process")
class LogTask:
    """중앙 로그 수집 TASK"""

    def __init__(self):
        self._queue = queue.Queue()
        self._current_file = None
        self._current_date = None
        self._current_file_path = None
        self._file_seq = 0
        self._startup_time: float = time.time()  # 시스템 시작 시간

        # 알림 쿨다운 관리
        self._alert_cooldown: dict = {}  # {(task_name, level): last_alert_time}
        self._alert_counts: dict = {}    # {(task_name, level): count in window}
        self._alert_window_start: float = 0.0

        # config injection
        self.log_dir = "logs"
        self.console_level = "INFO"
        self.file_level = "DEBUG"
        self.alert_level = "ERROR"
        self.max_size_mb = 100
        self.max_files = 30

        # 알림 설정 (config injection)
        self.slack_webhook = None        # Slack webhook URL
        self.email_to = None             # Email 수신자
        self.email_from = None           # Email 발신자
        self.smtp_host = None            # SMTP 서버
        self.smtp_port = 587             # SMTP 포트
        self.smtp_user = None            # SMTP 사용자
        self.smtp_pass = None            # SMTP 비밀번호
        self.alert_cooldown_sec = 60     # 동일 알림 간격 (초)
        self.alert_aggregate_sec = 300   # 알림 집계 윈도우 (초)
        self.alert_aggregate_max = 10    # 집계 최대 개수

    def on_log(self, record: LogRecord):
        """단일 로그 수신 (RMI)"""
        self._queue.put(record)

    def on_logs(self, records: List[LogRecord]):
        """배치 로그 수신 (RMI)"""
        for record in records:
            self._queue.put(record)

    def get_recent_logs(self, count: int = 100, level: str = None, task_name: str = None, since_startup: bool = True) -> List[dict]:
        """최근 로그 조회 (RMI)

        Args:
            count: 조회할 로그 개수 (최대)
            level: 필터링할 레벨 (DEBUG, INFO, WARNING, ERROR, CRITICAL)
            task_name: 필터링할 TASK 이름
            since_startup: True면 시스템 시작 이후 로그만 조회 (기본값: True)

        Returns:
            로그 레코드 딕셔너리 리스트 (최신순)
        """
        import json

        log_path = Path(self.log_dir)
        if not log_path.exists():
            return []

        level_no = LEVEL_MAP.get(level, 0) if level else 0
        results = []

        # 최신 파일부터 역순으로 읽기
        log_files = sorted(log_path.glob("*.log"), key=lambda f: f.stat().st_mtime, reverse=True)

        for log_file in log_files:
            if len(results) >= count:
                break

            try:
                with open(log_file, "r", encoding="utf-8") as f:
                    lines = f.readlines()

                # 파일 내에서 역순으로 읽기
                for line in reversed(lines):
                    if len(results) >= count:
                        break

                    try:
                        record = json.loads(line.strip())

                        # 시스템 시작 이후 로그만 필터링
                        if since_startup:
                            ts = record.get("ts", 0)
                            if ts < self._startup_time:
                                continue

                        # 레벨 필터링
                        if level_no > 0:
                            rec_level_no = LEVEL_MAP.get(record.get("lv", ""), 0)
                            if rec_level_no < level_no:
                                continue

                        # TASK 필터링
                        if task_name and record.get("task") != task_name:
                            continue

                        results.append(record)
                    except json.JSONDecodeError:
                        continue
            except Exception:
                continue

        return results

    def rmi_loop(self):
        log_path = Path(self.log_dir)
        log_path.mkdir(parents=True, exist_ok=True)

        console_level_no = LEVEL_MAP.get(self.console_level, 20)
        file_level_no = LEVEL_MAP.get(self.file_level, 10)
        alert_level_no = LEVEL_MAP.get(self.alert_level, 40)

        self.runtime.log.info(f"LogTask started: dir={self.log_dir}, console={self.console_level}, file={self.file_level}")

        while not self.runtime.should_stop():
            try:
                record = self._queue.get(timeout=0.1)
            except queue.Empty:
                continue

            # 콘솔 출력
            if record.level_no >= console_level_no:
                self._print_console(record)

            # 파일 기록
            if record.level_no >= file_level_no:
                self._write_file(record, log_path)

            # 알림 발송
            if record.level_no >= alert_level_no:
                self._send_alert(record)

        self.runtime.log.info("LogTask stopped")

    def _print_console(self, record: LogRecord):
        """콘솔 출력"""
        level_colors = {
            "DEBUG": "\033[90m",    # gray
            "INFO": "\033[0m",      # default
            "WARNING": "\033[93m",  # yellow
            "ERROR": "\033[91m",    # red
            "CRITICAL": "\033[95m", # magenta
        }
        reset = "\033[0m"
        color = level_colors.get(record.level, "")

        print(f"{color}[{record.date_str} {record.time_str}] [{record.level:5}] [{record.task_name:12}] {record.message}{reset}")

    def _write_file(self, record: LogRecord, log_path: Path):
        """파일 기록 (일별 + 크기 기반 로테이션)"""
        import json

        # 날짜 변경 시 새 파일
        if self._current_date != record.date_str:
            if self._current_file:
                self._current_file.close()
            self._current_date = record.date_str
            self._file_seq = 0
            self._current_file_path = log_path / f"{record.date_str}.log"
            self._current_file = open(self._current_file_path, "a", encoding="utf-8")
            self._cleanup_old_files(log_path)

        # 크기 기반 로테이션 체크
        if self._current_file_path and self._current_file_path.exists():
            size_mb = self._current_file_path.stat().st_size / (1024 * 1024)
            if size_mb >= self.max_size_mb:
                self._current_file.close()
                self._file_seq += 1
                self._current_file_path = log_path / f"{record.date_str}_{self._file_seq:03d}.log"
                self._current_file = open(self._current_file_path, "a", encoding="utf-8")
                self._cleanup_old_files(log_path)

        # JSON Lines 형식
        line = json.dumps(record.to_dict(), ensure_ascii=False)
        self._current_file.write(line + "\n")
        self._current_file.flush()

    def _cleanup_old_files(self, log_path: Path):
        """오래된 로그 파일 삭제"""
        log_files = sorted(log_path.glob("*.log"), key=lambda f: f.stat().st_mtime, reverse=True)
        for old_file in log_files[self.max_files:]:
            try:
                old_file.unlink()
            except Exception:
                pass

    def _send_alert(self, record: LogRecord):
        """알림 발송 (Slack/Email)"""
        now = time.time()
        key = (record.task_name, record.level)

        # 쿨다운 체크
        last_alert = self._alert_cooldown.get(key, 0)
        if now - last_alert < self.alert_cooldown_sec:
            # 집계 카운트 증가
            self._alert_counts[key] = self._alert_counts.get(key, 0) + 1
            return

        # 집계 윈도우 체크 (일정 기간 동안 집계 후 요약 알림)
        if now - self._alert_window_start > self.alert_aggregate_sec:
            self._alert_window_start = now
            self._alert_counts.clear()

        # 집계 최대치 도달 시 요약 알림
        count = self._alert_counts.get(key, 0)
        if count >= self.alert_aggregate_max:
            record = LogRecord(
                timestamp=now,
                level=record.level,
                task_name=record.task_name,
                task_pid=record.task_pid,
                message=f"[집계] {count}건의 {record.level} 발생 (최근 {self.alert_aggregate_sec}초)",
            )
            self._alert_counts[key] = 0

        # 알림 발송
        self._alert_cooldown[key] = now

        # Slack 알림
        if self.slack_webhook:
            self._send_slack(record)

        # Email 알림
        if self.email_to and self.smtp_host:
            self._send_email(record)

    def _send_slack(self, record: LogRecord):
        """Slack 알림 발송"""
        import urllib.request
        import json

        emoji = {"ERROR": "🚨", "CRITICAL": "🔥"}.get(record.level, "⚠️")
        color = {"ERROR": "#FF0000", "CRITICAL": "#8B0000"}.get(record.level, "#FFA500")

        payload = {
            "attachments": [{
                "color": color,
                "blocks": [
                    {
                        "type": "header",
                        "text": {"type": "plain_text", "text": f"{emoji} [{record.level}] {record.task_name}"}
                    },
                    {
                        "type": "section",
                        "text": {"type": "mrkdwn", "text": f"```{record.message[:1000]}```"}
                    },
                    {
                        "type": "context",
                        "elements": [
                            {"type": "mrkdwn", "text": f"*Time:* {record.date_str} {record.time_str}"},
                            {"type": "mrkdwn", "text": f"*PID:* {record.task_pid}"}
                        ]
                    }
                ]
            }]
        }

        try:
            data = json.dumps(payload).encode("utf-8")
            req = urllib.request.Request(
                self.slack_webhook,
                data=data,
                headers={"Content-Type": "application/json"}
            )
            urllib.request.urlopen(req, timeout=5)
        except Exception as e:
            print(f"[LogTask] Slack alert failed: {e}")

    def _send_email(self, record: LogRecord):
        """Email 알림 발송"""
        import smtplib
        from email.mime.text import MIMEText

        subject = f"[{record.level}] {record.task_name} - Alert"
        body = f"""
로그 알림
=========

Level: {record.level}
Task: {record.task_name}
Time: {record.date_str} {record.time_str}
PID: {record.task_pid}

Message:
{record.message}
"""

        msg = MIMEText(body, "plain", "utf-8")
        msg["Subject"] = subject
        msg["From"] = self.email_from or self.smtp_user
        msg["To"] = self.email_to

        try:
            with smtplib.SMTP(self.smtp_host, self.smtp_port) as server:
                server.starttls()
                if self.smtp_user and self.smtp_pass:
                    server.login(self.smtp_user, self.smtp_pass)
                server.send_message(msg)
        except Exception as e:
            print(f"[LogTask] Email alert failed: {e}")
