"""
╔══════════════════════════════════════════════════════════════════════════════╗
║  Alaska 2026, Dongil Vision                                                  ║
║  Processor Survivability and Analysis Platform                               ║
╠══════════════════════════════════════════════════════════════════════════════╣
║  Module  : task_monitor_ext_link.py                                          ║
║  Version : 1.0.0                                                             ║
║  Date    : 2026-02-05                                                        ║
╚══════════════════════════════════════════════════════════════════════════════╝
"""

import json
import smtplib
import socket
import threading
import time
import urllib.request
import urllib.error
from datetime import datetime
from email.mime.text import MIMEText
from email.mime.multipart import MIMEMultipart
from typing import Dict, List, Any, Optional, Callable, TYPE_CHECKING

if TYPE_CHECKING:
    from .task_manager import TaskManager


class SlackSender:
    """Slack Webhook 발송"""

    def __init__(self, webhook_url: str, channel: str = '', username: str = 'Alaska Monitor'):
        self._url = webhook_url
        self._channel = channel
        self._username = username

    def send_message(self, text: str) -> bool:
        """텍스트 메시지 발송"""
        payload = {'text': text, 'username': self._username}
        if self._channel:
            payload['channel'] = self._channel
        return self._post(payload)

    def send_blocks(self, blocks: List[Dict]) -> bool:
        """Block Kit 메시지 발송"""
        payload = {'blocks': blocks, 'username': self._username}
        if self._channel:
            payload['channel'] = self._channel
        return self._post(payload)

    def send_report(self, report: Dict[str, Any]) -> bool:
        """Health Report 발송 (Block Kit 형식)"""
        blocks = self._format_report_blocks(report)
        return self.send_blocks(blocks)

    def send_alert(self, alert: Dict[str, Any]) -> bool:
        """Alert 발송"""
        blocks = self._format_alert_blocks(alert)
        return self.send_blocks(blocks)

    def _post(self, payload: Dict) -> bool:
        """Webhook POST 요청"""
        if not self._url:
            return False
        try:
            data = json.dumps(payload).encode('utf-8')
            req = urllib.request.Request(
                self._url, data=data,
                headers={'Content-Type': 'application/json'}
            )
            with urllib.request.urlopen(req, timeout=10) as resp:
                return resp.status == 200
        except Exception:
            return False

    def _format_report_blocks(self, report: Dict) -> List[Dict]:
        """Health Report를 Slack Block Kit 형식으로 변환"""
        sys_info = report.get('system', {})
        cpu = report.get('cpu', {})
        mem = report.get('memory', {})
        tasks = report.get('tasks', {})
        rmi = report.get('rmi', {})

        blocks = [
            {'type': 'header', 'text': {'type': 'plain_text', 'text': 'Alaska Health Report'}},
            {'type': 'section', 'fields': [
                {'type': 'mrkdwn', 'text': f"*Hostname:*\n{sys_info.get('hostname', 'N/A')}"},
                {'type': 'mrkdwn', 'text': f"*Uptime:*\n{sys_info.get('uptime_str', 'N/A')}"},
            ]},
            {'type': 'section', 'fields': [
                {'type': 'mrkdwn', 'text': f"*CPU:*\n{cpu.get('usage_percent', 0):.1f}%"},
                {'type': 'mrkdwn', 'text': f"*Memory:*\n{mem.get('usage_percent', 0):.1f}%"},
            ]},
            {'type': 'divider'},
            {'type': 'section', 'fields': [
                {'type': 'mrkdwn', 'text': f"*Tasks:*\n{tasks.get('active', 0)}/{tasks.get('total', 0)} active"},
                {'type': 'mrkdwn', 'text': f"*Restarts:*\n{tasks.get('restarts', 0)}"},
            ]},
            {'type': 'section', 'fields': [
                {'type': 'mrkdwn', 'text': f"*RMI Calls:*\n{rmi.get('total_calls', 0):,}"},
                {'type': 'mrkdwn', 'text': f"*RMI Fails:*\n{rmi.get('failed_calls', 0)}"},
            ]},
            {'type': 'context', 'elements': [
                {'type': 'mrkdwn', 'text': f"Report Time: {report.get('timestamp', 'N/A')}"}
            ]}
        ]
        return blocks

    def _format_alert_blocks(self, alert: Dict) -> List[Dict]:
        """Alert를 Slack Block Kit 형식으로 변환"""
        alert_type = alert.get('type', 'Unknown')
        emoji = {'task_restart': ':warning:', 'rmi_fail': ':x:', 'signal_error': ':exclamation:'}.get(alert_type, ':bell:')
        blocks = [
            {'type': 'header', 'text': {'type': 'plain_text', 'text': f'{emoji} Alaska Alert: {alert_type}'}},
            {'type': 'section', 'fields': [
                {'type': 'mrkdwn', 'text': f"*Time:*\n{alert.get('time', 'N/A')}"},
                {'type': 'mrkdwn', 'text': f"*Hostname:*\n{alert.get('hostname', 'N/A')}"},
            ]},
        ]
        if 'task' in alert:
            blocks.append({'type': 'section', 'fields': [
                {'type': 'mrkdwn', 'text': f"*Task:*\n{alert['task']}"},
                {'type': 'mrkdwn', 'text': f"*Detail:*\n{alert.get('detail', 'N/A')}"},
            ]})
        if 'message' in alert:
            blocks.append({'type': 'section', 'text': {'type': 'mrkdwn', 'text': alert['message']}})
        return blocks


class EmailSender:
    """SMTP Email 발송"""

    def __init__(self, config: Dict[str, Any]):
        self._host = config.get('smtp_host', 'localhost')
        self._port = config.get('smtp_port', 587)
        self._user = config.get('smtp_user', '')
        self._password = config.get('smtp_password', '')
        self._tls = config.get('smtp_tls', True)
        self._from = config.get('from_addr', '')
        self._to = config.get('to_addrs', [])
        if isinstance(self._to, str):
            self._to = [a.strip() for a in self._to.split(',')]

    def send(self, subject: str, body: str, html: bool = False) -> bool:
        """메일 발송"""
        if not self._to or not self._from:
            return False
        try:
            msg = MIMEMultipart('alternative')
            msg['Subject'] = subject
            msg['From'] = self._from
            msg['To'] = ', '.join(self._to)
            content_type = 'html' if html else 'plain'
            msg.attach(MIMEText(body, content_type, 'utf-8'))

            with smtplib.SMTP(self._host, self._port, timeout=30) as server:
                if self._tls:
                    server.starttls()
                if self._user and self._password:
                    server.login(self._user, self._password)
                server.sendmail(self._from, self._to, msg.as_string())
            return True
        except Exception:
            return False

    def send_report(self, report: Dict[str, Any]) -> bool:
        """Health Report 메일 발송"""
        subject = f"[Alaska] Health Report - {report.get('timestamp', '')}"
        html_body = self._format_report_html(report)
        return self.send(subject, html_body, html=True)

    def send_alert(self, alert: Dict[str, Any]) -> bool:
        """Alert 메일 발송"""
        alert_type = alert.get('type', 'Unknown')
        subject = f"[Alaska Alert] {alert_type} - {alert.get('time', '')}"
        html_body = self._format_alert_html(alert)
        return self.send(subject, html_body, html=True)

    def _format_report_html(self, report: Dict) -> str:
        """Report를 HTML로 변환"""
        sys_info = report.get('system', {})
        cpu = report.get('cpu', {})
        mem = report.get('memory', {})
        tasks = report.get('tasks', {})
        rmi = report.get('rmi', {})
        return f"""
        <html><body style="font-family: Arial, sans-serif;">
        <h2>Alaska Health Report</h2>
        <p><strong>Report Time:</strong> {report.get('timestamp', 'N/A')}</p>
        <p><strong>Hostname:</strong> {sys_info.get('hostname', 'N/A')} |
           <strong>Uptime:</strong> {sys_info.get('uptime_str', 'N/A')}</p>
        <hr>
        <h3>System Status</h3>
        <table border="1" cellpadding="5" style="border-collapse: collapse;">
            <tr><td>CPU Usage</td><td>{cpu.get('usage_percent', 0):.1f}%</td></tr>
            <tr><td>Memory</td><td>{mem.get('used_gb', 0):.1f} / {mem.get('total_gb', 0):.1f} GB ({mem.get('usage_percent', 0):.1f}%)</td></tr>
        </table>
        <h3>Task Status</h3>
        <table border="1" cellpadding="5" style="border-collapse: collapse;">
            <tr><td>Total Tasks</td><td>{tasks.get('total', 0)}</td></tr>
            <tr><td>Active Tasks</td><td>{tasks.get('active', 0)}</td></tr>
            <tr><td>Restarts</td><td>{tasks.get('restarts', 0)}</td></tr>
        </table>
        <h3>RMI Statistics</h3>
        <table border="1" cellpadding="5" style="border-collapse: collapse;">
            <tr><td>Total Calls</td><td>{rmi.get('total_calls', 0):,}</td></tr>
            <tr><td>Failed Calls</td><td>{rmi.get('failed_calls', 0)}</td></tr>
        </table>
        </body></html>
        """

    def _format_alert_html(self, alert: Dict) -> str:
        """Alert를 HTML로 변환"""
        return f"""
        <html><body style="font-family: Arial, sans-serif;">
        <h2 style="color: #c00;">Alaska Alert: {alert.get('type', 'Unknown')}</h2>
        <p><strong>Time:</strong> {alert.get('time', 'N/A')}</p>
        <p><strong>Hostname:</strong> {alert.get('hostname', 'N/A')}</p>
        <p><strong>Task:</strong> {alert.get('task', 'N/A')}</p>
        <p><strong>Detail:</strong> {alert.get('detail', 'N/A')}</p>
        {f"<p>{alert.get('message', '')}</p>" if alert.get('message') else ''}
        </body></html>
        """


class WebhookSender:
    """Custom HTTP Webhook 호출"""

    def __init__(self, config: Dict[str, Any]):
        self._url = config.get('url', '')
        self._method = config.get('method', 'POST').upper()
        self._headers = config.get('headers', {})
        self._timeout = config.get('timeout', 10)

    def send(self, data: Dict[str, Any]) -> bool:
        """Webhook 호출"""
        if not self._url:
            return False
        try:
            body = json.dumps(data).encode('utf-8')
            headers = {'Content-Type': 'application/json', **self._headers}
            req = urllib.request.Request(self._url, data=body, headers=headers, method=self._method)
            with urllib.request.urlopen(req, timeout=self._timeout) as resp:
                return 200 <= resp.status < 300
        except Exception:
            return False

    def send_report(self, report: Dict[str, Any]) -> bool:
        """Health Report 발송"""
        return self.send({'type': 'health_report', 'data': report})

    def send_alert(self, alert: Dict[str, Any]) -> bool:
        """Alert 발송"""
        return self.send({'type': 'alert', 'data': alert})


class HealthReporter:
    """Health Report 데이터 수집 및 포맷팅"""

    def __init__(self, manager: Optional['TaskManager'] = None):
        self._manager = manager
        self._last_report_time: Optional[datetime] = None
        self._restart_count_since_last = 0

    def collect(self) -> Dict[str, Any]:
        """현재 시스템 상태 수집"""
        from .task_monitor_sysinfo import HwInfoCollector

        now = datetime.now()
        hw = HwInfoCollector.collect_summary()
        tasks_info = self._collect_tasks()
        rmi_info = self._collect_rmi()

        report = {
            'timestamp': now.strftime('%Y-%m-%d %H:%M:%S'),
            'system': hw.get('system', {}),
            'cpu': hw.get('cpu', {}),
            'memory': hw.get('memory', {}),
            'storage': hw.get('storage', []),
            'tasks': tasks_info,
            'rmi': rmi_info,
        }
        self._last_report_time = now
        self._restart_count_since_last = 0
        return report

    def _collect_tasks(self) -> Dict[str, Any]:
        """Task 정보 수집"""
        if not self._manager:
            return {'total': 0, 'active': 0, 'restarts': self._restart_count_since_last}
        try:
            task_info = self._manager.get_task_info()
            total = len(task_info)
            active = sum(1 for t in task_info.values() if t.get('alive', False))
            return {'total': total, 'active': active, 'restarts': self._restart_count_since_last}
        except Exception:
            return {'total': 0, 'active': 0, 'restarts': self._restart_count_since_last}

    def _collect_rmi(self) -> Dict[str, Any]:
        """RMI 통계 수집"""
        if not self._manager:
            return {'total_calls': 0, 'failed_calls': 0}
        try:
            stats = self._manager.get_rmi_stats() if hasattr(self._manager, 'get_rmi_stats') else {}
            return {
                'total_calls': stats.get('total_calls', 0),
                'failed_calls': stats.get('failed_calls', 0),
                'avg_response_ms': stats.get('avg_response_ms', 0),
            }
        except Exception:
            return {'total_calls': 0, 'failed_calls': 0}

    def increment_restart(self):
        """Task 재시작 카운트 증가"""
        self._restart_count_since_last += 1

    def format_text(self, report: Dict[str, Any]) -> str:
        """텍스트 형식으로 포맷"""
        sys = report.get('system', {})
        cpu = report.get('cpu', {})
        mem = report.get('memory', {})
        tasks = report.get('tasks', {})
        rmi = report.get('rmi', {})

        lines = [
            "Alaska Health Report",
            "=" * 50,
            f"Report Time: {report.get('timestamp', 'N/A')}",
            f"Hostname: {sys.get('hostname', 'N/A')}",
            f"Uptime: {sys.get('uptime_str', 'N/A')}",
            "",
            "System Status",
            "-" * 50,
            f"CPU Usage: {cpu.get('usage_percent', 0):.1f}%",
            f"Memory: {mem.get('used_gb', 0):.1f} / {mem.get('total_gb', 0):.1f} GB ({mem.get('usage_percent', 0):.1f}%)",
            "",
            "Task Status",
            "-" * 50,
            f"Total Tasks: {tasks.get('total', 0)}",
            f"Active Tasks: {tasks.get('active', 0)}",
            f"Restarts: {tasks.get('restarts', 0)}",
            "",
            "RMI Statistics",
            "-" * 50,
            f"Total Calls: {rmi.get('total_calls', 0):,}",
            f"Failed Calls: {rmi.get('failed_calls', 0)}",
        ]
        return '\n'.join(lines)


class ReportScheduler:
    """Health Report 스케줄러"""

    def __init__(self, schedule: List[str], callback: Callable[[], None]):
        self._schedule = schedule  # ["09:00", "18:00"]
        self._callback = callback
        self._running = False
        self._thread: Optional[threading.Thread] = None
        self._last_run: Optional[str] = None

    def start(self):
        """스케줄러 시작"""
        if self._running:
            return
        self._running = True
        self._thread = threading.Thread(target=self._run, daemon=True, name='ReportScheduler')
        self._thread.start()

    def stop(self):
        """스케줄러 중지"""
        self._running = False

    def _run(self):
        """스케줄러 루프"""
        while self._running:
            now = datetime.now()
            current_time = now.strftime("%H:%M")

            if current_time in self._schedule and current_time != self._last_run:
                try:
                    self._callback()
                except Exception:
                    pass
                self._last_run = current_time

            # 분이 바뀌면 last_run 리셋
            if self._last_run and current_time != self._last_run:
                self._last_run = None

            time.sleep(30)


class ExternalLinkManager:
    """외부연동 통합 관리자"""

    def __init__(self, gconfig, manager: Optional['TaskManager'] = None):
        self._gconfig = gconfig
        self._manager = manager
        self._config: Dict[str, Any] = {}
        self._scheduler: Optional[ReportScheduler] = None
        self._reporter = HealthReporter(manager)
        self._load_config()

    def _load_config(self):
        """설정 로드"""
        try:
            self._config = self._gconfig.get('external_link', {})
        except Exception:
            self._config = {}

    def _save_config(self):
        """설정 저장"""
        try:
            self._gconfig.set('external_link', self._config)
        except Exception:
            pass

    def start_scheduler(self):
        """Health Report 스케줄러 시작"""
        hr_config = self._config.get('health_report', {})
        if not hr_config.get('enabled', False):
            return
        schedule = hr_config.get('schedule', [])
        if not schedule:
            return
        self._scheduler = ReportScheduler(schedule, self._scheduled_report)
        self._scheduler.start()

    def stop_scheduler(self):
        """스케줄러 중지"""
        if self._scheduler:
            self._scheduler.stop()
            self._scheduler = None

    def _scheduled_report(self):
        """스케줄된 리포트 발송"""
        hr_config = self._config.get('health_report', {})
        targets = hr_config.get('targets', ['slack'])
        self.send_report(targets)

    def send_report(self, targets: Optional[List[str]] = None) -> Dict[str, bool]:
        """Health Report 발송"""
        if targets is None:
            hr_config = self._config.get('health_report', {})
            targets = hr_config.get('targets', ['slack'])

        report = self._reporter.collect()
        results = {}

        if 'slack' in targets:
            results['slack'] = self._send_to_slack(report, 'report')
        if 'email' in targets:
            results['email'] = self._send_to_email(report, 'report')
        if 'webhook' in targets:
            results['webhook'] = self._send_to_webhook(report, 'report')

        return results

    def send_alert(self, alert_type: str, data: Dict[str, Any]) -> Dict[str, bool]:
        """Alert 발송"""
        alert_config = self._config.get('alert', {})

        # Alert 조건 확인
        if alert_type == 'task_restart' and not alert_config.get('on_task_restart', False):
            return {}
        if alert_type == 'signal_error' and not alert_config.get('on_signal_error', False):
            return {}
        if alert_type == 'rmi_fail':
            threshold = alert_config.get('on_rmi_fail_threshold', 0)
            if threshold <= 0 or data.get('fail_count', 0) < threshold:
                return {}

        alert = {
            'type': alert_type,
            'time': datetime.now().strftime('%Y-%m-%d %H:%M:%S'),
            'hostname': socket.gethostname(),
            **data
        }

        results = {}
        slack_cfg = self._config.get('slack', {})
        if slack_cfg.get('enabled', False):
            results['slack'] = self._send_to_slack(alert, 'alert')

        email_cfg = self._config.get('email', {})
        if email_cfg.get('enabled', False):
            results['email'] = self._send_to_email(alert, 'alert')

        webhook_cfg = self._config.get('webhook', {})
        if webhook_cfg.get('enabled', False):
            results['webhook'] = self._send_to_webhook(alert, 'alert')

        return results

    def _send_to_slack(self, data: Dict, msg_type: str) -> bool:
        """Slack으로 발송"""
        cfg = self._config.get('slack', {})
        if not cfg.get('enabled', False):
            return False
        sender = SlackSender(cfg.get('webhook_url', ''), cfg.get('channel', ''), cfg.get('username', 'Alaska Monitor'))
        if msg_type == 'report':
            return sender.send_report(data)
        else:
            return sender.send_alert(data)

    def _send_to_email(self, data: Dict, msg_type: str) -> bool:
        """Email로 발송"""
        cfg = self._config.get('email', {})
        if not cfg.get('enabled', False):
            return False
        sender = EmailSender(cfg)
        if msg_type == 'report':
            return sender.send_report(data)
        else:
            return sender.send_alert(data)

    def _send_to_webhook(self, data: Dict, msg_type: str) -> bool:
        """Webhook으로 발송"""
        cfg = self._config.get('webhook', {})
        if not cfg.get('enabled', False):
            return False
        sender = WebhookSender(cfg)
        if msg_type == 'report':
            return sender.send_report(data)
        else:
            return sender.send_alert(data)

    def test_slack(self) -> Dict[str, Any]:
        """Slack 테스트"""
        cfg = self._config.get('slack', {})
        if not cfg.get('webhook_url'):
            return {'success': False, 'error': 'Webhook URL not configured'}
        sender = SlackSender(cfg.get('webhook_url', ''), cfg.get('channel', ''), cfg.get('username', 'Alaska Monitor'))
        success = sender.send_message('Alaska Monitor: Test message')
        return {'success': success, 'error': None if success else 'Failed to send'}

    def test_email(self) -> Dict[str, Any]:
        """Email 테스트"""
        cfg = self._config.get('email', {})
        if not cfg.get('smtp_host'):
            return {'success': False, 'error': 'SMTP host not configured'}
        sender = EmailSender(cfg)
        success = sender.send('[Alaska] Test Email', 'This is a test email from Alaska Monitor.')
        return {'success': success, 'error': None if success else 'Failed to send'}

    def test_webhook(self) -> Dict[str, Any]:
        """Webhook 테스트"""
        cfg = self._config.get('webhook', {})
        if not cfg.get('url'):
            return {'success': False, 'error': 'Webhook URL not configured'}
        sender = WebhookSender(cfg)
        success = sender.send({'type': 'test', 'message': 'Alaska Monitor test'})
        return {'success': success, 'error': None if success else 'Failed to send'}

    def get_config(self) -> Dict[str, Any]:
        """설정 조회 (민감 정보 마스킹)"""
        config = json.loads(json.dumps(self._config))  # Deep copy

        # Password 마스킹
        if 'email' in config and config['email'].get('smtp_password'):
            config['email']['smtp_password'] = '••••••••'

        # Slack webhook URL 부분 마스킹
        if 'slack' in config and config['slack'].get('webhook_url'):
            url = config['slack']['webhook_url']
            if '/services/' in url:
                parts = url.split('/services/')
                tokens = parts[1].split('/') if len(parts) > 1 else []
                if len(tokens) >= 3:
                    config['slack']['webhook_url'] = f"{parts[0]}/services/{tokens[0]}/****/****"

        # Webhook Authorization 마스킹
        if 'webhook' in config:
            headers = config['webhook'].get('headers', {})
            if 'Authorization' in headers:
                config['webhook']['headers']['Authorization'] = 'Bearer ••••••••'

        return config

    def set_config(self, config: Dict[str, Any]) -> bool:
        """설정 저장"""
        try:
            # 마스킹된 값은 기존 값 유지
            if 'email' in config:
                if config['email'].get('smtp_password') == '••••••••':
                    config['email']['smtp_password'] = self._config.get('email', {}).get('smtp_password', '')

            if 'slack' in config:
                if '****' in config['slack'].get('webhook_url', ''):
                    config['slack']['webhook_url'] = self._config.get('slack', {}).get('webhook_url', '')

            if 'webhook' in config:
                headers = config['webhook'].get('headers', {})
                if headers.get('Authorization') == 'Bearer ••••••••':
                    orig_headers = self._config.get('webhook', {}).get('headers', {})
                    config['webhook']['headers']['Authorization'] = orig_headers.get('Authorization', '')

            self._config = config
            self._save_config()

            # 스케줄러 재시작
            self.stop_scheduler()
            self.start_scheduler()

            return True
        except Exception:
            return False

    def notify_task_restart(self, task_id: str, restart_count: int, reason: str = ''):
        """Task 재시작 알림"""
        self._reporter.increment_restart()
        self.send_alert('task_restart', {
            'task': task_id,
            'restart_count': restart_count,
            'detail': reason or 'Task restarted'
        })

    def notify_rmi_fail(self, fail_count: int, last_error: str = ''):
        """RMI 실패 알림"""
        self.send_alert('rmi_fail', {
            'fail_count': fail_count,
            'detail': last_error or 'RMI call failed'
        })

    def notify_signal_error(self, signal_name: str, error: str):
        """Signal 에러 알림"""
        self.send_alert('signal_error', {
            'signal': signal_name,
            'detail': error
        })
