<?php
// Authoritative MAC ownership resolver shared by Security Monitor and receiver matching.
// It does not poll MikroTik. It resolves only from data already collected by the monitor/cache.

require_once __DIR__ . '/helpers.php';
require_once __DIR__ . '/ppp_active_cache_lib.php';

if (!function_exists('uamac_norm_mac')) {
function uamac_norm_mac(string $mac): string {
    if (function_exists('mtwatch_norm_mac')) return mtwatch_norm_mac($mac);
    $mac = strtoupper(trim($mac));
    $mac = preg_replace('/[^0-9A-F]/', '', $mac) ?? '';
    if (strlen($mac) !== 12) return '';
    return implode(':', str_split($mac, 2));
}
}

if (!function_exists('uamac_norm_user')) {
function uamac_norm_user(string $u): string {
    if (function_exists('mtwatch_norm_user')) return mtwatch_norm_user($u);
    $u = trim($u);
    $u = preg_replace('/@.*/', '', $u) ?? $u;
    return trim($u);
}
}

if (!function_exists('uamac_table_exists')) {
function uamac_table_exists(mysqli $conn, string $table): bool {
    static $cache = [];
    if (array_key_exists($table, $cache)) return $cache[$table];
    $t = $conn->real_escape_string($table);
    $rs = @$conn->query("SHOW TABLES LIKE '{$t}'");
    return $cache[$table] = (bool)($rs && $rs->num_rows > 0);
}
}

if (!function_exists('uamac_col_exists')) {
function uamac_col_exists(mysqli $conn, string $table, string $col): bool {
    static $cache = [];
    $k = $table . '|' . $col;
    if (array_key_exists($k, $cache)) return $cache[$k];
    $t = $conn->real_escape_string($table);
    $c = $conn->real_escape_string($col);
    $rs = @$conn->query("SHOW COLUMNS FROM `{$t}` LIKE '{$c}'");
    return $cache[$k] = (bool)($rs && $rs->num_rows > 0);
}
}

if (!function_exists('uamac_ensure_schema')) {
function uamac_ensure_schema(mysqli $conn): void {
    @mysqli_report(MYSQLI_REPORT_OFF);
    @ $conn->query("CREATE TABLE IF NOT EXISTS mtwatch_mac_ownership (
        id INT UNSIGNED NOT NULL AUTO_INCREMENT,
        mac_address VARCHAR(32) NOT NULL,
        subscriber_id INT NULL,
        username VARCHAR(191) NULL,
        status VARCHAR(32) NOT NULL DEFAULT 'unknown',
        confidence INT NOT NULL DEFAULT 0,
        resolution_source VARCHAR(80) NULL,
        reason TEXT NULL,
        candidates_json LONGTEXT NULL,
        first_seen_at DATETIME NOT NULL DEFAULT CURRENT_TIMESTAMP,
        last_seen_at DATETIME NOT NULL DEFAULT CURRENT_TIMESTAMP,
        resolved_at DATETIME NULL,
        resolved_by VARCHAR(80) NULL,
        cleanup_count INT NOT NULL DEFAULT 0,
        PRIMARY KEY(id),
        UNIQUE KEY uniq_mac_address (mac_address),
        KEY idx_status_seen (status, last_seen_at),
        KEY idx_subscriber (subscriber_id)
    ) ENGINE=InnoDB DEFAULT CHARSET=utf8mb4 COLLATE=utf8mb4_unicode_ci");
}
}

if (!function_exists('uamac_cache_clear')) {
function uamac_cache_clear(): void {
    foreach ([
        __DIR__ . '/cache/runtime/receiver_matches/snapshot.json',
        __DIR__ . '/cache/runtime/receiver_matches/snapshot.lock',
        __DIR__ . '/cache/runtime/app_stats_snapshot.json',
        __DIR__ . '/cache/runtime/app_stats_snapshot.lock',
    ] as $f) if (is_file($f)) @unlink($f);
}
}

if (!function_exists('uamac_candidate_label')) {
function uamac_candidate_label(array $c): string {
    $name = trim((string)($c['full_name'] ?? ''));
    $ppp = trim((string)($c['ppp_username'] ?? '')) ?: trim((string)($c['username'] ?? ''));
    return '#' . (int)($c['id'] ?? 0) . ' ' . ($name !== '' ? $name : $ppp);
}
}

if (!function_exists('uamac_add_candidate')) {
function uamac_add_candidate(array &$byMac, string $mac, array $owner, string $source, int $priority, ?string $seenAt = null): void {
    $mac = uamac_norm_mac($mac);
    $sid = (int)($owner['id'] ?? 0);
    if ($mac === '' || $sid <= 0) return;
    if (!isset($byMac[$mac][$sid])) {
        $byMac[$mac][$sid] = [
            'id'=>$sid,
            'username'=>(string)($owner['username'] ?? ''),
            'ppp_username'=>(string)($owner['ppp_username'] ?? ''),
            'ppp_user'=>(string)($owner['ppp_user'] ?? ''),
            'full_name'=>(string)($owner['full_name'] ?? ''),
            'priority'=>0,
            'sources'=>[],
            'seen_at'=>$seenAt ?: '',
        ];
    }
    $byMac[$mac][$sid]['priority'] = max((int)$byMac[$mac][$sid]['priority'], $priority);
    $byMac[$mac][$sid]['sources'][$source] = true;
    if ($seenAt && (empty($byMac[$mac][$sid]['seen_at']) || strtotime($seenAt) > strtotime((string)$byMac[$mac][$sid]['seen_at']))) {
        $byMac[$mac][$sid]['seen_at'] = $seenAt;
    }
}
}

if (!function_exists('uamac_subscribers_by_user')) {
function uamac_subscribers_by_user(mysqli $conn): array {
    $out = [];
    if (!uamac_table_exists($conn, 'subscribers')) return $out;
    $cols = ['id','username'];
    foreach (['ppp_username','full_name'] as $c) if (uamac_col_exists($conn, 'subscribers', $c)) $cols[] = $c;
    $where = uamac_col_exists($conn, 'subscribers', 'is_deleted') ? 'WHERE COALESCE(is_deleted,0)=0' : '';
    $rs = @$conn->query('SELECT ' . implode(',', array_map(static fn($c) => '`'.$c.'`', array_unique($cols))) . " FROM subscribers {$where}");
    if ($rs) while ($r = $rs->fetch_assoc()) {
        $u = uamac_norm_user((string)($r['ppp_username'] ?? $r['username'] ?? ''));
        if ($u !== '') $out[$u] = $r;
    }
    return $out;
}
}

if (!function_exists('uamac_collect_candidates')) {
function uamac_collect_candidates(mysqli $conn, array $activeRows = []): array {
    uamac_ensure_schema($conn);
    $byMac = [];
    $byUser = uamac_subscribers_by_user($conn);

    if (uamac_table_exists($conn, 'subscribers')) {
        $macCols = [
            'current_caller_id'=>78,
            'current_mac'=>76,
            'last_caller_id'=>46,
            'last_mac'=>44,
            'caller_id'=>38,
            'mac_address'=>38,
        ];
        $cols = ['id','username'];
        foreach (['ppp_username','full_name'] as $c) if (uamac_col_exists($conn,'subscribers',$c)) $cols[] = $c;
        foreach ($macCols as $c => $_p) if (uamac_col_exists($conn,'subscribers',$c)) $cols[] = $c;
        $where = uamac_col_exists($conn, 'subscribers', 'is_deleted') ? 'WHERE COALESCE(is_deleted,0)=0' : '';
        $rs = @$conn->query('SELECT ' . implode(',', array_map(static fn($c) => '`'.$c.'`', array_unique($cols))) . " FROM subscribers {$where}");
        if ($rs) while ($r = $rs->fetch_assoc()) {
            $owner = [
                'id'=>(int)($r['id'] ?? 0),
                'username'=>(string)($r['username'] ?? ''),
                'ppp_username'=>(string)($r['ppp_username'] ?? ''),
                'ppp_user'=>uamac_norm_user((string)($r['ppp_username'] ?? $r['username'] ?? '')),
                'full_name'=>(string)($r['full_name'] ?? ''),
            ];
            foreach ($macCols as $c => $priority) if (array_key_exists($c, $r)) uamac_add_candidate($byMac, (string)$r[$c], $owner, 'subscribers.' . $c, $priority, null);
        }
    }

    if (uamac_table_exists($conn, 'subscriber_mac_history')) {
        $rs = @$conn->query("SELECT h.mac_address AS mac, h.subscriber_id AS id, h.username, h.last_seen_at, s.full_name, s.ppp_username, s.username AS app_username FROM subscriber_mac_history h LEFT JOIN subscribers s ON s.id=h.subscriber_id ORDER BY h.last_seen_at DESC LIMIT 5000");
        if ($rs) while ($r = $rs->fetch_assoc()) {
            $owner = [
                'id'=>(int)($r['id'] ?? 0),
                'username'=>(string)($r['app_username'] ?? $r['username'] ?? ''),
                'ppp_username'=>(string)($r['ppp_username'] ?? $r['username'] ?? ''),
                'ppp_user'=>uamac_norm_user((string)($r['ppp_username'] ?? $r['username'] ?? '')),
                'full_name'=>(string)($r['full_name'] ?? ''),
            ];
            uamac_add_candidate($byMac, (string)($r['mac'] ?? ''), $owner, 'subscriber_mac_history', 28, (string)($r['last_seen_at'] ?? ''));
        }
    }

    // Fresh rows supplied by the security worker are the strongest source and may safely resolve stale duplicates.
    foreach ($activeRows as $row) {
        if (!is_array($row)) continue;
        $u = uamac_norm_user((string)($row['name'] ?? $row['user'] ?? $row['username'] ?? ''));
        if ($u === '' || !isset($byUser[$u])) continue;
        $mac = (string)($row['caller_id'] ?? $row['caller-id'] ?? $row['mac'] ?? '');
        $owner = $byUser[$u];
        $owner['ppp_user'] = $u;
        uamac_add_candidate($byMac, $mac, $owner, 'router_active', 100, date('Y-m-d H:i:s'));
    }

    // Existing PPP active cache is reused only as a secondary clue; no MikroTik call here.
    $pppCache = function_exists('ua_ppp_cache_snapshot') ? ua_ppp_cache_snapshot(86400) : [];
    foreach ((array)($pppCache['rows'] ?? []) as $row) {
        $u = uamac_norm_user((string)($row['name'] ?? $row['username'] ?? ''));
        if ($u === '' || !isset($byUser[$u])) continue;
        $owner = $byUser[$u];
        $owner['ppp_user'] = $u;
        uamac_add_candidate($byMac, (string)($row['caller_id'] ?? $row['caller-id'] ?? ''), $owner, 'ppp_active_cache', 90, (string)($pppCache['updated_at'] ?? date('Y-m-d H:i:s')));
    }

    return $byMac;
}
}

if (!function_exists('uamac_pick_owner')) {
function uamac_pick_owner(array $candidates): array {
    $candidates = array_values($candidates);
    if (!$candidates) return ['status'=>'unknown','owner'=>null,'confidence'=>0,'source'=>'none','reason'=>'no candidates'];
    usort($candidates, static function($a, $b){ return ((int)($b['priority'] ?? 0)) <=> ((int)($a['priority'] ?? 0)); });

    $routerActive = [];
    $pppActive = [];
    $current = [];
    foreach ($candidates as $c) {
        $src = array_keys((array)($c['sources'] ?? []));
        if (in_array('router_active', $src, true)) $routerActive[(int)$c['id']] = $c;
        if (in_array('ppp_active_cache', $src, true)) $pppActive[(int)$c['id']] = $c;
        if (array_intersect($src, ['subscribers.current_caller_id','subscribers.current_mac'])) $current[(int)$c['id']] = $c;
    }
    if (count($routerActive) === 1) {
        $owner = reset($routerActive);
        return ['status'=>'auto_active','owner'=>$owner,'confidence'=>100,'source'=>'router_active','reason'=>'الحساب متصل حالياً بهذا MAC في قراءة مراقب الشبكة؛ لذلك هو المالك الرسمي.'];
    }
    if (count($routerActive) > 1) {
        return ['status'=>'conflict','owner'=>null,'confidence'=>0,'source'=>'multi_router_active','reason'=>'نفس MAC نشط حالياً على أكثر من حساب، ولا يجوز حسمه تلقائياً.'];
    }
    if (count($pppActive) === 1) {
        $owner = reset($pppActive);
        return ['status'=>'auto_current','owner'=>$owner,'confidence'=>90,'source'=>'ppp_active_cache','reason'=>'كاش PPP النشط يربط هذا MAC بحساب واحد فقط.'];
    }
    if (count($current) === 1) {
        $owner = reset($current);
        return ['status'=>'auto_current','owner'=>$owner,'confidence'=>82,'source'=>'current_field','reason'=>'يوجد مالك تشغيلي/حالي واحد لهذا MAC داخل بيانات المشتركين.'];
    }
    if (count($candidates) === 1) {
        return ['status'=>'single','owner'=>$candidates[0],'confidence'=>(int)($candidates[0]['priority'] ?? 50),'source'=>'single_candidate','reason'=>'MAC موجود عند مشترك واحد فقط.'];
    }
    $top = $candidates[0];
    $second = $candidates[1] ?? null;
    if ((int)($top['priority'] ?? 0) >= 76 && (!$second || ((int)$top['priority'] - (int)($second['priority'] ?? 0)) >= 25)) {
        return ['status'=>'auto_current','owner'=>$top,'confidence'=>(int)$top['priority'],'source'=>'priority_current_vs_stale','reason'=>'حقل تشغيلي حالي أقوى من سجلات قديمة لنفس MAC.'];
    }
    return ['status'=>'conflict','owner'=>null,'confidence'=>0,'source'=>'multiple_candidates','reason'=>'MAC مسجل عند أكثر من مشترك ولا يوجد دليل حالي وحيد يحسمه.'];
}
}

if (!function_exists('uamac_clean_stale_owner_fields')) {
function uamac_clean_stale_owner_fields(mysqli $conn, string $mac, int $winnerId, array $candidates, string $reason = ''): int {
    $mac = uamac_norm_mac($mac);
    if ($mac === '' || $winnerId <= 0 || !uamac_table_exists($conn, 'subscribers')) return 0;
    $cleanCols = ['current_caller_id','last_caller_id','current_mac','last_mac'];
    $sets = [];
    foreach ($cleanCols as $c) if (uamac_col_exists($conn, 'subscribers', $c)) $sets[] = "`{$c}`=CASE WHEN UPPER(REPLACE(REPLACE(REPLACE(COALESCE(`{$c}`,''),':',''),'-',''),'.',''))='" . str_replace(':','',$conn->real_escape_string($mac)) . "' THEN NULL ELSE `{$c}` END";
    if (uamac_col_exists($conn,'subscribers','airmax_matched_mac')) {
        $sets[] = "airmax_device_id=CASE WHEN UPPER(REPLACE(REPLACE(REPLACE(COALESCE(airmax_matched_mac,''),':',''),'-',''),'.',''))='" . str_replace(':','',$conn->real_escape_string($mac)) . "' THEN NULL ELSE airmax_device_id END";
        $sets[] = "airmax_device_name=CASE WHEN UPPER(REPLACE(REPLACE(REPLACE(COALESCE(airmax_matched_mac,''),':',''),'-',''),'.',''))='" . str_replace(':','',$conn->real_escape_string($mac)) . "' THEN NULL ELSE airmax_device_name END";
        $sets[] = "airmax_matched_mac=CASE WHEN UPPER(REPLACE(REPLACE(REPLACE(COALESCE(airmax_matched_mac,''),':',''),'-',''),'.',''))='" . str_replace(':','',$conn->real_escape_string($mac)) . "' THEN NULL ELSE airmax_matched_mac END";
        $sets[] = "airmax_match_source=CASE WHEN UPPER(REPLACE(REPLACE(REPLACE(COALESCE(airmax_matched_mac,''),':',''),'-',''),'.',''))='" . str_replace(':','',$conn->real_escape_string($mac)) . "' THEN NULL ELSE airmax_match_source END";
        $sets[] = "airmax_match_confidence=CASE WHEN UPPER(REPLACE(REPLACE(REPLACE(COALESCE(airmax_matched_mac,''),':',''),'-',''),'.',''))='" . str_replace(':','',$conn->real_escape_string($mac)) . "' THEN NULL ELSE airmax_match_confidence END";
    }
    if (!$sets) return 0;
    $losers = [];
    foreach ($candidates as $c) { $sid = (int)($c['id'] ?? 0); if ($sid > 0 && $sid !== $winnerId) $losers[$sid] = true; }
    if (!$losers) return 0;
    $ids = implode(',', array_map('intval', array_keys($losers)));
    $sql = "UPDATE subscribers SET " . implode(',', $sets) . " WHERE id IN ({$ids})";
    $ok = @$conn->query($sql);
    $affected = $ok ? max(0, (int)$conn->affected_rows) : 0;
    if ($affected > 0 && function_exists('mtwatch_log_event_unique')) {
        foreach (array_keys($losers) as $sid) {
            mtwatch_log_event_unique($conn, (int)$sid, '', 'MAC_OWNERSHIP_STALE_CLEANED', 'تنظيف MAC قديم', 'تم تنظيف MAC قديم ' . $mac . ' من الحقول التشغيلية بعد حسم ملكيته للمشترك #' . $winnerId . ($reason !== '' ? ' — ' . $reason : ''), date('Y-m-d H:i:s'), hash('sha256', 'uamac_clean|'.$sid.'|'.$winnerId.'|'.$mac.'|'.date('Y-m-d')));
        }
    }
    if ($affected > 0) uamac_cache_clear();
    return $affected;
}
}

if (!function_exists('uamac_upsert_ownership')) {
function uamac_upsert_ownership(mysqli $conn, string $mac, ?array $owner, string $status, int $confidence, string $source, string $reason, array $candidates, int $cleanupCount = 0, string $resolvedBy = 'auto'): void {
    uamac_ensure_schema($conn);
    $mac = uamac_norm_mac($mac);
    if ($mac === '') return;
    $sid = $owner ? (int)($owner['id'] ?? 0) : 0;
    $username = $owner ? (string)($owner['ppp_username'] ?? $owner['username'] ?? $owner['ppp_user'] ?? '') : '';
    $cand = [];
    foreach ($candidates as $c) {
        $tmp = $c;
        $tmp['sources'] = array_keys((array)($tmp['sources'] ?? []));
        $cand[] = $tmp;
    }
    $json = json_encode($cand, JSON_UNESCAPED_UNICODE|JSON_UNESCAPED_SLASHES);
    $stmt = @$conn->prepare("INSERT INTO mtwatch_mac_ownership (mac_address,subscriber_id,username,status,confidence,resolution_source,reason,candidates_json,last_seen_at,resolved_at,resolved_by,cleanup_count)
        VALUES (?,?,?,?,?,?,?,?,NOW(),IF(? IN ('conflict','manual_needed'),NULL,NOW()),?,?)
        ON DUPLICATE KEY UPDATE subscriber_id=VALUES(subscriber_id), username=VALUES(username), status=VALUES(status), confidence=VALUES(confidence), resolution_source=VALUES(resolution_source), reason=VALUES(reason), candidates_json=VALUES(candidates_json), last_seen_at=NOW(), resolved_at=VALUES(resolved_at), resolved_by=VALUES(resolved_by), cleanup_count=cleanup_count+VALUES(cleanup_count)");
    if (!$stmt) return;
    $st = $status; $src = $source; $rsn = $reason; $by = $resolvedBy;
    $stmt->bind_param('sississsssi', $mac, $sid, $username, $st, $confidence, $src, $rsn, $json, $st, $by, $cleanupCount);
    @$stmt->execute();
    $stmt->close();
}
}


if (!function_exists('uamac_ensure_alert_schema')) {
function uamac_ensure_alert_schema(mysqli $conn): void {
    @mysqli_report(MYSQLI_REPORT_OFF);
    @ $conn->query("CREATE TABLE IF NOT EXISTS subscriber_network_alerts (
      id INT UNSIGNED NOT NULL AUTO_INCREMENT,
      alert_key VARCHAR(190) NOT NULL,
      severity VARCHAR(20) NOT NULL DEFAULT 'warn',
      title VARCHAR(191) NOT NULL,
      message TEXT NULL,
      subscriber_id INT NULL,
      username VARCHAR(191) NULL,
      mac_address VARCHAR(32) NULL,
      ip_address VARCHAR(45) NULL,
      source VARCHAR(40) NULL,
      is_open TINYINT(1) NOT NULL DEFAULT 1,
      first_seen_at DATETIME NOT NULL DEFAULT CURRENT_TIMESTAMP,
      last_seen_at DATETIME NOT NULL DEFAULT CURRENT_TIMESTAMP,
      hit_count INT NOT NULL DEFAULT 1,
      meta_json LONGTEXT NULL,
      PRIMARY KEY(id),
      UNIQUE KEY uniq_alert_key (alert_key),
      KEY idx_open_seen (is_open, last_seen_at),
      KEY idx_subscriber (subscriber_id),
      KEY idx_mac (mac_address)
    ) ENGINE=InnoDB DEFAULT CHARSET=utf8mb4 COLLATE=utf8mb4_unicode_ci");
    foreach ([
        'stage' => "ALTER TABLE subscriber_network_alerts ADD COLUMN `stage` VARCHAR(30) NOT NULL DEFAULT 'confirmed'",
        'decision_status' => "ALTER TABLE subscriber_network_alerts ADD COLUMN `decision_status` VARCHAR(30) NULL",
        'decision_note' => "ALTER TABLE subscriber_network_alerts ADD COLUMN `decision_note` TEXT NULL",
        'confirmed_at' => "ALTER TABLE subscriber_network_alerts ADD COLUMN `confirmed_at` DATETIME NULL",
        'updated_at' => "ALTER TABLE subscriber_network_alerts ADD COLUMN `updated_at` DATETIME NULL",
    ] as $col => $ddl) {
        if (!uamac_col_exists($conn, 'subscriber_network_alerts', $col)) @ $conn->query($ddl);
    }
}
}

if (!function_exists('uamac_alert_upsert_direct')) {
function uamac_alert_upsert_direct(mysqli $conn, string $key, string $severity, string $title, string $message, ?int $subscriberId, string $username, string $mac, string $ip, string $source, array $meta = []): void {
    uamac_ensure_alert_schema($conn);
    $key = substr(preg_replace('/\s+/', '_', trim($key)) ?? '', 0, 190);
    if ($key === '') return;
    $severity = in_array($severity, ['info','warn','danger','success'], true) ? $severity : 'warn';
    $mac = uamac_norm_mac($mac);
    $username = uamac_norm_user($username);
    $stage = 'confirmed';
    if (isset($meta['stage'])) {
        $stageRaw = strtolower(trim((string)$meta['stage']));
        if (in_array($stageRaw, ['learning','identifying','suspect','confirmed','closed','allowlisted','resolved'], true)) $stage = $stageRaw;
    }
    $json = json_encode($meta, JSON_UNESCAPED_UNICODE|JSON_UNESCAPED_SLASHES);
    if (!is_string($json)) $json = '{}';
    $stmt = @$conn->prepare("INSERT INTO subscriber_network_alerts
      (alert_key, severity, title, message, subscriber_id, username, mac_address, ip_address, source, is_open, first_seen_at, last_seen_at, hit_count, meta_json, stage, confirmed_at)
      VALUES (?,?,?,?,?,?,?,?,?,1,NOW(),NOW(),1,?,?,IF(?='confirmed',NOW(),NULL))
      ON DUPLICATE KEY UPDATE severity=VALUES(severity), title=VALUES(title), message=VALUES(message), subscriber_id=VALUES(subscriber_id), username=VALUES(username), mac_address=VALUES(mac_address), ip_address=VALUES(ip_address), source=VALUES(source), is_open=1, last_seen_at=NOW(), hit_count=hit_count+1, meta_json=VALUES(meta_json), stage=VALUES(stage), confirmed_at=IF(VALUES(stage)='confirmed' AND confirmed_at IS NULL,NOW(),confirmed_at), updated_at=NOW()");
    if (!$stmt) return;
    $sid = $subscriberId ?: null;
    $stmt->bind_param('ssssisssssss', $key, $severity, $title, $message, $sid, $username, $mac, $ip, $source, $json, $stage, $stage);
    @$stmt->execute();
    $stmt->close();
}
}

if (!function_exists('uamac_register_conflict')) {
function uamac_register_conflict(mysqli $conn, string $mac, array $candidates, string $reason = 'MAC مسجل عند أكثر من مشترك ولا يوجد دليل حالي وحيد يحسمه.', string $reportedBy = 'receiver_match'): void {
    uamac_ensure_schema($conn);
    $mac = uamac_norm_mac($mac);
    if ($mac === '') return;
    $normalized = [];
    foreach ($candidates as $c) {
        $sid = (int)($c['id'] ?? 0);
        if ($sid <= 0) continue;
        if (!isset($c['sources']) || !is_array($c['sources'])) {
            $src = (string)($c['mac_source'] ?? $reportedBy);
            $c['sources'] = $src !== '' ? [$src => true] : [$reportedBy => true];
        }
        $c['priority'] = isset($c['priority']) ? (int)$c['priority'] : 0;
        $normalized[$sid] = $c;
    }
    if (count($normalized) < 2) return;
    uamac_upsert_ownership($conn, $mac, null, 'conflict', 0, $reportedBy, $reason, $normalized, 0, 'security_monitor');
    uamac_alert_conflict($conn, $mac, $normalized, $reason);
}
}

if (!function_exists('uamac_alert_conflict')) {
function uamac_alert_conflict(mysqli $conn, string $mac, array $candidates, string $reason): void {
    $labels = [];
    foreach ($candidates as $c) $labels[] = uamac_candidate_label($c);
    $msg = 'MAC ' . $mac . ' مسجل عند أكثر من مشترك: ' . implode('، ', $labels) . ' — ' . $reason;
    $meta = [
        'stage'=>'confirmed',
        'conflict_type'=>'duplicate_mac_ownership',
        'candidates'=>array_values($candidates),
    ];
    if (function_exists('mtwatch_alert')) {
        mtwatch_alert($conn, 'mac_ownership_conflict:' . $mac, 'warn', 'تعارض ملكية MAC', $msg, null, '', $mac, '', 'mac_ownership', $meta);
    } else {
        uamac_alert_upsert_direct($conn, 'mac_ownership_conflict:' . $mac, 'warn', 'تعارض ملكية MAC', $msg, null, '', $mac, '', 'mac_ownership', $meta);
    }
}
}

if (!function_exists('uamac_close_conflict_alert')) {
function uamac_close_conflict_alert(mysqli $conn, string $mac, string $note): void {
    $key = 'mac_ownership_conflict:' . uamac_norm_mac($mac);
    if (function_exists('mtwatch_close_alert_key')) {
        mtwatch_close_alert_key($conn, $key, 'resolved', $note);
        return;
    }
    uamac_ensure_alert_schema($conn);
    $ke = $conn->real_escape_string(substr(preg_replace('/\s+/', '_', trim($key)) ?? '', 0, 190));
    $no = $conn->real_escape_string($note);
    @ $conn->query("UPDATE subscriber_network_alerts SET is_open=0, stage='resolved', decision_status='resolved', decision_note='{$no}', updated_at=NOW(), last_seen_at=NOW() WHERE alert_key='{$ke}' AND is_open=1 LIMIT 1");
}
}

if (!function_exists('uamac_resolve_mac_ownership')) {
function uamac_resolve_mac_ownership(mysqli $conn, array $activeRows = [], array $opts = []): array {
    uamac_ensure_schema($conn);
    $autoCleanup = !empty($opts['auto_cleanup']);
    $candsByMac = uamac_collect_candidates($conn, $activeRows);
    $resolved = 0; $conflicts = 0; $cleaned = 0;
    foreach ($candsByMac as $mac => $cands) {
        $pick = uamac_pick_owner($cands);
        $status = (string)($pick['status'] ?? 'unknown');
        $owner = is_array($pick['owner'] ?? null) ? $pick['owner'] : null;
        if ($status === 'conflict') {
            uamac_upsert_ownership($conn, $mac, null, 'conflict', 0, (string)$pick['source'], (string)$pick['reason'], $cands, 0, 'security_monitor');
            uamac_alert_conflict($conn, $mac, $cands, (string)$pick['reason']);
            $conflicts++;
            continue;
        }
        if ($owner) {
            $cleanup = 0;
            if ($autoCleanup && count($cands) > 1 && (int)($pick['confidence'] ?? 0) >= 78) {
                $cleanup = uamac_clean_stale_owner_fields($conn, $mac, (int)$owner['id'], $cands, (string)$pick['reason']);
                $cleaned += $cleanup;
            }
            uamac_upsert_ownership($conn, $mac, $owner, $status, (int)$pick['confidence'], (string)$pick['source'], (string)$pick['reason'], $cands, $cleanup, 'security_monitor');
            if (count($cands) > 1) uamac_close_conflict_alert($conn, $mac, 'تم حسم ملكية MAC بواسطة مراقب الشبكة لصالح ' . uamac_candidate_label($owner) . '.');
            $resolved++;
        }
    }
    return ['ok'=>true,'resolved'=>$resolved,'conflicts'=>$conflicts,'cleaned'=>$cleaned,'macs'=>count($candsByMac)];
}
}

if (!function_exists('uamac_owner_index')) {
function uamac_owner_index(mysqli $conn): array {
    uamac_ensure_schema($conn);
    $out = [];
    $rs = @$conn->query("SELECT mac_address, subscriber_id, username, status, confidence, resolution_source FROM mtwatch_mac_ownership WHERE subscriber_id IS NOT NULL AND subscriber_id>0 AND status NOT IN ('conflict','manual_needed','unknown')");
    if ($rs) while ($r = $rs->fetch_assoc()) {
        $mac = uamac_norm_mac((string)($r['mac_address'] ?? ''));
        $sid = (int)($r['subscriber_id'] ?? 0);
        if ($mac !== '' && $sid > 0) $out[$mac] = $r;
    }
    return $out;
}
}

if (!function_exists('uamac_claim_owner')) {
function uamac_claim_owner(mysqli $conn, string $mac, int $subscriberId, bool $cleanOthers = true, string $by = 'security_monitor_manual'): array {
    uamac_ensure_schema($conn);
    $mac = uamac_norm_mac($mac);
    if ($mac === '' || $subscriberId <= 0 || !uamac_table_exists($conn, 'subscribers')) return ['ok'=>false,'message'=>'bad_mac_or_subscriber'];
    $rs = @$conn->query("SELECT id, username, ppp_username, full_name FROM subscribers WHERE id={$subscriberId} LIMIT 1");
    $owner = ($rs && ($r = $rs->fetch_assoc())) ? $r : null;
    if (!$owner) return ['ok'=>false,'message'=>'subscriber_not_found'];
    $cands = uamac_collect_candidates($conn, []);
    $macCands = (array)($cands[$mac] ?? []);
    if (!isset($macCands[$subscriberId])) $macCands[$subscriberId] = $owner + ['sources'=>['manual'=>true],'priority'=>100];
    $cleanup = $cleanOthers ? uamac_clean_stale_owner_fields($conn, $mac, $subscriberId, $macCands, 'حسم يدوي من مراقب الشبكة') : 0;
    uamac_upsert_ownership($conn, $mac, $owner + ['ppp_user'=>uamac_norm_user((string)($owner['ppp_username'] ?? $owner['username'] ?? ''))], 'manual', 100, 'manual_security_monitor', 'تم اعتماد مالك MAC يدوياً من مراقب الشبكة.', $macCands, $cleanup, $by);
    uamac_close_conflict_alert($conn, $mac, 'تم حسم ملكية MAC يدوياً لصالح ' . uamac_candidate_label($owner) . '.');
    uamac_cache_clear();
    return ['ok'=>true,'cleaned'=>$cleanup,'subscriber_id'=>$subscriberId,'mac'=>$mac];
}
}
