<?php
// =============================================================================
// dashboard.php - SHT30 온습도 모니터링 대시보드
// cafe24 웹호스팅에서 직접 브라우저로 접근하는 관리 페이지
//
// 온습도 임계 경보(고온/저온/고습/저습)와 장비 오프라인/복구를 모니터링한다.
// 임계 판정과 SMS
// 발송은 서버(api/sensor_data.php + config.php METRIC_* 상수)에서 수행하고,
// 이 대시보드는 그 결과(sensor_metric/sensor_status/sms_log)를 표시한다.
// =============================================================================

require_once __DIR__ . '/config.php';
require_once __DIR__ . '/admin_security.php';
require_once __DIR__ . '/cron_heartbeat.php';
require_once __DIR__ . '/ops_checks.php';

// ── 세션 인증 ─────────────────────────────────────────────
ini_set('session.cookie_httponly', 1);
ini_set('session.cookie_secure', 1);
ini_set('session.cookie_samesite', 'Strict');
ini_set('session.use_strict_mode', 1);
session_start();

if (empty($_SESSION['csrf_token'])) {
    $_SESSION['csrf_token'] = bin2hex(random_bytes(32));
}

// 로그아웃 처리
if (isset($_GET['logout'])) {
    admin_audit_log('logout', $_SESSION['username'] ?? ADMIN_USER, true);
    session_destroy();
    header('Location: login.php');
    exit;
}

// 세션 확인
if (empty($_SESSION['logged_in']) || $_SESSION['logged_in'] !== true) {
    header('Location: login.php');
    exit;
}

// 세션 타임아웃 확인
if (isset($_SESSION['login_time']) && (time() - $_SESSION['login_time']) > SESSION_TIMEOUT) {
    session_destroy();
    header('Location: login.php?expired');
    exit;
}

// 활동 시 로그인 시간 갱신
$_SESSION['login_time'] = time();

// ── Heartbeat: 오프라인 센서 체크 (HEARTBEAT_CHECK_INTERVAL_SEC 쓰로틀) ──────
check_offline_sensors_throttled();

$db = get_db();

// ── 온습도 경보 상태 라벨/표시 헬퍼 ─────────────────────────
// metric_status: normal|high_temp|low_temp|high_humidity|low_humidity
function metric_status_label(string $status): string {
    $labels = [
        'normal'        => '정상',
        'high_temp'     => '고온',
        'low_temp'      => '저온',
        'high_humidity' => '고습',
        'low_humidity'  => '저습',
    ];
    return $labels[$status] ?? $status;
}

// 측정값을 사람이 읽는 짧은 문자열로 (소수점 1자리)
function metric_display(?string $value, string $unit): string {
    if ($value === null || $value === '') {
        return '-';
    }
    return number_format((float)$value, 1) . $unit;
}

// ── 센서별 현재 상태 + 최신 측정값 ──────────────────────────
$statuses = $db->query("SELECT * FROM sensor_status ORDER BY sensor_id")->fetchAll();

// 센서별 최신 sensor_metric (최대 id 기준)
$latest_metrics = [];
$metric_rows = $db->query("
    SELECT sm.*
    FROM sensor_metric sm
    INNER JOIN (
        SELECT sensor_id, MAX(id) AS latest_id
        FROM sensor_metric
        GROUP BY sensor_id
    ) latest ON latest.latest_id = sm.id
    ORDER BY sm.sensor_id
")->fetchAll();
foreach ($metric_rows as $metric_row) {
    $latest_metrics[(int)$metric_row['sensor_id']] = $metric_row;
}

// 각 센서에 대해 실시간 오프라인/경보 상태 계산
$now = time();
foreach ($statuses as &$s) {
    $sid = (int)$s['sensor_id'];
    $metric = $latest_metrics[$sid] ?? null;
    $s['latest_metric'] = $metric;

    $last_seen_ts = strtotime($s['last_seen'] ?? '2000-01-01');
    $elapsed = $now - $last_seen_ts;
    // offline_alerted 플래그가 1이거나, last_seen이 HEARTBEAT_TIMEOUT_SEC 이상 경과했으면 오프라인
    $s['is_offline'] = ($s['offline_alerted'] == 1) || ($elapsed >= HEARTBEAT_TIMEOUT_SEC);
    $s['offline_elapsed_min'] = (int)round($elapsed / 60);

    $status_code = $metric['metric_status'] ?? 'normal';
    $s['metric_status'] = $status_code;
    $s['is_alerting'] = ($status_code !== 'normal');
}
unset($s); // 참조 해제

// ── 온습도 추이 차트용: 한 센서의 최근 100개 측정 ───────────
// 기본 sensor_id=2, 없으면 첫 센서
$chart_sensor_ids = array_map(fn($s) => (int)$s['sensor_id'], $statuses);
$chart_sensor_id = in_array(2, $chart_sensor_ids, true)
    ? 2
    : ($chart_sensor_ids[0] ?? 0);

$chart_sensor_name = '';
foreach ($statuses as $s) {
    if ((int)$s['sensor_id'] === $chart_sensor_id) {
        $chart_sensor_name = $s['sensor_name'];
        break;
    }
}

$chart_points = [];
if ($chart_sensor_id > 0) {
    $chart_stmt = $db->prepare("
        SELECT measured_at, temperature_c, humidity_percent
        FROM sensor_metric
        WHERE sensor_id = :sid
        ORDER BY id DESC
        LIMIT 100
    ");
    $chart_stmt->execute([':sid' => $chart_sensor_id]);
    $chart_rows = $chart_stmt->fetchAll();
    // 오래된 → 최신 순으로 (그래프 좌→우)
    $chart_rows = array_reverse($chart_rows);
    foreach ($chart_rows as $cr) {
        $chart_points[] = [
            't'    => $cr['measured_at'],
            'temp' => $cr['temperature_c'] !== null ? (float)$cr['temperature_c'] : null,
            'hum'  => $cr['humidity_percent'] !== null ? (float)$cr['humidity_percent'] : null,
        ];
    }
}

// ── 최근 측정 이력 (sensor_metric 30건) ─────────────────────
$recent_metrics = $db->query("
    SELECT sensor_id, sensor_name, device_location,
           temperature_c, humidity_percent, metric_status, measured_at
    FROM sensor_metric
    ORDER BY id DESC
    LIMIT 30
")->fetchAll();

// ── 최근 센서 이벤트 로그 (sensor_log: startup/periodic) ────
$logs = $db->query("
    SELECT id, sensor_name, device_location, event_type, sensor_time
    FROM sensor_log
    ORDER BY id DESC
    LIMIT 30
")->fetchAll();

// ── 최근 SMS 발송 이력 ─────────────────────────────────────
$sms_logs = $db->query("
    SELECT * FROM sms_log
    ORDER BY id DESC
    LIMIT 20
")->fetchAll();

// ── 30일 운영 요약 ─────────────────────────────────────────
// 종류별 임계 경보 SMS 건수 (sms_log message prefix 기준)
$sms_summary_rows = $db->query("
    SELECT
        SUM(message LIKE '[고온경보]%')   AS high_temp,
        SUM(message LIKE '[저온경보]%')   AS low_temp,
        SUM(message LIKE '[고습경보]%')   AS high_humidity,
        SUM(message LIKE '[저습경보]%')   AS low_humidity,
        SUM(message LIKE '[정상복귀]%')   AS recovery,
        SUM(message LIKE '[장비 오프라인]%') AS offline,
        SUM(message LIKE '[장비 복구]%')  AS device_online,
        SUM(status = 'fail')              AS sms_fail
    FROM sms_log
    WHERE sent_at >= DATE_SUB(NOW(), INTERVAL 30 DAY)
")->fetch();

$summary = [
    'high_temp'     => (int)($sms_summary_rows['high_temp'] ?? 0),
    'low_temp'      => (int)($sms_summary_rows['low_temp'] ?? 0),
    'high_humidity' => (int)($sms_summary_rows['high_humidity'] ?? 0),
    'low_humidity'  => (int)($sms_summary_rows['low_humidity'] ?? 0),
    'recovery'      => (int)($sms_summary_rows['recovery'] ?? 0),
    'offline'       => (int)($sms_summary_rows['offline'] ?? 0),
    'device_online' => (int)($sms_summary_rows['device_online'] ?? 0),
    'sms_fail'      => (int)($sms_summary_rows['sms_fail'] ?? 0),
];
$summary['alert_total'] = $summary['high_temp'] + $summary['low_temp']
    + $summary['high_humidity'] + $summary['low_humidity'];

// 30일 평균 온습도 + 최근 온습도(차트 센서 기준 최신값)
$metric_avg = $db->query("
    SELECT AVG(temperature_c) AS avg_temp, AVG(humidity_percent) AS avg_hum
    FROM sensor_metric
    WHERE measured_at >= DATE_SUB(NOW(), INTERVAL 30 DAY)
")->fetch();
$avg_temp = $metric_avg['avg_temp'] !== null ? round((float)$metric_avg['avg_temp'], 1) : null;
$avg_hum  = $metric_avg['avg_hum']  !== null ? round((float)$metric_avg['avg_hum'], 1)  : null;

$latest_overall = !empty($chart_points) ? end($chart_points) : null;
$latest_temp = $latest_overall['temp'] ?? null;
$latest_hum  = $latest_overall['hum'] ?? null;

// ── 운영 자가진단 (ops_checks.php) ─────────────────────────
$ops_checks = collect_ops_checks();
$ops_failed = ops_failed_count($ops_checks);
$ops_critical_count = count(array_filter(
    $ops_checks,
    fn($c) => !$c['ok'] && $c['severity'] === 'critical'
));
$ops_warn_count = count(array_filter(
    $ops_checks,
    fn($c) => !$c['ok'] && $c['severity'] !== 'critical'
));

// ── 상단 시스템 상태 집계 ──────────────────────────────────
$offline_count  = count(array_filter($statuses, fn($s) => $s['is_offline']));
$alert_count    = count(array_filter($statuses, fn($s) => $s['is_alerting'] && !$s['is_offline']));
$normal_count   = count($statuses) - $offline_count - $alert_count;

if ($alert_count > 0) {
    $system_state  = 'danger';
    $system_title  = '온습도 경보 발생';
    $system_detail = "임계 경보 센서 {$alert_count}개, 오프라인 {$offline_count}개";
} elseif ($offline_count > 0) {
    $system_state  = 'warning';
    $system_title  = '장비 오프라인 확인 필요';
    $system_detail = "오프라인 {$offline_count}개, 중요 진단 {$ops_critical_count}건";
} elseif ($ops_critical_count > 0) {
    $system_state  = 'warning';
    $system_title  = '운영 확인 필요';
    $system_detail = "중요 진단 {$ops_critical_count}건을 확인하세요.";
} else {
    $system_state  = 'normal';
    $system_title  = '정상 감시 중';
    $system_detail = '현재 온습도 경보와 중요 운영 경고가 없습니다.';
}
?>
<!DOCTYPE html>
<html lang="ko">
<head>
    <meta charset="UTF-8">
    <meta name="viewport" content="width=device-width, initial-scale=1.0">
    <title>온습도 모니터링 대시보드 <?= htmlspecialchars(APP_VERSION) ?></title>
    <style>
        * { box-sizing: border-box; margin: 0; padding: 0; }
        body { font-family: 'Noto Sans KR', sans-serif; background: #f0f4f8; color: #333; }
        header { background: #1a56db; color: #fff; padding: 16px 24px; }
        header h1 { font-size: 1.3rem; }
        .top-actions { display: flex; gap: 8px; align-items: center; flex-wrap: wrap; }
        .top-actions a { color:#fff; font-size:.85rem; text-decoration:none; background:rgba(255,255,255,.15); padding:6px 14px; border-radius:6px; }
        .container { max-width: 1100px; margin: 24px auto; padding: 0 16px; }
        .status-hero { border-radius: 8px; color: #fff; padding: 18px 22px; margin-bottom: 18px;
                       display: grid; grid-template-columns: minmax(0, 1fr) auto; gap: 16px;
                       align-items: center; box-shadow: 0 4px 18px rgba(15,23,42,.14); }
        .status-hero.normal { background: #166534; }
        .status-hero.warning { background: #92400e; }
        .status-hero.danger { background: #991b1b; }
        .status-hero h2 { color: #fff; margin: 0 0 6px; font-size: 1.25rem; }
        .status-hero p { color: rgba(255,255,255,.88); font-size: .9rem; }
        .status-clock { text-align: right; font-size: .82rem; color: rgba(255,255,255,.82); white-space: nowrap; }
        .mobile-quick { display: none; }
        .quick-grid { display: grid; grid-template-columns: repeat(4, minmax(0, 1fr)); gap: 8px; margin-bottom: 10px; }
        .quick-tile { background: #fff; border: 1px solid #e2e8f0; border-radius: 8px; padding: 10px;
                      text-decoration: none; color: #1f2937; min-height: 70px; }
        .quick-tile span { display: block; color: #64748b; font-size: .68rem; margin-bottom: 5px; }
        .quick-tile strong { font-size: 1.25rem; line-height: 1; }
        .quick-tile.danger { border-color: #fecaca; background: #fff1f2; }
        .quick-tile.warn { border-color: #fde68a; background: #fffbeb; }
        .quick-tile.ok { border-color: #bbf7d0; background: #f0fdf4; }
        .quick-links { display: flex; gap: 8px; overflow-x: auto; -webkit-overflow-scrolling: touch;
                       padding-bottom: 6px; margin-bottom: 16px; }
        .quick-links a { flex: 0 0 auto; color: #1d4ed8; background: #eff6ff; border: 1px solid #bfdbfe;
                         text-decoration: none; border-radius: 999px; padding: 8px 11px; font-size: .78rem; font-weight: 700; }
        .ops-grid { display: grid; grid-template-columns: minmax(0, 1.5fr) minmax(280px, .9fr);
                    gap: 16px; align-items: start; margin-bottom: 24px; }
        .panel { background: #fff; border-radius: 8px; padding: 18px;
                 box-shadow: 0 1px 4px rgba(0,0,0,.1); }
        .panel h2 { margin-top: 0; }
        .diag-list { display: grid; gap: 8px; }
        .diag-item { border: 1px solid #e2e8f0; border-radius: 7px; padding: 9px 10px;
                     display: grid; grid-template-columns: auto minmax(0,1fr); gap: 8px; align-items: start; }
        .diag-dot { width: 10px; height: 10px; border-radius: 50%; margin-top: 4px; background: #22c55e; }
        .diag-item.warn .diag-dot { background: #f59e0b; }
        .diag-item.critical .diag-dot { background: #ef4444; }
        .diag-item strong { display: block; font-size: .82rem; }
        .diag-item span { color: #64748b; font-size: .74rem; line-height: 1.35; }
        .report-grid { display: grid; grid-template-columns: repeat(4, minmax(0, 1fr)); gap: 10px; }
        .report-stat { border: 1px solid #e2e8f0; border-radius: 7px; padding: 10px; }
        .report-stat span { display: block; color: #64748b; font-size: .72rem; margin-bottom: 4px; }
        .report-stat strong { font-size: 1.05rem; }
        .cards { display: flex; gap: 16px; flex-wrap: wrap; margin-bottom: 24px; }
        .card { background: #fff; border-radius: 8px; padding: 20px 24px;
                box-shadow: 0 1px 4px rgba(0,0,0,.1); flex: 1; min-width: 180px; }
        .card .val { font-size: 2rem; font-weight: 700; margin-top: 6px; }
        .sensor-grid { display: flex; gap: 16px; flex-wrap: wrap; margin-bottom: 24px; }
        .sensor-card { background: #fff; border-radius: 8px; padding: 20px;
                       box-shadow: 0 1px 4px rgba(0,0,0,.1);
                       border-left: 6px solid #22c55e; min-width: 200px; }
        .sensor-card.alert { border-left-color: #ef4444; background: #fff5f5; }
        .sensor-card.offline { border-left-color: #f59e0b; background: #fffbeb; }
        .sensor-card .badge { display: inline-block; padding: 2px 10px; border-radius: 9999px;
                              font-size: .75rem; font-weight: 600; }
        .badge.ok { background: #dcfce7; color: #166534; }
        .badge.alert { background: #fee2e2; color: #991b1b; }
        .badge.offline { background: #fef3c7; color: #92400e; }
        .metric-line { display: flex; flex-wrap: wrap; gap: 6px; margin-top: 10px; }
        .metric-chip { display: inline-flex; align-items: center; border-radius: 999px;
                       background: #eef2ff; color: #3730a3; padding: 4px 8px;
                       font-size: .76rem; font-weight: 700; }
        .metric-chip.hot { background: #fee2e2; color: #991b1b; }
        .metric-chip.cold { background: #dbeafe; color: #1e40af; }
        .metric-chip.humid { background: #fef9c3; color: #854d0e; }
        .metric-time { color: #64748b; font-size: .72rem; margin-top: 6px; }
        table { width: 100%; border-collapse: collapse; background: #fff;
                border-radius: 8px; overflow: hidden; box-shadow: 0 1px 4px rgba(0,0,0,.1); }
        th { background: #f8fafc; font-size: .8rem; color: #64748b;
             padding: 10px 14px; text-align: left; border-bottom: 1px solid #e2e8f0; }
        td { padding: 9px 14px; border-bottom: 1px solid #f1f5f9; font-size: .85rem; }
        tr:hover td { background: #f8fafc; }
        .alert-row td { background: #fef2f2 !important; }
        h2 { margin: 24px 0 12px; font-size: 1rem; color: #475569; }
        .refresh { float: right; font-size: .8rem; color: #94a3b8; }
        .ok-color { color: #16a34a; font-weight: 600; }
        .err-color { color: #dc2626; font-weight: 600; }
        .warn-color { color: #d97706; font-weight: 600; }
        /* 상태 배지 (테이블) */
        .pill { display: inline-block; border-radius: 999px; padding: 2px 9px;
                font-size: .72rem; font-weight: 700; background: #dcfce7; color: #166534; }
        .pill.high_temp { background: #fee2e2; color: #991b1b; }
        .pill.low_temp { background: #dbeafe; color: #1e40af; }
        .pill.high_humidity { background: #fef9c3; color: #854d0e; }
        .pill.low_humidity { background: #ede9fe; color: #6d28d9; }
        /* 온습도 추이 차트 */
        .chart-panel { background: #fff; border-radius: 8px; padding: 18px;
                       box-shadow: 0 1px 4px rgba(0,0,0,.1); margin-bottom: 24px; }
        .chart-legend { display: flex; gap: 16px; flex-wrap: wrap; margin: 6px 0 12px; font-size: .8rem; color: #475569; }
        .chart-legend span { display: inline-flex; align-items: center; gap: 6px; }
        .legend-swatch { width: 14px; height: 4px; border-radius: 2px; display: inline-block; }
        .chart-svg-wrap { overflow-x: auto; -webkit-overflow-scrolling: touch; }
        .chart-empty { color: #94a3b8; padding: 24px; text-align: center; }
        /* 테이블 가로 스크롤 컨테이너 */
        .table-wrap { overflow-x: auto; -webkit-overflow-scrolling: touch;
                      margin-bottom: 24px; border-radius: 8px; }
        .table-wrap table { margin-bottom: 0; }
        .mobile-detail { margin-bottom: 24px; }
        .mobile-detail summary { display: none; }
        .empty-note { color: #94a3b8; padding: 18px; text-align: center;
                      background: #fff; border-radius: 8px; box-shadow: 0 1px 4px rgba(0,0,0,.1); }

        /* --- 태블릿 (≤768px) --- */
        @media (max-width: 768px) {
            header { display: block !important; }
            .top-actions { margin-top: 12px; overflow-x: auto; flex-wrap: nowrap; -webkit-overflow-scrolling: touch; padding-bottom: 2px; }
            .top-actions a { flex: 0 0 auto; padding: 7px 11px; }
            .status-hero { grid-template-columns: 1fr; }
            .status-clock { text-align: left; }
            .mobile-quick { display: block; }
            .ops-grid { grid-template-columns: 1fr; }
            .report-grid { grid-template-columns: repeat(2, minmax(0, 1fr)); }
            .cards { flex-direction: column; }
            .card { min-width: 0; }
            .sensor-card { min-width: 0; }
            header h1 { font-size: 1.1rem; }
            .card .val { font-size: 1.6rem; }
            th { font-size: .75rem; padding: 8px 10px; }
            td { font-size: .8rem; padding: 7px 10px; }
            .mobile-detail { background: #fff; border-radius: 8px; box-shadow: 0 1px 4px rgba(0,0,0,.1); overflow: hidden; }
            .mobile-detail summary { display: flex; justify-content: space-between; align-items: center; gap: 10px;
                                     padding: 13px 14px; color: #334155; font-weight: 700; cursor: pointer; list-style: none; }
            .mobile-detail summary::-webkit-details-marker { display: none; }
            .mobile-detail summary span { color: #64748b; font-size: .74rem; font-weight: 600; }
            .mobile-detail .table-wrap { margin-bottom: 0; border-top: 1px solid #e2e8f0; border-radius: 0; }
        }

        /* --- 폰 (≤480px) --- */
        @media (max-width: 480px) {
            .container { padding: 0 10px; }
            header { padding: 12px 16px; }
            header h1 { font-size: 1rem; }
            .quick-grid { grid-template-columns: repeat(2, minmax(0, 1fr)); }
            .card { padding: 14px 16px; }
            .card .val { font-size: 1.3rem; }
            .sensor-grid { flex-direction: column; }
            .sensor-card { min-width: 0; }
            th { font-size: .7rem; padding: 6px 8px; }
            td { font-size: .75rem; padding: 6px 8px; }
            .col-hide-xs { display: none; }
            .report-grid { grid-template-columns: 1fr; }
        }

        /* --- 초소형 (≤360px) --- */
        @media (max-width: 360px) {
            header h1 { font-size: .9rem; }
            .card .val { font-size: 1.1rem; }
            th { font-size: .65rem; padding: 5px 6px; }
            td { font-size: .7rem; padding: 5px 6px; }
        }
    </style>
    <script>setTimeout(()=>location.reload(), 30000);</script><!-- 30초 자동 새로고침 -->
</head>
<body>
<header style="display:flex;align-items:center;justify-content:space-between">
    <h1>온습도 모니터링 대시보드 <?= htmlspecialchars(APP_VERSION) ?></h1>
    <div class="top-actions">
        <a href="setup_wizard.php">설치 점검</a>
        <a href="monthly_report.php">월간 보고</a>
        <a href="security_evidence.php">보안 증적</a>
        <a href="?logout">로그아웃</a>
    </div>
</header>
<div class="container">

    <section class="status-hero <?= htmlspecialchars($system_state) ?>">
        <div>
            <h2><?= htmlspecialchars($system_title) ?></h2>
            <p><?= htmlspecialchars($system_detail) ?></p>
        </div>
        <div class="status-clock">
            <?= date('Y-m-d H:i:s') ?><br>
            30초 자동 새로고침
        </div>
    </section>

    <section class="mobile-quick" aria-label="모바일 빠른 조회">
        <div class="quick-grid">
            <a class="quick-tile <?= $alert_count > 0 ? 'danger' : 'ok' ?>" href="#sensors-section">
                <span>경보 센서</span>
                <strong><?= $alert_count ?>개</strong>
            </a>
            <a class="quick-tile <?= $offline_count > 0 ? 'warn' : 'ok' ?>" href="#sensors-section">
                <span>오프라인</span>
                <strong><?= $offline_count ?>개</strong>
            </a>
            <a class="quick-tile <?= $summary['alert_total'] > 0 ? 'warn' : 'ok' ?>" href="#summary-section">
                <span>30일 경보 SMS</span>
                <strong><?= $summary['alert_total'] ?>건</strong>
            </a>
            <a class="quick-tile <?= $ops_critical_count > 0 ? 'warn' : 'ok' ?>" href="#diagnostics-section">
                <span>중요 진단</span>
                <strong><?= $ops_critical_count ?>건</strong>
            </a>
        </div>
        <nav class="quick-links" aria-label="빠른 이동">
            <a href="#sensors-section">센서 상태</a>
            <a href="#chart-section">온습도 추이</a>
            <a href="#summary-section">운영 요약</a>
            <a href="#metrics-section">측정 이력</a>
            <a href="#logs-section">이벤트 로그</a>
            <a href="#sms-section">SMS</a>
        </nav>
    </section>

    <div class="ops-grid">
        <div>
            <!-- 요약 카드 -->
            <div class="cards">
                <div class="card">
                    <div>등록 센서</div>
                    <div class="val"><?= count($statuses) ?>개</div>
                </div>
                <div class="card">
                    <div>경보 센서</div>
                    <div class="val" style="color:<?= $alert_count > 0 ? '#ef4444' : '#22c55e' ?>">
                        <?= $alert_count ?>개
                    </div>
                </div>
                <div class="card">
                    <div>오프라인</div>
                    <div class="val" style="color:<?= $offline_count > 0 ? '#f59e0b' : '#22c55e' ?>">
                        <?= $offline_count ?>개
                    </div>
                </div>
            </div>
            <div class="cards" style="margin-bottom:0">
                <div class="card">
                    <div>최근 온도</div>
                    <div class="val" style="font-size:1.4rem"><?= htmlspecialchars(metric_display($latest_temp !== null ? (string)$latest_temp : null, '°C')) ?></div>
                </div>
                <div class="card">
                    <div>최근 습도</div>
                    <div class="val" style="font-size:1.4rem"><?= htmlspecialchars(metric_display($latest_hum !== null ? (string)$latest_hum : null, '%')) ?></div>
                </div>
                <div class="card">
                    <div>현재 시각</div>
                    <div class="val" style="font-size:1.1rem"><?= date('Y-m-d H:i:s') ?></div>
                </div>
            </div>
        </div>

        <aside class="panel" id="diagnostics-section">
            <h2>운영 자가진단</h2>
            <div style="font-size:.82rem;color:#64748b;margin-bottom:12px">
                실패 <?= $ops_failed ?>건 · 중요 <?= $ops_critical_count ?>건 · 주의 <?= $ops_warn_count ?>건
            </div>
            <div class="diag-list">
                <?php foreach ($ops_checks as $check):
                    $diag_class = $check['ok'] ? 'ok' : $check['severity'];
                ?>
                <div class="diag-item <?= htmlspecialchars($diag_class) ?>">
                    <i class="diag-dot"></i>
                    <div>
                        <strong><?= htmlspecialchars($check['label']) ?> · <?= $check['ok'] ? '정상' : '확인 필요' ?></strong>
                        <span><?= htmlspecialchars($check['detail']) ?></span>
                    </div>
                </div>
                <?php endforeach; ?>
            </div>
            <p style="color:#64748b;font-size:.84rem;margin:12px 0 0">보안대책서 첨부용 점검표는 상단의 보안 증적 화면에서 내려받을 수 있습니다.</p>
        </aside>
    </div>

    <!-- 센서 상태 -->
    <h2 id="sensors-section">센서 현재 상태 <span class="refresh">30초 자동 새로고침</span></h2>
    <div class="sensor-grid">
        <?php foreach ($statuses as $s):
            // 상태 우선순위: 오프라인 > 임계 경보 > 정상
            $status_code = $s['metric_status'] ?? 'normal';
            if ($s['is_offline']) {
                $card_class  = 'offline';
                $badge_class = 'offline';
                $badge_text  = $s['is_alerting'] ? ('오프라인 (' . metric_status_label($status_code) . ' 중 끊김)') : '오프라인';
            } elseif ($s['is_alerting']) {
                $card_class  = 'alert';
                $badge_class = 'alert';
                $badge_text  = metric_status_label($status_code) . ' 경보!';
            } else {
                $card_class  = '';
                $badge_class = 'ok';
                $badge_text  = '정상';
            }

            $temp_chip_class = '';
            $hum_chip_class  = '';
            if ($status_code === 'high_temp') { $temp_chip_class = 'hot'; }
            elseif ($status_code === 'low_temp') { $temp_chip_class = 'cold'; }
            elseif ($status_code === 'high_humidity' || $status_code === 'low_humidity') { $hum_chip_class = 'humid'; }
        ?>
        <div class="sensor-card <?= $card_class ?>">
            <strong><?= htmlspecialchars($s['sensor_name']) ?></strong><br>
            <small style="color:#64748b"><?= htmlspecialchars($s['device_location']) ?> / <?= htmlspecialchars($s['device_id']) ?></small><br>
            <div style="margin-top:10px">
                <span class="badge <?= $badge_class ?>">
                    <?= htmlspecialchars($badge_text) ?>
                </span>
            </div>
            <?php if (!empty($s['latest_metric'])):
                $metric = $s['latest_metric'];
                $metric_temp = metric_display($metric['temperature_c'], '°C');
                $metric_humidity = metric_display($metric['humidity_percent'], '%');
            ?>
            <div class="metric-line">
                <span class="metric-chip <?= $temp_chip_class ?>">온도 <?= htmlspecialchars($metric_temp) ?></span>
                <span class="metric-chip <?= $hum_chip_class ?>">습도 <?= htmlspecialchars($metric_humidity) ?></span>
            </div>
            <div class="metric-time">측정: <?= htmlspecialchars($metric['measured_at']) ?></div>
            <?php else: ?>
            <div class="metric-time" style="margin-top:10px">측정값 없음</div>
            <?php endif; ?>
            <div style="font-size:.75rem;margin-top:6px;<?= $s['is_offline'] ? 'color:#dc2626;font-weight:600' : 'color:#94a3b8' ?>">
                최종확인: <?= htmlspecialchars($s['last_seen']) ?>
                <?php if ($s['is_offline']): ?>
                    (<?= $s['offline_elapsed_min'] ?>분 전)
                <?php endif; ?>
            </div>
        </div>
        <?php endforeach; ?>
        <?php if (empty($statuses)): ?>
        <p style="color:#94a3b8">아직 수신된 센서 데이터가 없습니다.</p>
        <?php endif; ?>
    </div>

    <!-- 온습도 추이 차트 (인라인 SVG, 외부 라이브러리 없음) -->
    <h2 id="chart-section">온습도 추이
        <?php if ($chart_sensor_id > 0): ?>
            <span style="font-weight:400;color:#94a3b8;font-size:.82rem">
                · <?= htmlspecialchars($chart_sensor_name !== '' ? $chart_sensor_name : ('센서 ' . $chart_sensor_id)) ?> 최근 <?= count($chart_points) ?>개
            </span>
        <?php endif; ?>
    </h2>
    <div class="chart-panel">
        <?php if (count($chart_points) >= 2): ?>
        <div class="chart-legend">
            <span><i class="legend-swatch" style="background:#ef4444"></i> 온도(°C)</span>
            <span><i class="legend-swatch" style="background:#2563eb"></i> 습도(%)</span>
        </div>
        <div class="chart-svg-wrap">
            <?php
            // ── 인라인 SVG 라인 차트 (온도/습도 2계열) ──────────────
            $chart_w = 920; $chart_h = 280;
            $pad_l = 44; $pad_r = 44; $pad_t = 16; $pad_b = 34;
            $plot_w = $chart_w - $pad_l - $pad_r;
            $plot_h = $chart_h - $pad_t - $pad_b;
            $n = count($chart_points);

            $temps = array_values(array_filter(array_map(fn($p) => $p['temp'], $chart_points), fn($v) => $v !== null));
            $hums  = array_values(array_filter(array_map(fn($p) => $p['hum'], $chart_points), fn($v) => $v !== null));

            // 온도 축 범위 (여유 패딩)
            $t_min = !empty($temps) ? min($temps) : 0;
            $t_max = !empty($temps) ? max($temps) : 1;
            if ($t_min === $t_max) { $t_min -= 1; $t_max += 1; }
            $t_pad = ($t_max - $t_min) * 0.1;
            $t_min -= $t_pad; $t_max += $t_pad;

            // 습도 축 범위
            $h_min = !empty($hums) ? min($hums) : 0;
            $h_max = !empty($hums) ? max($hums) : 1;
            if ($h_min === $h_max) { $h_min -= 1; $h_max += 1; }
            $h_pad = ($h_max - $h_min) * 0.1;
            $h_min -= $h_pad; $h_max += $h_pad;

            $x_at = function (int $i) use ($pad_l, $plot_w, $n): float {
                if ($n <= 1) { return $pad_l; }
                return $pad_l + ($plot_w * $i / ($n - 1));
            };
            $y_temp = function (?float $v) use ($pad_t, $plot_h, $t_min, $t_max): ?float {
                if ($v === null) { return null; }
                $r = ($t_max - $t_min) ?: 1;
                return $pad_t + $plot_h * (1 - (($v - $t_min) / $r));
            };
            $y_hum = function (?float $v) use ($pad_t, $plot_h, $h_min, $h_max): ?float {
                if ($v === null) { return null; }
                $r = ($h_max - $h_min) ?: 1;
                return $pad_t + $plot_h * (1 - (($v - $h_min) / $r));
            };

            // 결측(null)은 구간을 끊어 polyline 들을 만든다
            $build_segments = function (string $key, callable $y_fn) use ($chart_points, $x_at): array {
                $segments = [];
                $current = [];
                foreach ($chart_points as $i => $p) {
                    $val = $p[$key];
                    if ($val === null) {
                        if (count($current) >= 2) { $segments[] = $current; }
                        $current = [];
                        continue;
                    }
                    $current[] = sprintf('%.1f,%.1f', $x_at($i), $y_fn((float)$val));
                }
                if (count($current) >= 2) { $segments[] = $current; }
                return $segments;
            };

            $temp_segments = $build_segments('temp', $y_temp);
            $hum_segments  = $build_segments('hum', $y_hum);

            $fmt = fn($v) => rtrim(rtrim(number_format($v, 1, '.', ''), '0'), '.');
            ?>
            <svg viewBox="0 0 <?= $chart_w ?> <?= $chart_h ?>" width="100%" height="<?= $chart_h ?>"
                 preserveAspectRatio="xMidYMid meet" role="img"
                 aria-label="온습도 추이 라인 차트" style="min-width:680px">
                <!-- 플롯 배경/축 -->
                <rect x="<?= $pad_l ?>" y="<?= $pad_t ?>" width="<?= $plot_w ?>" height="<?= $plot_h ?>"
                      fill="#f8fafc" stroke="#e2e8f0"></rect>
                <?php
                // 가로 그리드 + 좌(온도)/우(습도) 눈금 라벨 (5단계)
                for ($g = 0; $g <= 4; $g++):
                    $gy = $pad_t + $plot_h * $g / 4;
                    $tv = $t_max - ($t_max - $t_min) * $g / 4;
                    $hv = $h_max - ($h_max - $h_min) * $g / 4;
                ?>
                <line x1="<?= $pad_l ?>" y1="<?= sprintf('%.1f', $gy) ?>"
                      x2="<?= $pad_l + $plot_w ?>" y2="<?= sprintf('%.1f', $gy) ?>"
                      stroke="#e2e8f0" stroke-dasharray="3,3"></line>
                <text x="<?= $pad_l - 6 ?>" y="<?= sprintf('%.1f', $gy + 3) ?>"
                      text-anchor="end" font-size="10" fill="#ef4444"><?= htmlspecialchars($fmt($tv)) ?></text>
                <text x="<?= $pad_l + $plot_w + 6 ?>" y="<?= sprintf('%.1f', $gy + 3) ?>"
                      text-anchor="start" font-size="10" fill="#2563eb"><?= htmlspecialchars($fmt($hv)) ?></text>
                <?php endfor; ?>

                <!-- X축 시간 라벨 (시작/중간/끝) -->
                <?php
                $label_idx = $n > 1 ? [0, intdiv($n - 1, 2), $n - 1] : [0];
                foreach ($label_idx as $li):
                    $lx = $x_at($li);
                    $anchor = $li === 0 ? 'start' : ($li === $n - 1 ? 'end' : 'middle');
                    $tlabel = substr($chart_points[$li]['t'], 5, 11); // MM-DD HH:MM
                ?>
                <text x="<?= sprintf('%.1f', $lx) ?>" y="<?= $chart_h - 12 ?>"
                      text-anchor="<?= $anchor ?>" font-size="10" fill="#94a3b8"><?= htmlspecialchars($tlabel) ?></text>
                <?php endforeach; ?>

                <!-- 습도 라인 -->
                <?php foreach ($hum_segments as $seg): ?>
                <polyline points="<?= htmlspecialchars(implode(' ', $seg)) ?>"
                          fill="none" stroke="#2563eb" stroke-width="1.8"
                          stroke-linejoin="round" stroke-linecap="round"></polyline>
                <?php endforeach; ?>
                <!-- 온도 라인 -->
                <?php foreach ($temp_segments as $seg): ?>
                <polyline points="<?= htmlspecialchars(implode(' ', $seg)) ?>"
                          fill="none" stroke="#ef4444" stroke-width="1.8"
                          stroke-linejoin="round" stroke-linecap="round"></polyline>
                <?php endforeach; ?>
            </svg>
        </div>
        <?php else: ?>
        <div class="chart-empty">추이를 표시할 측정 데이터가 충분하지 않습니다 (2개 이상 필요).</div>
        <?php endif; ?>
    </div>

    <section class="panel" id="summary-section" style="margin-bottom:24px">
        <h2>30일 운영 요약</h2>
        <div class="report-grid">
            <div class="report-stat">
                <span>고온 경보 SMS</span>
                <strong><?= $summary['high_temp'] ?>건</strong>
            </div>
            <div class="report-stat">
                <span>저온 경보 SMS</span>
                <strong><?= $summary['low_temp'] ?>건</strong>
            </div>
            <div class="report-stat">
                <span>고습 경보 SMS</span>
                <strong><?= $summary['high_humidity'] ?>건</strong>
            </div>
            <div class="report-stat">
                <span>저습 경보 SMS</span>
                <strong><?= $summary['low_humidity'] ?>건</strong>
            </div>
            <div class="report-stat">
                <span>정상 복귀 SMS</span>
                <strong><?= $summary['recovery'] ?>건</strong>
            </div>
            <div class="report-stat">
                <span>오프라인 SMS</span>
                <strong><?= $summary['offline'] ?>건</strong>
            </div>
            <div class="report-stat">
                <span>장비 복구 SMS</span>
                <strong><?= $summary['device_online'] ?>건</strong>
            </div>
            <div class="report-stat">
                <span>SMS 실패</span>
                <strong style="color:<?= $summary['sms_fail'] > 0 ? '#dc2626' : 'inherit' ?>"><?= $summary['sms_fail'] ?>건</strong>
            </div>
            <div class="report-stat">
                <span>평균 온도</span>
                <strong><?= $avg_temp !== null ? htmlspecialchars((string)$avg_temp) . '°C' : '-' ?></strong>
            </div>
            <div class="report-stat">
                <span>평균 습도</span>
                <strong><?= $avg_hum !== null ? htmlspecialchars((string)$avg_hum) . '%' : '-' ?></strong>
            </div>
            <div class="report-stat">
                <span>최근 온도</span>
                <strong><?= htmlspecialchars(metric_display($latest_temp !== null ? (string)$latest_temp : null, '°C')) ?></strong>
            </div>
            <div class="report-stat">
                <span>최근 습도</span>
                <strong><?= htmlspecialchars(metric_display($latest_hum !== null ? (string)$latest_hum : null, '%')) ?></strong>
            </div>
        </div>
    </section>

    <!-- 최근 온습도 측정 이력 -->
    <h2 id="metrics-section">최근 온습도 측정 (30건)</h2>
    <details class="mobile-detail" data-mobile-collapsed open>
    <summary>최근 온습도 측정 <span>30건 기준</span></summary>
    <div class="table-wrap">
    <table>
        <thead>
            <tr>
                <th>측정시각</th><th>센서</th><th class="col-hide-xs">위치</th>
                <th>온도</th><th>습도</th><th>상태</th>
            </tr>
        </thead>
        <tbody>
        <?php foreach ($recent_metrics as $row):
            $status_code = $row['metric_status'] ?? 'normal';
            $is_alert = $status_code !== 'normal';
        ?>
        <tr class="<?= $is_alert ? 'alert-row' : '' ?>">
            <td><?= htmlspecialchars($row['measured_at']) ?></td>
            <td><?= htmlspecialchars($row['sensor_name']) ?></td>
            <td class="col-hide-xs"><?= htmlspecialchars($row['device_location']) ?></td>
            <td><?= htmlspecialchars(metric_display($row['temperature_c'], '°C')) ?></td>
            <td><?= htmlspecialchars(metric_display($row['humidity_percent'], '%')) ?></td>
            <td><span class="pill <?= htmlspecialchars($status_code) ?>"><?= htmlspecialchars(metric_status_label($status_code)) ?></span></td>
        </tr>
        <?php endforeach; ?>
        <?php if (empty($recent_metrics)): ?>
        <tr><td colspan="6" style="text-align:center;color:#94a3b8">아직 온습도 측정 데이터가 없습니다.</td></tr>
        <?php endif; ?>
        </tbody>
    </table>
    </div>
    </details>

    <!-- 센서 이벤트 로그 (startup/periodic) -->
    <h2 id="logs-section">센서 이벤트 로그 (30건)</h2>
    <details class="mobile-detail" data-mobile-collapsed open>
    <summary>센서 이벤트 로그 <span>30건 기준</span></summary>
    <div class="table-wrap">
    <table>
        <thead>
            <tr>
                <th class="col-hide-xs">#</th><th>시각</th><th>센서</th>
                <th class="col-hide-xs">위치</th><th>이벤트</th>
            </tr>
        </thead>
        <tbody>
        <?php foreach ($logs as $row):
            $event_labels = [
                'startup'  => '장비시작',
                'periodic' => '정기보고',
            ];
            $evt = $row['event_type'];
        ?>
        <tr>
            <td class="col-hide-xs"><?= (int)$row['id'] ?></td>
            <td><?= htmlspecialchars($row['sensor_time']) ?></td>
            <td><?= htmlspecialchars($row['sensor_name']) ?></td>
            <td class="col-hide-xs"><?= htmlspecialchars($row['device_location']) ?></td>
            <td><?= htmlspecialchars($event_labels[$evt] ?? $evt) ?></td>
        </tr>
        <?php endforeach; ?>
        <?php if (empty($logs)): ?>
        <tr><td colspan="5" style="text-align:center;color:#94a3b8">아직 센서 이벤트가 없습니다.</td></tr>
        <?php endif; ?>
        </tbody>
    </table>
    </div>
    </details>

    <!-- SMS 발송 로그 -->
    <h2 id="sms-section">SMS 발송 이력 (최근 20건)</h2>
    <details class="mobile-detail" data-mobile-collapsed open>
    <summary>SMS 발송 이력 <span>최근 20건</span></summary>
    <div class="table-wrap">
    <table>
        <thead>
            <tr><th class="col-hide-xs">#</th><th>발송시각</th><th>센서</th><th>수신자</th><th>상태</th><th class="col-hide-xs">결과</th></tr>
        </thead>
        <tbody>
        <?php foreach ($sms_logs as $row): ?>
        <tr>
            <td class="col-hide-xs"><?= (int)$row['id'] ?></td>
            <td><?= htmlspecialchars($row['sent_at']) ?></td>
            <td><?= htmlspecialchars($row['sensor_name']) ?></td>
            <td><?= htmlspecialchars($row['receivers']) ?></td>
            <td>
                <?php if ($row['status'] === 'success'): ?>
                <span class="ok-color">성공</span>
                <?php else: ?>
                <span class="err-color">실패</span>
                <?php endif; ?>
            </td>
            <td class="col-hide-xs" style="font-size:.75rem;color:#64748b"><?= htmlspecialchars($row['result_message']) ?></td>
        </tr>
        <?php endforeach; ?>
        <?php if (empty($sms_logs)): ?>
        <tr><td colspan="6" style="text-align:center;color:#94a3b8">아직 SMS 발송 이력이 없습니다.</td></tr>
        <?php endif; ?>
        </tbody>
    </table>
    </div>
    </details>

</div>

<script>
function applyMobileDisclosureState() {
    const shouldCollapse = window.matchMedia('(max-width: 768px)').matches;
    document.querySelectorAll('details[data-mobile-collapsed]').forEach((detail) => {
        if (shouldCollapse && !detail.dataset.mobileInitialized) {
            detail.removeAttribute('open');
            detail.dataset.mobileInitialized = '1';
        }
        if (!shouldCollapse) {
            detail.setAttribute('open', '');
            delete detail.dataset.mobileInitialized;
        }
    });
}

applyMobileDisclosureState();
window.addEventListener('resize', applyMobileDisclosureState);
</script>
</body>
</html>
