"""
╔══════════════════════════════════════════════════════════════════════════════╗
║  ALASKA v2.0 - Task Log                                                      ║
║  Central Logging System                                                      ║
╠══════════════════════════════════════════════════════════════════════════════╣
║  Version : 2.0.1                                                             ║
║  Date    : 2026-02-07                                                        ║
╠══════════════════════════════════════════════════════════════════════════════╣
║  Summary:                                                                    ║
║    TASK 로그를 중앙 LogTask로 집중 관리                                      ║
║    - 콘솔/파일/알림(Slack, Email) 출력 지원                                  ║
║    - 레벨별 필터링 (DEBUG, INFO, WARNING, ERROR, CRITICAL)                   ║
║    - 일별/크기 기반 로그 로테이션                                            ║
╠══════════════════════════════════════════════════════════════════════════════╣
║  Classes:                                                                    ║
║    LogRecord  - 로그 레코드 데이터 클래스                                    ║
║    RmiLogger  - TaskRuntime 내장 로거 (self.runtime.log)                     ║
║    LogTask    - 중앙 로그 수집 Task                                          ║
╚══════════════════════════════════════════════════════════════════════════════╝
"""

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

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

_DEFAULT_BUFFER_SIZE = 10
_DEFAULT_FLUSH_INTERVAL = 1.0
_LOG_POLL_TIMEOUT = 0.1


# ─────────────────────────────────────────────────────────────────────────────
# LogRecord
# ─────────────────────────────────────────────────────────────────────────────

@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 = _DEFAULT_BUFFER_SIZE
        self._last_flush = time.time()
        self._flush_interval = _DEFAULT_FLUSH_INTERVAL

    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]:
        """최근 로그 조회"""
        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=_LOG_POLL_TIMEOUT)
            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}")
