<?php
/**
 * visitor_logs 聚合分析与结论生成
 */
require_once dirname(__DIR__) . '/DbHelper.php';
require_once __DIR__ . '/VisitorLogSchema.php';

class VisitorLogAnalytics
{
    /**
     * @param mysqli $conn
     * @param array  $filters date_from, date_to, domain, campagin_id
     * @return array
     */
    public static function buildReport($conn, array $filters = [])
    {
        VisitorLogSchema::ensureColumns($conn);
        VisitorLogSchema::ensureIndexes($conn);

        $where     = self::buildWhere($conn, $filters);
        $summary   = self::fetchSummary($conn, $where);
        $byResult  = self::fetchGroup($conn, $where, 'result', 'label');
        $byReason  = self::fetchReasonTop($conn, $where, 15);
        $byCountry = self::fetchGroup($conn, $where, 'country', 'country', 10);
        $byHour    = self::fetchByHour($conn, $where);
        $byDevice  = self::fetchDeviceTop($conn, $where, 8);
        $timing    = self::fetchTimingStats($conn, $where);
        $compare   = self::fetchCompare($conn, $filters, $summary);
        $conclusions = self::buildConclusions($summary, $byReason, $byCountry, $byHour, $byDevice, $timing, $compare, $byResult);

        return [
            'summary'     => $summary,
            'by_result'   => $byResult,
            'by_reason'   => $byReason,
            'by_country'  => $byCountry,
            'by_hour'     => $byHour,
            'by_device'   => $byDevice,
            'timing'      => $timing,
            'compare'     => $compare,
            'conclusions' => $conclusions,
        ];
    }

    /**
     * @param mysqli $conn
     * @param array  $filters
     */
    private static function buildWhere($conn, array $filters)
    {
        $parts = ['1=1'];

        $dateFrom = trim((string) ($filters['date_from'] ?? ''));
        $dateTo   = trim((string) ($filters['date_to'] ?? ''));
        if ($dateFrom === '' && $dateTo === '') {
            $dateTo   = date('Y-m-d');
            $dateFrom = date('Y-m-d', strtotime('-6 days'));
        }
        if ($dateFrom !== '') {
            $parts[] = "`visit_date` >= '" . cloak_db_escape($conn, $dateFrom . ' 00:00:00') . "'";
        }
        if ($dateTo !== '') {
            $parts[] = "`visit_date` <= '" . cloak_db_escape($conn, $dateTo . ' 23:59:59') . "'";
        }

        $domain = trim((string) ($filters['domain'] ?? ''));
        if ($domain !== '') {
            $parts[] = "`domain` LIKE '%" . cloak_db_escape($conn, $domain) . "%'";
        }

        $campaginId = trim((string) ($filters['campagin_id'] ?? ''));
        if ($campaginId !== '') {
            $parts[] = "`campagin_id` = '" . cloak_db_escape($conn, $campaginId) . "'";
        }

        return implode(' AND ', $parts);
    }

    /**
     * @param mysqli $conn
     * @param string $where
     */
    private static function fetchSummary($conn, $where)
    {
        $sql = "SELECT
            COUNT(*) AS total,
            SUM(CASE WHEN `result` = 'true' THEN 1 ELSE 0 END) AS pass_cnt,
            SUM(CASE WHEN `result` = 'false' THEN 1 ELSE 0 END) AS block_cnt,
            SUM(CASE WHEN `result` = 'wait' THEN 1 ELSE 0 END) AS wait_cnt
            FROM `visitor_logs` WHERE {$where}";
        $res = $conn->query($sql);
        $row = $res ? $res->fetch_assoc() : [];

        $total  = (int) ($row['total'] ?? 0);
        $pass   = (int) ($row['pass_cnt'] ?? 0);
        $block  = (int) ($row['block_cnt'] ?? 0);
        $wait   = (int) ($row['wait_cnt'] ?? 0);
        $decided = max(1, $pass + $block);

        return [
            'total'       => $total,
            'pass'        => $pass,
            'block'       => $block,
            'wait'        => $wait,
            'pass_rate'   => $total > 0 ? round($pass / $decided * 100, 1) : 0,
            'block_rate'  => $total > 0 ? round($block / $decided * 100, 1) : 0,
            'wait_rate'   => $total > 0 ? round($wait / $total * 100, 1) : 0,
        ];
    }

    /**
     * @param mysqli $conn
     * @param string $where
     * @param string $column
     * @param string $labelKey
     * @param int    $limit
     */
    private static function fetchGroup($conn, $where, $column, $labelKey, $limit = 0)
    {
        $limitSql = $limit > 0 ? ' LIMIT ' . (int) $limit : '';
        $sql = "SELECT `{$column}` AS grp, COUNT(*) AS cnt FROM `visitor_logs`
            WHERE {$where} GROUP BY `{$column}` ORDER BY cnt DESC{$limitSql}";
        $res  = $conn->query($sql);
        $rows = [];
        if ($res) {
            while ($r = $res->fetch_assoc()) {
                $label = trim((string) ($r['grp'] ?? ''));
                if ($label === '') {
                    $label = '（空）';
                }
                if ($column === 'result') {
                    if ($label === 'true') {
                        $label = '通过';
                    } elseif ($label === 'false') {
                        $label = '拦截';
                    } elseif ($label === 'wait') {
                        $label = '检测中';
                    }
                }
                $rows[] = [
                    $labelKey => $label,
                    'count'   => (int) $r['cnt'],
                ];
            }
            $res->free();
        }
        return $rows;
    }

    /**
     * @param mysqli $conn
     * @param string $where
     * @param int    $limit
     */
    private static function fetchReasonTop($conn, $where, $limit)
    {
        $limit = (int) $limit;
        $sql   = "SELECT IFNULL(NULLIF(TRIM(`reason`), ''), '（无理由/通过）') AS reason, COUNT(*) AS cnt
            FROM `visitor_logs` WHERE {$where} AND (`result` = 'false' OR `result` = 'wait')
            GROUP BY reason ORDER BY cnt DESC LIMIT {$limit}";
        $res   = $conn->query($sql);
        $rows  = [];
        $total = 0;
        if ($res) {
            while ($r = $res->fetch_assoc()) {
                $cnt = (int) $r['cnt'];
                $total += $cnt;
                $rows[] = [
                    'reason' => (string) $r['reason'],
                    'count'  => $cnt,
                ];
            }
            $res->free();
        }
        foreach ($rows as &$row) {
            $row['pct'] = $total > 0 ? round($row['count'] / $total * 100, 1) : 0;
        }
        unset($row);
        return $rows;
    }

    /**
     * @param mysqli $conn
     * @param string $where
     */
    private static function fetchByHour($conn, $where)
    {
        $sql = "SELECT HOUR(`visit_date`) AS hr, COUNT(*) AS cnt
            FROM `visitor_logs` WHERE {$where}
            GROUP BY hr ORDER BY hr ASC";
        $res  = $conn->query($sql);
        $map  = array_fill(0, 24, 0);
        if ($res) {
            while ($r = $res->fetch_assoc()) {
                $hr = (int) $r['hr'];
                if ($hr >= 0 && $hr <= 23) {
                    $map[$hr] = (int) $r['cnt'];
                }
            }
            $res->free();
        }
        $rows = [];
        for ($h = 0; $h < 24; $h++) {
            $rows[] = ['hour' => sprintf('%02d:00', $h), 'count' => $map[$h]];
        }
        return $rows;
    }

    /**
     * @param mysqli $conn
     * @param string $where
     * @param int    $limit
     */
    private static function fetchDeviceTop($conn, $where, $limit)
    {
        $limit = (int) $limit;
        $sql   = "SELECT IFNULL(NULLIF(TRIM(`device`), ''), '未知') AS device, COUNT(*) AS cnt
            FROM `visitor_logs` WHERE {$where}
            GROUP BY device ORDER BY cnt DESC LIMIT {$limit}";
        $res  = $conn->query($sql);
        $rows = [];
        if ($res) {
            while ($r = $res->fetch_assoc()) {
                $rows[] = [
                    'device' => (string) $r['device'],
                    'count'  => (int) $r['cnt'],
                ];
            }
            $res->free();
        }
        return $rows;
    }

    /**
     * @param mysqli $conn
     * @param string $where
     */
    private static function fetchTimingStats($conn, $where)
    {
        $sql = "SELECT `judge_timing` FROM `visitor_logs`
            WHERE {$where} AND `judge_timing` IS NOT NULL AND `judge_timing` <> ''
            ORDER BY `id` DESC LIMIT 500";
        $res = $conn->query($sql);

        $stageTotals = [];
        $stageCounts = [];
        $totalMsSum  = 0;
        $totalMsCnt  = 0;

        if ($res) {
            while ($r = $res->fetch_assoc()) {
                $decoded = json_decode((string) ($r['judge_timing'] ?? ''), true);
                if (!is_array($decoded)) {
                    continue;
                }
                if (isset($decoded['total_ms'])) {
                    $totalMsSum += (float) $decoded['total_ms'];
                    $totalMsCnt++;
                }
                if (!empty($decoded['stages']) && is_array($decoded['stages'])) {
                    foreach ($decoded['stages'] as $stage) {
                        if (!is_array($stage)) {
                            continue;
                        }
                        $name = trim((string) ($stage['name'] ?? ''));
                        if ($name === '') {
                            continue;
                        }
                        $ms = (float) ($stage['ms'] ?? 0);
                        if (!isset($stageTotals[$name])) {
                            $stageTotals[$name] = 0;
                            $stageCounts[$name] = 0;
                        }
                        $stageTotals[$name] += $ms;
                        $stageCounts[$name]++;
                    }
                }
            }
            $res->free();
        }

        $stages = [];
        foreach ($stageTotals as $name => $sum) {
            $cnt = max(1, $stageCounts[$name]);
            $stages[] = [
                'stage'  => $name,
                'avg_ms' => round($sum / $cnt, 2),
            ];
        }
        usort($stages, static function ($a, $b) {
            return $b['avg_ms'] <=> $a['avg_ms'];
        });

        return [
            'avg_total_ms' => $totalMsCnt > 0 ? round($totalMsSum / $totalMsCnt, 2) : 0,
            'sample_size'  => $totalMsCnt,
            'stages'       => $stages,
        ];
    }

    /**
     * @param mysqli $conn
     * @param array  $filters
     * @param array  $currentSummary
     */
    private static function fetchCompare($conn, array $filters, array $currentSummary)
    {
        $dateFrom = trim((string) ($filters['date_from'] ?? ''));
        $dateTo   = trim((string) ($filters['date_to'] ?? ''));
        if ($dateFrom === '' || $dateTo === '') {
            $dateTo   = date('Y-m-d');
            $dateFrom = date('Y-m-d', strtotime('-6 days'));
        }

        $start = strtotime($dateFrom . ' 00:00:00');
        $end   = strtotime($dateTo . ' 23:59:59');
        if ($start === false || $end === false || $end < $start) {
            return [
                'prev_total'       => 0,
                'pass_rate_delta'  => 0,
                'block_rate_delta' => 0,
            ];
        }

        $days     = max(1, (int) floor(($end - $start) / 86400) + 1);
        $prevEnd  = date('Y-m-d 23:59:59', $start - 86400);
        $prevStart = date('Y-m-d 00:00:00', $start - ($days * 86400));

        $prevFilters = $filters;
        $prevFilters['date_from'] = substr($prevStart, 0, 10);
        $prevFilters['date_to']   = substr($prevEnd, 0, 10);
        $prevWhere = self::buildWhere($conn, $prevFilters);
        $prevSummary = self::fetchSummary($conn, $prevWhere);

        return [
            'prev_total'       => (int) $prevSummary['total'],
            'pass_rate_delta'  => round($currentSummary['pass_rate'] - $prevSummary['pass_rate'], 1),
            'block_rate_delta' => round($currentSummary['block_rate'] - $prevSummary['block_rate'], 1),
        ];
    }

    /**
     * @param array $summary
     * @param array $byReason
     * @param array $byCountry
     * @param array $byHour
     * @param array $byDevice
     * @param array $timing
     * @param array $compare
     * @param array $byResult
     * @return string[]
     */
    private static function buildConclusions(array $summary, array $byReason, array $byCountry, array $byHour, array $byDevice, array $timing, array $compare, array $byResult)
    {
        $lines = [];
        $total = (int) $summary['total'];

        if ($total === 0) {
            $lines[] = '所选时间范围内暂无访客日志，请调整筛选条件或确认异步队列已落库。';
            return $lines;
        }

        $lines[] = sprintf(
            '共记录 %d 次访问，通过率 %.1f%%，拦截率 %.1f%%，检测中占比 %.1f%%。',
            $total,
            $summary['pass_rate'],
            $summary['block_rate'],
            $summary['wait_rate']
        );

        if ($compare['prev_total'] > 0) {
            $deltaSign = $compare['pass_rate_delta'] >= 0 ? '上升' : '下降';
            $lines[] = sprintf(
                '与上一同等周期（%d 条）相比，通过率%s %.1f 个百分点，拦截率变化 %.1f 个百分点。',
                $compare['prev_total'],
                $deltaSign,
                abs($compare['pass_rate_delta']),
                $compare['block_rate_delta']
            );
        }

        if ($byReason !== []) {
            $top = $byReason[0];
            $lines[] = sprintf(
                '主要拦截/待判定理由为「%s」，占该类记录 %.1f%%（%d 次）。',
                $top['reason'],
                $top['pct'],
                $top['count']
            );
            if (count($byReason) >= 2) {
                $second = $byReason[1];
                $lines[] = sprintf('次常见理由：「%s」（%d 次，%.1f%%）。', $second['reason'], $second['count'], $second['pct']);
            }
        }

        $peakHour = null;
        $peakCnt  = 0;
        foreach ($byHour as $h) {
            if ($h['count'] > $peakCnt) {
                $peakCnt  = $h['count'];
                $peakHour = $h['hour'];
            }
        }
        if ($peakHour !== null && $peakCnt > 0) {
            $lines[] = sprintf('访问高峰时段为 %s，共 %d 次访问。', $peakHour, $peakCnt);
        }

        if ($byCountry !== []) {
            $topC = $byCountry[0];
            $countryLabel = $topC['country'] === '（空）' ? '未知国家' : $topC['country'];
            $lines[] = sprintf('访客来源以「%s」最多，共 %d 次。', $countryLabel, $topC['count']);
        }

        if ($byDevice !== []) {
            $lines[] = sprintf('设备分布首位：%s（%d 次）。', $byDevice[0]['device'], $byDevice[0]['count']);
        }

        if ($timing['sample_size'] > 0) {
            $lines[] = sprintf('判定耗时抽样 %d 条，平均总耗时 %.2f ms。', $timing['sample_size'], $timing['avg_total_ms']);
            if (!empty($timing['stages'])) {
                $slow = $timing['stages'][0];
                $lines[] = sprintf('最慢阶段为「%s」，平均 %.2f ms。', $slow['stage'], $slow['avg_ms']);
            }
        }

        if ($summary['block_rate'] > 60) {
            $lines[] = '告警：拦截率超过 60%，建议检查 API 配置、黑白名单及广告来源规则是否过严。';
        }
        if ($summary['wait_rate'] > 5) {
            $lines[] = '告警：检测中（wait）状态占比偏高，可能存在二次风控未完成或 f_check 回调异常。';
        }

        foreach (array_slice($byReason, 0, 3) as $r) {
            if ($r['pct'] >= 40 && $total >= 20) {
                $lines[] = sprintf('关注：理由「%s」占比达 %.1f%%，建议针对性排查该规则。', $r['reason'], $r['pct']);
                break;
            }
        }

        return $lines;
    }
}
