<?php
session_start();

// Require ECUPRATIX auth (shared portal session)
if (!isset($_SESSION['auth'])) {
    echo json_encode(["status"=>"error","msg"=>"unauthorized"]);
    exit;
}
// Force admin role for this admin API
$_SESSION['role'] = 'admin';
if (!isset($_SESSION['user'])) $_SESSION['user'] = ($_SESSION['user_name'] ?? 'admin');

@ini_set('memory_limit', '1024M'); @ini_set('upload_max_filesize', '512M'); @ini_set('post_max_size', '512M'); @ini_set('max_execution_time', '300');
header('Content-Type: application/json');
$tempDir = __DIR__ . '/temp';
if (!file_exists($tempDir)) { @mkdir($tempDir, 0777, true); }
if (!is_dir($tempDir) || !is_writable($tempDir)) {
    $tempDir = sys_get_temp_dir() . '/ecupratix_temp';
    if (!file_exists($tempDir)) { @mkdir($tempDir, 0777, true); }
}
require_once __DIR__ . '/EcuEngine.php';

// ============================================================
// PATH NORMALIZER (master_bin_path)
// Some older profiles may store paths like "dtcpatcher/data/..." while
// runtime expects paths relative to this folder.
// This helper makes SIM calculation stable (no intermittent missing files).
// ============================================================
function _fs_profile_path(string $rel): string {
    $rel = ltrim($rel, '/');
    if (strpos($rel, 'dtcpatcher/') === 0) {
        $rel = substr($rel, strlen('dtcpatcher/'));
    }
    return __DIR__ . '/' . $rel;
}


// ===== USER DB + AUDIT LOG (admin UX) =====
$DATA_DIR = __DIR__ . '/data';
if (!file_exists($DATA_DIR)) { @mkdir($DATA_DIR, 0777, true); }
$USERS_DB = $DATA_DIR . '/users.json';
$AUDIT_LOG = $DATA_DIR . '/audit.jsonl';
$PACKAGES_DB = $DATA_DIR . '/packages.json';
$USAGE_DB = $DATA_DIR . '/usage.json';


function _now_iso() { return gmdate('c'); }

function _load_users_db() {
    global $USERS_DB;
    if (!file_exists($USERS_DB)) return null;
    $j = json_decode(@file_get_contents($USERS_DB), true);
    return is_array($j) ? $j : null;
}
function _save_users_db($db) {
    global $USERS_DB;
    @file_put_contents($USERS_DB, json_encode($db, JSON_PRETTY_PRINT|JSON_UNESCAPED_UNICODE));
}
function _ensure_default_users() {
    // Backward compatible: if users.json doesn't exist, allow hardcoded admin/user.
    // If it exists, it becomes the source of truth.
    global $USERS_DB;
    if (file_exists($USERS_DB)) return;
    $db = [
        "users" => [
            ["username"=>"admin","password"=>"pass","role"=>"admin","active"=>true,"created_at"=>_now_iso()],
            ["username"=>"user","password"=>"pass","role"=>"user","active"=>true,"created_at"=>_now_iso()]
        ]
    ];
    _save_users_db($db);
}
function _audit($action, $details = []) {
    global $AUDIT_LOG;
    $u = $_SESSION['user'] ?? '';
    $role = $_SESSION['role'] ?? '';
    $ip = $_SERVER['REMOTE_ADDR'] ?? '';
    $ua = $_SERVER['HTTP_USER_AGENT'] ?? '';
    $row = [
        "ts" => _now_iso(),
        "user" => $u,
        "role" => $role,
        "action" => $action,
        "ip" => $ip,
        "ua" => $ua,
        "details" => $details
    ];
    @file_put_contents($AUDIT_LOG, json_encode($row, JSON_UNESCAPED_UNICODE) . "\n", FILE_APPEND);
}
function _read_audit($limit = 1000) {
    global $AUDIT_LOG;
    if (!file_exists($AUDIT_LOG)) return [];
    $lines = @file($AUDIT_LOG, FILE_IGNORE_NEW_LINES|FILE_SKIP_EMPTY_LINES);
    if (!$lines) return [];
    $lines = array_slice($lines, max(0, count($lines)-$limit));
    $out = [];
    foreach ($lines as $ln) {
        $j = json_decode($ln, true);
        if (is_array($j)) $out[] = $j;
    }
    return $out;
}
_ensure_default_users();

function _load_packages_db() {
    global $PACKAGES_DB;
    if (!file_exists($PACKAGES_DB)) return null;
    $j = json_decode(@file_get_contents($PACKAGES_DB), true);
    return is_array($j) ? $j : null;
}
function _save_packages_db($db) {
    global $PACKAGES_DB;
    @file_put_contents($PACKAGES_DB, json_encode($db, JSON_PRETTY_PRINT|JSON_UNESCAPED_UNICODE));
}
function _ensure_default_packages() {
    global $PACKAGES_DB;
    if (file_exists($PACKAGES_DB)) return;
    $db = [
        "packages" => [
            [
                "id"=>"basic",
                "name"=>"Basic",
                "active"=>true,
                "daily_limit"=>20,
                "allowed_brands"=>[],
                "allowed_brand_groups"=>[],
                "notes"=>"Default package"
            ],
            [
                "id"=>"full",
                "name"=>"Full",
                "active"=>true,
                "daily_limit"=>200,
                "allowed_brands"=>[],
                "allowed_brand_groups"=>[],
                "notes"=>"Unlimited-like"
            ]
        ]
    ];
    _save_packages_db($db);
}
function _get_user_record($username) {
    $db = _load_users_db();
    if (!$db || !isset($db['users']) || !is_array($db['users'])) return null;
    foreach ($db['users'] as $usr) {
        if (($usr['username'] ?? '') === $username) return $usr;
    }
    return null;
}
function _date_ymd_utc($iso) {
    if (!$iso) return '';
    try { $dt = new DateTime($iso); return $dt->format('Y-m-d'); } catch(Exception $e) { return ''; }
}
function _today_ymd_utc() { return gmdate('Y-m-d'); }

function _within_validity($usr) {
    // returns [bool ok, string msg]
    if (!$usr) return [false, "user_not_found"];
    if (!($usr['active'] ?? true)) return [false, "inactive"];
    $vf = trim((string)($usr['valid_from'] ?? ''));
    $vt = trim((string)($usr['valid_to'] ?? ''));
    $today = _today_ymd_utc();
    if ($vf !== '' && $today < $vf) return [false, "not_started"];
    if ($vt !== '' && $today > $vt) return [false, "expired"];
    return [true, ""];
}
function _load_usage() {
    global $USAGE_DB;
    if (!file_exists($USAGE_DB)) return ["days"=>[]];
    $j = json_decode(@file_get_contents($USAGE_DB), true);
    return is_array($j) ? $j : ["days"=>[]];
}
function _save_usage($db) {
    global $USAGE_DB;
    @file_put_contents($USAGE_DB, json_encode($db, JSON_PRETTY_PRINT|JSON_UNESCAPED_UNICODE));
}
function _normalize_user_package_ids($usr) {
    // Backward compatible:
    // - new: package_ids: ["BASIC","PRO"]
    // - old: package_id: "BASIC"
    $ids = [];
    if (isset($usr['package_ids']) && is_array($usr['package_ids'])) {
        foreach ($usr['package_ids'] as $x) {
            $x = trim((string)$x);
            if ($x !== '' && !in_array($x, $ids)) $ids[] = $x;
        }
    }
    if (count($ids) === 0) {
        $one = trim((string)($usr['package_id'] ?? ''));
        if ($one !== '') $ids[] = $one;
    }
    return $ids;
}
function _get_user_packages($usr) {
    $ids = _normalize_user_package_ids($usr);
    if (count($ids) === 0) return [];
    $pdb = _load_packages_db();
    if (!$pdb || !isset($pdb['packages']) || !is_array($pdb['packages'])) return [];
    $out = [];
    foreach ($pdb['packages'] as $p) {
        $pid = (string)($p['id'] ?? '');
        if ($pid !== '' && in_array($pid, $ids) && ($p['active'] ?? true)) $out[] = $p;
    }
    return $out;
}
function _effective_daily_limit($usr) {
    // priority: user.daily_limit > max(packages.daily_limit) > default 20
    if (isset($usr['daily_limit']) && is_numeric($usr['daily_limit'])) return intval($usr['daily_limit']);
    $max = null;
    foreach (_get_user_packages($usr) as $p) {
        if (isset($p['daily_limit']) && is_numeric($p['daily_limit'])) {
            $v = intval($p['daily_limit']);
            if ($max === null || $v > $max) $max = $v;
        }
    }
    if ($max !== null) return $max;
    return 20;
}
function _consume_quota($username, $opKey, $inc = 1) {
    // opKey: 'op' (generic), we keep per-user per-day total
    $usr = _get_user_record($username);
    if (!$usr) return [false, 0, 0, "user_not_found"];
    $limit = _effective_daily_limit($usr);
    if ($limit <= 0) return [true, 0, 0, "unlimited"]; // 0 or negative => unlimited
    $day = _today_ymd_utc();
    $db = _load_usage();
    if (!isset($db['days'][$day])) $db['days'][$day] = ["users"=>[]];
    if (!isset($db['days'][$day]['users'][$username])) $db['days'][$day]['users'][$username] = ["count"=>0,"ops"=>[]];
    $urow = &$db['days'][$day]['users'][$username];
    if (!isset($urow['count'])) $urow['count'] = 0;
    if (!isset($urow['ops'])) $urow['ops'] = [];
    $curr = intval($urow['count']);
    if ($curr + $inc > $limit) {
        $remain = max(0, $limit - $curr);
        return [false, $remain, $limit, "quota_exceeded"];
    }
    $urow['count'] = $curr + $inc;
    $opKey = (string)$opKey;
    if (!isset($urow['ops'][$opKey])) $urow['ops'][$opKey] = 0;
    $urow['ops'][$opKey] = intval($urow['ops'][$opKey]) + $inc;
    _save_usage($db);
    $remain = max(0, $limit - intval($urow['count']));
    return [true, $remain, $limit, ""];
}
function _check_profile_allowed_for_user($usr, $profile) {
    // If no package constraints => allow
    $pkgs = _get_user_packages($usr);
    if (!$pkgs || count($pkgs) === 0) return [true, ""];

    // UNION of constraints across assigned packages
    $allowedBrands = [];
    $allowedGroups = [];
    foreach ($pkgs as $p) {
        $ab = $p['allowed_brands'] ?? [];
        $ag = $p['allowed_brand_groups'] ?? [];
        if (is_array($ab)) { foreach ($ab as $x) { $x=trim((string)$x); if($x!=='' && !in_array($x,$allowedBrands)) $allowedBrands[]=$x; } }
        if (is_array($ag)) { foreach ($ag as $x) { $x=trim((string)$x); if($x!=='' && !in_array($x,$allowedGroups)) $allowedGroups[]=$x; } }
    }
    $b = trim((string)($profile['brand'] ?? ''));
    $g = trim((string)($profile['brand_group'] ?? ''));
    if (is_array($allowedBrands) && count($allowedBrands) > 0) {
        if (!in_array($b, $allowedBrands)) return [false, "brand_not_allowed"];
    }
    if (is_array($allowedGroups) && count($allowedGroups) > 0) {
        if (!in_array($g, $allowedGroups)) return [false, "group_not_allowed"];
    }
    return [true, ""];
}

_ensure_default_packages();


function _require_admin() {
    if (($_SESSION['role'] ?? '') !== 'admin') { echo json_encode(["status"=>"error","msg"=>"unauthorized"]); exit; }
}
$action = $_POST['action'] ?? ''; $engine = new EcuEngine();

function _upload_err_msg($code) {
    $map = [
        UPLOAD_ERR_OK => 'OK',
        UPLOAD_ERR_INI_SIZE => 'Dosya sunucu limitini aştı (upload_max_filesize).',
        UPLOAD_ERR_FORM_SIZE => 'Dosya form limitini aştı (MAX_FILE_SIZE).',
        UPLOAD_ERR_PARTIAL => 'Dosya kısmi yüklendi.',
        UPLOAD_ERR_NO_FILE => 'Dosya seçilmedi.',
        UPLOAD_ERR_NO_TMP_DIR => 'Sunucuda geçici klasör (tmp) yok.',
        UPLOAD_ERR_CANT_WRITE => 'Disk’e yazılamadı (permission).',
        UPLOAD_ERR_EXTENSION => 'PHP extension upload’u durdurdu.'
    ];
    return $map[$code] ?? ('Bilinmeyen upload hatası: ' . $code);
}
function _require_upload($key) {
    if (!isset($_FILES[$key])) { return [false, "Dosya alanı yok: $key"]; }
    $e = $_FILES[$key]['error'] ?? 99;
    if ($e !== UPLOAD_ERR_OK) { return [false, _upload_err_msg($e)]; }
    $tmp = $_FILES[$key]['tmp_name'] ?? '';
    if ($tmp === '' || !is_uploaded_file($tmp)) { return [false, 'Upload tmp dosyası geçersiz.']; }
    return [true, $tmp];
}// --- AUTH ---
if ($action == 'login') {
    $u = (string)($_POST['u'] ?? '');
    $p = (string)($_POST['p'] ?? '');
    $db = _load_users_db();
    if ($db && isset($db['users']) && is_array($db['users'])) {
        foreach ($db['users'] as $usr) {
            if (($usr['active'] ?? true) && ($usr['username'] ?? '') === $u && ($usr['password'] ?? '') === $p) {
                list($okv,$vmsg) = _within_validity($usr);
                if(!$okv){ echo json_encode(['status'=>'error','msg'=>'Hesap süresi/paketi aktif değil']); exit; }
                $_SESSION['role'] = $usr['role'] ?? 'user';
                $_SESSION['user'] = $u;
                $_SESSION['package_id'] = $usr['package_id'] ?? '';
                $_SESSION['daily_limit'] = _effective_daily_limit($usr);
                _audit('login', ["username"=>$u]);
                _audit('user_upload'); echo json_encode(['status'=>'ok','role'=>$_SESSION['role']]);
                exit;
            }
        }
        echo json_encode(['status'=>'error','msg'=>'Giris basarisiz']);
        exit;
    }
    // fallback (legacy)
    if ($u === 'admin' && $p === 'pass') { $_SESSION['role'] = 'admin'; $_SESSION['user']='admin'; _audit('login',["username"=>$u]); _audit('user_upload'); echo json_encode(['status'=>'ok','role'=>'admin']); }
    else if ($u === 'user' && $p === 'pass') { $_SESSION['role'] = 'user'; $_SESSION['user']='user'; _audit('login',["username"=>$u]); _audit('user_upload'); echo json_encode(['status'=>'ok','role'=>'user']); }
    else { echo json_encode(['status'=>'error','msg'=>'Giris basarisiz']); }
    exit;
}
if ($action == 'check_auth') {
    if (!isset($_SESSION['role'])) echo json_encode(['status'=>'error']);
    else _audit('user_upload'); echo json_encode(['status'=>'ok', 'role'=>$_SESSION['role']]);
    exit;
}
if ($action == 'logout') { _audit('logout'); session_destroy(); _audit('user_upload'); echo json_encode(['status'=>'ok']); exit; }

// --- ADMIN ---
if ($action == 'admin_load_files') {
    list($ok1,$tmp1)=_require_upload('bin_file'); if(!$ok1){ echo json_encode(['status'=>'error','msg'=>$tmp1]); exit; }
    list($ok2,$tmp2)=_require_upload('json_file'); if(!$ok2){ echo json_encode(['status'=>'error','msg'=>$tmp2]); exit; }
    $binData = file_get_contents($tmp1); $jsonContent = file_get_contents($tmp2);
    $jsonMap = json_decode($jsonContent, true);
    if (!$jsonMap) { echo json_encode(['status'=>'error', 'msg'=>'JSON Hatası']); exit; }
    $token = md5(uniqid()); file_put_contents("$tempDir/{$token}.bin", $binData); file_put_contents("$tempDir/{$token}.json", $jsonContent);
    $maps = $engine->getMaps($jsonMap);
    $choices = []; $limit=0;
    foreach ($maps as $i => $m) {
        if($limit++ > 5000) break; list($r, $c, $org, $start) = $engine->getRowsColsOrgStart($m);
        $name = $engine->mapName($m, $i); $choices[] = ['id' => "[$i] $name", 'text' => "[$i] $name | {$r}x{$c} | 0x" . dechex($start), 'cols' => $c];
    }
    _audit('user_upload'); echo json_encode(['status'=>'ok', 'token'=>$token, 'tables'=>$choices, 'count'=>count($maps)]); exit;
}
if ($action == 'get_profiles') { _audit('user_upload'); echo json_encode(['status'=>'ok', 'profiles'=>$engine->getProfilesList()]); exit; }
if ($action == 'delete_profile') { _audit('profile_deleted',["id"=>$_POST['id']]); $engine->deleteProfile($_POST['id']); _audit('user_upload'); echo json_encode(['status'=>'ok', 'profiles'=>$engine->getProfilesList()]); exit; }
if ($action == 'admin_read_test') {
    $token = $_POST['token']; $methods = $_POST['methods'] ?? []; $tables = $_POST['tables'] ?? [];
    if(!file_exists("$tempDir/{$token}.bin")) { echo json_encode(['status'=>'error','msg'=>'Timeout']); exit; }
    $binData = file_get_contents("$tempDir/{$token}.bin"); $jsonMap = json_decode(file_get_contents("$tempDir/{$token}.json"), true);
    $maps = $engine->getMaps($jsonMap); $results = [];
    foreach ($methods as $method) { $res = $engine->executeMethod($method, $binData, $maps, $tables); $results = array_merge($results, $res); }
    _audit('user_upload'); echo json_encode(['status'=>'ok','data'=>$results]); exit;
}

if ($action == 'admin_simulator_run') {
    if(($_SESSION['role'] ?? '') !== 'admin') { echo json_encode(['status'=>'error','msg'=>'Unauthorized']); exit; }

    $profileId = trim((string)($_POST['profile_id'] ?? ''));
    if (!isset($_FILES['bin']) || (($_FILES['bin']['error'] ?? 99) !== UPLOAD_ERR_OK) || !is_uploaded_file($_FILES['bin']['tmp_name'])) {
        echo json_encode(['status'=>'error','msg'=>'BIN yüklenmedi']); exit;
    }

    $binPath = $_FILES['bin']['tmp_name'];
    $userBinData = file_get_contents($binPath);

    // Load profiles
    $profiles = [];
    if (file_exists('profiles_db.json')) $profiles = json_decode(file_get_contents('profiles_db.json'), true);
    if (!is_array($profiles)) $profiles = [];

    $best = null;
    $resultsByProfile = [];

    foreach ($profiles as $idx => $p) {
        $pid = (string)$idx;
        if ($profileId !== '' && $profileId !== $pid) continue;

        $jsonMap = json_decode($p['json_text'] ?? '', true);
        if (!is_array($jsonMap)) continue;

        $maps = $engine->getMaps($jsonMap);

        // ---- SIMILARITY (Master BIN vs User BIN) ----
        $sim = null;
        $masterFs = '';
        if (!empty($p['master_bin_path'])) {
            $masterFs = _fs_profile_path((string)$p['master_bin_path']);
            if (file_exists($masterFs)) {
                $sim = $engine->bytewiseSimilarityFile($masterFs, $binPath);
            }
        }

        // ---- ADDRESS VERIFY SCORE using saved map_signatures ----
        $addrScore = $engine->signatureMatchScore($userBinData, $maps, $p['map_signatures'] ?? []);

        // STRICT GATE: SIM>=60 and ADDR>=70
        $simVal = ($sim === null) ? 0.0 : floatval($sim);
        if ($simVal < 60.0) continue;
        if ($addrScore < 70.0) continue;

        // ---- DTC PREVIEW ----
        $methods = $p['read_methods'] ?? [];
        $tables  = $p['read_tables'] ?? [];
        if (is_string($methods)) { $tmp = json_decode($methods, true); if (is_array($tmp)) $methods = $tmp; }
        if (is_string($tables)) { $tmp = json_decode($tables, true); if (is_array($tmp)) $tables = $tmp; }

        $dtc = [];
        if (is_array($methods)) {
            foreach ($methods as $method) {
                $res = $engine->executeMethod($method, $userBinData, $maps, $tables);
                if (is_array($res)) $dtc = array_merge($dtc, $res);
            }
        }

        $row = [
            'id' => $pid,
            'name' => $p['name'] ?? ('Profile ' . $pid),
            'brand' => $p['brand'] ?? '',
            'ecu_model' => $p['ecu_model'] ?? '',
            'similarity' => $simVal,
            'addr_score' => $addrScore,
            'dtc_count' => is_array($dtc) ? count($dtc) : 0,
            'dtc' => $dtc
        ];
        $resultsByProfile[] = $row;

        if ($best === null) $best = $row;
        else {
            // choose best by SIM desc, then ADDR desc
            $s1 = $row['similarity'] ?? -1;
            $s2 = $best['similarity'] ?? -1;
            if ($s1 > $s2) $best = $row;
            else if ($s1 == $s2 && $row['addr_score'] > $best['addr_score']) $best = $row;
        }

        if ($profileId !== '') break;
    }

    if ($best === null) { echo json_encode(['status'=>'error','msg'=>'Profil bulunamadı']); exit; }

    echo json_encode([
        'status' => 'ok',
        'best' => $best,
        'profiles' => $resultsByProfile
    ]);
    exit;
}

if ($action == 'admin_patch_preview') {
    $token = $_POST['token']; $patchCfg = json_decode($_POST['patch_config'], true); 
    $testRows = []; if(isset($_POST['test_rows'])) { $parts = explode(',', $_POST['test_rows']); foreach($parts as $p) { if(is_numeric(trim($p))) $testRows[] = intval(trim($p)); } }
    if(!file_exists("$tempDir/{$token}.bin")) { echo json_encode(['status'=>'error','msg'=>'Timeout']); exit; }
    $binData = file_get_contents("$tempDir/{$token}.bin"); $jsonMap = json_decode(file_get_contents("$tempDir/{$token}.json"), true);
    $maps = $engine->getMaps($jsonMap); $res = $engine->applyPatch($binData, $maps, $patchCfg, $testRows);
    _audit('user_upload'); echo json_encode(['status'=>'ok','log'=>$res['log']]); exit;
}
if ($action == 'save_profile') {
    $token = $_POST['token']; if(!file_exists("$tempDir/{$token}.bin")) { echo json_encode(['status'=>'error','msg'=>'Timeout']); exit; }
    $binData = file_get_contents("$tempDir/{$token}.bin"); $jsonContent = file_get_contents("$tempDir/{$token}.json"); $sig = $engine->fileSignature($binData);
    // Optional meta fields
    $brand = trim((string)($_POST['brand'] ?? ''));
    $ecuModel = trim((string)($_POST['ecu_model'] ?? ''));
    $group = trim((string)($_POST['brand_group'] ?? ''));

    // Optional logo upload OR pick from logo library
    $logoUrl = '';
    if (isset($_FILES['brand_logo']) && (($_FILES['brand_logo']['error'] ?? 99) === UPLOAD_ERR_OK) && isset($_FILES['brand_logo']['tmp_name']) && is_uploaded_file($_FILES['brand_logo']['tmp_name'])) {
        $logosDir = __DIR__ . '/assets/logos';
        if (!file_exists($logosDir)) @mkdir($logosDir, 0777, true);
        $ext = pathinfo($_FILES['brand_logo']['name'] ?? '', PATHINFO_EXTENSION);
        $ext = preg_replace('/[^a-zA-Z0-9]/', '', $ext);
        if ($ext === '') $ext = 'png';
        $fname = 'logo_' . time() . '_' . substr(md5(uniqid()), 0, 10) . '.' . strtolower($ext);
        $dst = $logosDir . '/' . $fname;
        if (@move_uploaded_file($_FILES['brand_logo']['tmp_name'], $dst)) {
            $logoUrl = 'assets/logos/' . $fname;
        }
    }

    // Pick from logo library (assets/brand_logos/*)
    if ($logoUrl === '') {
        $pick = trim((string)($_POST['brand_logo_pick'] ?? ''));
        if ($pick !== '' && strpos($pick, 'assets/brand_logos/') === 0) {
            $fs = __DIR__ . '/' . $pick;
            if (file_exists($fs) && is_file($fs)) $logoUrl = $pick;
        }
    }

    
    // Store Master BIN for strong similarity checks (admin side)
    $masterBinPath = '';
    $masterDir = __DIR__ . '/data/master_bins';
    if (!file_exists($masterDir)) { @mkdir($masterDir, 0777, true); }
    $masterName = 'master_' . time() . '_' . substr(md5(uniqid('', true)), 0, 10) . '.bin';
    $masterFs = $masterDir . '/' . $masterName;
    if (@file_put_contents($masterFs, $binData) !== false) {
        $masterBinPath = 'data/master_bins/' . $masterName;
    }
$profile = [
        "name" => $_POST['profile_name'],
        "size" => $sig['size'],
        "sha1_head16" => $sig['sha1_head16'],
        "sha1_tail16" => $sig['sha1_tail16'],
        "json_text" => $jsonContent,
        "read_methods" => $_POST['read_methods'],
        "read_tables" => $_POST['read_tables'],
        "patch_tables" => json_decode($_POST['patch_config'], true),
        "brand" => $brand,
        "ecu_model" => $ecuModel,
        "brand_group" => $group,
        "package_id" => trim((string)($_POST['package_id'] ?? '')),
        "brand_logo" => $logoUrl,
        "master_bin_path" => $masterBinPath
    ];
    $engine->saveProfile($profile, $binData); _audit('profile_saved',["name"=>$_POST['profile_name']]); _audit('user_upload'); echo json_encode(['status'=>'ok','msg'=>'Kaydedildi']); exit;
}

// --- USER ---
if ($action == 'user_upload') {
    $usr = _get_user_record($_SESSION['user'] ?? '');
    list($okv,$vmsg) = _within_validity($usr);
    if(!$okv){ echo json_encode(['status'=>'error','msg'=>'Hesap süresi/paketi aktif değil']); exit; }

    list($okU,$tmpU)=_require_upload('user_bin');
    if(!$okU){ echo json_encode(['status'=>'error','msg'=>$tmpU]); exit; }

    $binPath = $tmpU;
    $binData = @file_get_contents($binPath);
    if($binData === false || $binData === ''){ echo json_encode(['status'=>'error','msg'=>'Binary okunamadı']); exit; }

    // --- STRICT GATE RULES ---
    $MIN_SIM  = 60.0;  // SIM >= 60
    $MIN_ADDR = 70.0;  // ADDR >= 70

    // Load profiles
    $profiles = [];
    if (file_exists('profiles_db.json')) $profiles = json_decode(file_get_contents('profiles_db.json'), true);
    if (!is_array($profiles)) $profiles = [];

    $best = null;
    $bestProfileIdx = null;

    foreach ($profiles as $idx => $p) {

        // package/permission gate
        list($okp,$pmsg) = _check_profile_allowed_for_user($usr, $p);
        if(!$okp) continue;

        // Require master bin for REAL similarity (strict)
        $sim = 0.0;
        if (!empty($p['master_bin_path'])) {
            $masterFs = _fs_profile_path((string)$p['master_bin_path']);
            if (file_exists($masterFs)) {
                $tmpSim = $engine->bytewiseSimilarityFile($masterFs, $binPath);
                if ($tmpSim !== null) $sim = floatval($tmpSim);
            }
        }

        if ($sim < $MIN_SIM) continue;

        // Address verify (no pattern search, fixed addr compare using saved signatures)
        $jsonMap = json_decode($p['json_text'] ?? '', true);
        if (!is_array($jsonMap)) continue;

        $maps = $engine->getMaps($jsonMap);
        $addrScore = $engine->signatureMatchScore($binData, $maps, $p['map_signatures'] ?? []);

        if ($addrScore < $MIN_ADDR) continue;

        $row = [
            'idx' => $idx,
            'profile' => $p,
            'similarity' => $sim,
            'addr_score' => $addrScore
        ];

        if ($best === null) {
            $best = $row; $bestProfileIdx = $idx;
        } else {
            // choose best by SIM desc, then ADDR desc
            if ($row['similarity'] > $best['similarity']) { $best = $row; $bestProfileIdx = $idx; }
            else if ($row['similarity'] == $best['similarity'] && $row['addr_score'] > $best['addr_score']) { $best = $row; $bestProfileIdx = $idx; }
        }
    }

    if ($best === null) {
        echo json_encode([
            'status'=>'error',
            'msg'=>'Tablo bulunamadı (SIM<60 veya ADDR<70)',
            'sim_min'=>$MIN_SIM,
            'addr_min'=>$MIN_ADDR
        ]);
        exit;
    }

    $profile = $best['profile'];

    // Consume daily quota ON UPLOAD only if a valid profile is found
    list($okq,$remain,$limit,$qmsg) = _consume_quota($_SESSION['user'] ?? '', 'upload', 1);
    if(!$okq){ echo json_encode(['status'=>'error','msg'=>'Günlük limit doldu','remain'=>$remain,'limit'=>$limit]); exit; }

    $token = md5(uniqid());
    @file_put_contents("$tempDir/{$token}.bin", $binData);
    @file_put_contents("$tempDir/{$token}_profile.json", json_encode($profile, JSON_UNESCAPED_UNICODE));

    $jsonMap = json_decode($profile['json_text'], true);
    $maps = $engine->getMaps($jsonMap);

    // addresses are assumed identical (ADDR gate passed) -> keep detected_addr = origStart
    foreach ($maps as &$m) {
        $origStart = $engine->parseAddr($m['Fieldvalues.StartAddr'] ?? $m['StartAddr'] ?? $m['start'] ?? '$0');
        $m['detected_addr'] = $origStart;
    }

    // Execute read methods (DTC)
    $results = [];
    $methods = $profile['read_methods'] ?? [];
    $tables  = $profile['read_tables'] ?? [];
    if (is_string($methods)) { $tmp = json_decode($methods, true); if (is_array($tmp)) $methods = $tmp; }
    if (is_string($tables))  { $tmp = json_decode($tables, true); if (is_array($tmp)) $tables  = $tmp; }

    if (is_array($methods)) {
        foreach ($methods as $method) {
            $res = $engine->executeMethod($method, $binData, $maps, $tables);
            if (is_array($res)) $results = array_merge($results, $res);
        }
    }

    @file_put_contents("$tempDir/{$token}_maps.json", json_encode($maps, JSON_UNESCAPED_UNICODE));

    echo json_encode([
        'status'=>'ok',
        'token'=>$token,
        'data'=>$results,
        'profile_name'=>$profile['name'],
        'align_msg' => "SIM:" . number_format($best['similarity'],2) . "% | ADDR:" . number_format($best['addr_score'],2) . "%",

        'similarity' => $best['similarity'],
        'addr_score' => $best['addr_score'],

        'brand' => $profile['brand'] ?? '',
        'ecu_model' => $profile['ecu_model'] ?? '',
        'brand_group' => $profile['brand_group'] ?? '',
        'brand_logo' => $profile['brand_logo'] ?? '',
        'quota_remain' => $remain,
        'quota_limit' => $limit
    ]);
    exit;
}

if ($action == 'user_status') {
    $usr = _get_user_record($_SESSION['user'] ?? '');
    if(!$usr){ echo json_encode(['status'=>'error','msg'=>'no_user']); exit; }
    list($okv,$vmsg) = _within_validity($usr);
    // quota today
    $day = _today_ymd_utc();
    $db = _load_usage();
    $count = 0;
    if (isset($db['days'][$day]['users'][$usr['username']]['count'])) $count = intval($db['days'][$day]['users'][$usr['username']]['count']);
    $limit = _effective_daily_limit($usr);
    $remain = ($limit>0) ? max(0, $limit - $count) : 0;

    // days left
    $validTo = trim((string)($usr['valid_to'] ?? ''));
    $daysLeft = null;
    if ($validTo !== '') {
        $t = strtotime($validTo . ' 23:59:59 UTC');
        if ($t !== false) {
            $daysLeft = (int)ceil( max(0, ($t - time()) / 86400.0) );
        }
    }
    echo json_encode([
        'status'=>'ok',
        'username'=>$usr['username'],
        'role'=>$usr['role'] ?? 'user',
        'valid_from'=>$usr['valid_from'] ?? '',
        'valid_to'=>$validTo,
        'days_left'=>$daysLeft,
        'valid_ok'=>$okv ? 1 : 0,
        'quota_used'=>$count,
        'quota_limit'=>$limit,
        'quota_remain'=> ($limit>0 ? $remain : null)
    ]);
    exit;
}

if ($action == 'user_patch') { _audit('user_patch');
    $usr = _get_user_record($_SESSION['user'] ?? '');
    list($okv,$vmsg) = _within_validity($usr);
    if(!$okv){ echo json_encode(['status'=>'error','msg'=>'Hesap süresi/paketi aktif değil']); exit; }

    $token = $_POST['token'] ?? '';
    if ($token === '') { echo json_encode(['status'=>'error','msg'=>'Token yok']); exit; }

    if (!isset($_POST['rows'])) { echo json_encode(['status'=>'error','msg'=>'Seçim yapılmadı!']); exit; }
    $selectedRows = is_array($_POST['rows']) ? $_POST['rows'] : explode(',', $_POST['rows']);

    if(!file_exists("$tempDir/{$token}.bin")) { echo json_encode(['status'=>'error','msg'=>'Timeout']); exit; }
    $binData = file_get_contents("$tempDir/{$token}.bin");

    // Load maps saved at upload time
    if (file_exists("$tempDir/{$token}_maps.json")) {
        $maps = json_decode(file_get_contents("$tempDir/{$token}_maps.json"), true);
    } else {
        $maps = null;
    }

    // Load the exact matched profile saved at upload time (STRICT)
    if (file_exists("$tempDir/{$token}_profile.json")) {
        $profile = json_decode(file_get_contents("$tempDir/{$token}_profile.json"), true);
    } else {
        $profile = null;
    }

    if (!is_array($profile)) { echo json_encode(['status'=>'error','msg'=>'Profil bulunamadı (upload tekrar)']); exit; }
    if (!is_array($maps)) {
        $jsonMap = json_decode($profile['json_text'] ?? '', true);
        $maps = $engine->getMaps($jsonMap);
        foreach ($maps as &$m) {
            $origStart = $engine->parseAddr($m['Fieldvalues.StartAddr'] ?? $m['StartAddr'] ?? $m['start'] ?? '$0');
            $m['detected_addr'] = $origStart;
        }
    }

    $patchTables = $profile['patch_tables'] ?? [];
    if (is_string($patchTables)) { $tmp = json_decode($patchTables, true); if (is_array($tmp)) $patchTables = $tmp; }

    $res = $engine->applyPatch($binData, $maps, $patchTables, $selectedRows);

    $fname = "mod_" . time() . ".bin";
    file_put_contents("$tempDir/$fname", $res['data']);

    echo json_encode(['status'=>'ok','url'=>"temp/$fname",'count'=>$res['count']]);
    exit;
}
// ===== ADMIN UX APIs =====
if ($action == 'admin_list_users') {
    _require_admin();
    $db = _load_users_db(); if(!$db) $db=["users"=>[]];
    echo json_encode(["status"=>"ok","users"=>$db["users"]]); exit;
}
if ($action == 'admin_add_user') {
    _require_admin();
    $u = trim((string)($_POST['username'] ?? ''));
    $p = (string)($_POST['password'] ?? '');
    $role = (string)($_POST['role'] ?? 'user');
    if ($u === '' || $p === '') { echo json_encode(["status"=>"error","msg"=>"Eksik alan"]); exit; }
    $db = _load_users_db(); if(!$db) $db=["users"=>[]];
    foreach ($db["users"] as $usr) { if (($usr["username"]??'') === $u) { echo json_encode(["status"=>"error","msg"=>"Kullanıcı var"]); exit; } }
    $db["users"][] = ["username"=>$u,"password"=>$p,"role"=>$role,"active"=>true,"created_at"=>_now_iso()];
    _save_users_db($db);
    _audit('admin_add_user', ["username"=>$u,"role"=>$role]);
    echo json_encode(["status"=>"ok","users"=>$db["users"]]); exit;
}
if ($action == 'admin_delete_user') {
    _require_admin();
    $u = (string)($_POST['username'] ?? '');
    $db = _load_users_db(); if(!$db) $db=["users"=>[]];
    $out=[];
    foreach ($db["users"] as $usr) {
        if (($usr["username"]??'') !== $u) $out[]=$usr;
    }
    $db["users"] = $out;
    _save_users_db($db);
    _audit('admin_delete_user', ["username"=>$u]);
    echo json_encode(["status"=>"ok","users"=>$db["users"]]); exit;
}
if ($action == 'admin_toggle_user') {
    _require_admin();
    $u = (string)($_POST['username'] ?? '');
    $active = (int)($_POST['active'] ?? 1) ? true : false;
    $db = _load_users_db(); if(!$db) $db=["users"=>[]];
    foreach ($db["users"] as &$usr) {
        if (($usr["username"]??'') === $u) { $usr["active"]=$active; }
    }
    _save_users_db($db);
    _audit('admin_toggle_user', ["username"=>$u,"active"=>$active]);
    echo json_encode(["status"=>"ok","users"=>$db["users"]]); exit;
}


// Packages
if ($action == 'admin_list_packages') {
    _require_admin();
    $pdb = _load_packages_db(); if(!$pdb) $pdb=["packages"=>[]];
    echo json_encode(["status"=>"ok","packages"=>$pdb["packages"]]); exit;
}

// Logo library (assets/brand_logos/*) + uploaded logos (assets/logos/*)
if ($action == 'admin_list_logo_library') {
    _require_admin();
    $base = __DIR__;
    $dirs = [
        'assets/brand_logos',
        'assets/logos'
    ];
    $out = [];
    foreach ($dirs as $d) {
        $fsd = $base . '/' . $d;
        if (!file_exists($fsd) || !is_dir($fsd)) continue;
        $files = @scandir($fsd);
        if (!$files) continue;
        foreach ($files as $f) {
            if ($f === '.' || $f === '..') continue;
            $ext = strtolower(pathinfo($f, PATHINFO_EXTENSION));
            if (!in_array($ext, ['png','jpg','jpeg','webp','svg','gif'])) continue;
            $rel = $d . '/' . $f;
            $out[] = $rel;
        }
    }
    sort($out);
    echo json_encode(["status"=>"ok","logos"=>$out]);
    exit;
}
if ($action == 'admin_add_package') {
    _require_admin();
    $id = trim((string)($_POST['id'] ?? ''));
    $name = trim((string)($_POST['name'] ?? ''));
    $daily = (int)($_POST['daily_limit'] ?? 20);
    $brands = json_decode((string)($_POST['allowed_brands'] ?? '[]'), true);
    $groups = json_decode((string)($_POST['allowed_brand_groups'] ?? '[]'), true);
    if(!is_array($brands)) $brands = [];
    if(!is_array($groups)) $groups = [];
    if($id==='' || $name===''){ echo json_encode(["status"=>"error","msg"=>"Eksik alan"]); exit; }
    $pdb = _load_packages_db(); if(!$pdb) $pdb=["packages"=>[]];
    foreach($pdb["packages"] as $p){ if(($p["id"]??'')===$id){ echo json_encode(["status"=>"error","msg"=>"Paket var"]); exit; } }
    $pdb["packages"][] = ["id"=>$id,"name"=>$name,"active"=>true,"daily_limit"=>$daily,"allowed_brands"=>$brands,"allowed_brand_groups"=>$groups,"notes"=>trim((string)($_POST['notes'] ?? ''))];
    _save_packages_db($pdb);
    _audit('admin_add_package', ["id"=>$id,"name"=>$name,"daily_limit"=>$daily]);
    echo json_encode(["status"=>"ok","packages"=>$pdb["packages"]]); exit;
}
if ($action == 'admin_update_package') {
    _require_admin();
    $id = trim((string)($_POST['id'] ?? ''));
    $pdb = _load_packages_db(); if(!$pdb) $pdb=["packages"=>[]];
    foreach($pdb["packages"] as &$p){
        if(($p["id"]??'')===$id){
            if(isset($_POST['name'])) $p["name"]=trim((string)$_POST['name']);
            if(isset($_POST['active'])) $p["active"]= (int)$_POST['active'] ? true:false;
            if(isset($_POST['daily_limit'])) $p["daily_limit"]= (int)$_POST['daily_limit'];
            if(isset($_POST['allowed_brands'])){ $b=json_decode((string)$_POST['allowed_brands'],true); if(is_array($b)) $p["allowed_brands"]=$b; }
            if(isset($_POST['allowed_brand_groups'])){ $g=json_decode((string)$_POST['allowed_brand_groups'],true); if(is_array($g)) $p["allowed_brand_groups"]=$g; }
            if(isset($_POST['notes'])) $p["notes"]=trim((string)$_POST['notes']);
        }
    }
    _save_packages_db($pdb);
    _audit('admin_update_package', ["id"=>$id]);
    echo json_encode(["status"=>"ok","packages"=>$pdb["packages"]]); exit;
}
if ($action == 'admin_delete_package') {
    _require_admin();
    $id = trim((string)($_POST['id'] ?? ''));
    $pdb = _load_packages_db(); if(!$pdb) $pdb=["packages"=>[]];
    $out=[]; foreach($pdb["packages"] as $p){ if(($p["id"]??'')!==$id) $out[]=$p; }
    $pdb["packages"]=$out;
    _save_packages_db($pdb);
    _audit('admin_delete_package', ["id"=>$id]);
    echo json_encode(["status"=>"ok","packages"=>$pdb["packages"]]); exit;
}

// User policy update (validity + package + daily limit)
if ($action == 'admin_update_user_policy') {
    _require_admin();
    $u = trim((string)($_POST['username'] ?? ''));
    $vf = trim((string)($_POST['valid_from'] ?? '')); // Y-m-d
    $vt = trim((string)($_POST['valid_to'] ?? ''));   // Y-m-d
    // Multi package support: package_ids can be JSON array or comma-separated string.
    $pkgIdsRaw = $_POST['package_ids'] ?? null;
    $pkgIds = [];
    if (is_array($pkgIdsRaw)) {
        foreach ($pkgIdsRaw as $x) { $x = trim((string)$x); if($x!=='' && !in_array($x,$pkgIds)) $pkgIds[]=$x; }
    } else {
        $s = trim((string)$pkgIdsRaw);
        if ($s !== '') {
            $j = json_decode($s, true);
            if (is_array($j)) {
                foreach ($j as $x) { $x = trim((string)$x); if($x!=='' && !in_array($x,$pkgIds)) $pkgIds[]=$x; }
            } else {
                $parts = explode(',', $s);
                foreach ($parts as $x) { $x = trim((string)$x); if($x!=='' && !in_array($x,$pkgIds)) $pkgIds[]=$x; }
            }
        }
    }
    $dl = trim((string)($_POST['daily_limit'] ?? '')); // empty => inherit
    $db = _load_users_db(); if(!$db) $db=["users"=>[]];
    foreach ($db["users"] as &$usr) {
        if (($usr["username"]??'') === $u) {
            $usr["valid_from"] = $vf;
            $usr["valid_to"] = $vt;
            $usr["package_ids"] = $pkgIds;
            // keep legacy field for older UI (optional)
            if (count($pkgIds) > 0) $usr["package_id"] = $pkgIds[0]; else $usr["package_id"] = '';
            if ($dl === '') { unset($usr["daily_limit"]); } else { $usr["daily_limit"] = (int)$dl; }
        }
    }
    _save_users_db($db);
    _audit('admin_update_user_policy', ["username"=>$u,"valid_from"=>$vf,"valid_to"=>$vt,"package_ids"=>$pkgIds,"daily_limit"=>$dl]);
    echo json_encode(["status"=>"ok","users"=>$db["users"]]); exit;
}
if ($action == 'admin_user_usage_today') {
    _require_admin();
    $u = trim((string)($_POST['username'] ?? ''));
    $day = _today_ymd_utc();
    $udb = _load_usage();
    $count = 0; $ops = [];
    if (isset($udb['days'][$day]['users'][$u])) {
        $count = intval($udb['days'][$day]['users'][$u]['count'] ?? 0);
        $ops = $udb['days'][$day]['users'][$u]['ops'] ?? [];
    }
    $usr = _get_user_record($u);
    $limit = $usr ? _effective_daily_limit($usr) : 0;
    $remain = ($limit>0) ? max(0, $limit-$count) : 0;
    echo json_encode(["status"=>"ok","day"=>$day,"count"=>$count,"limit"=>$limit,"remain"=>$remain,"ops"=>$ops]); exit;
}


if ($action == 'admin_audit_recent') {
    _require_admin();
    $limit = (int)($_POST['limit'] ?? 50);
    $rows = _read_audit(2000);
    $rows = array_reverse($rows);
    $rows = array_slice($rows, 0, max(1,min(200,$limit)));
    echo json_encode(["status"=>"ok","rows"=>$rows]); exit;
}
if ($action == 'admin_audit_user_last') {
    _require_admin();
    $u = (string)($_POST['username'] ?? '');
    $limit = (int)($_POST['limit'] ?? 10);
    $rows = _read_audit(5000);
    $out = [];
    for ($i=count($rows)-1; $i>=0 && count($out)<$limit; $i--) {
        if (($rows[$i]["user"] ?? '') === $u) $out[] = $rows[$i];
    }
    echo json_encode(["status"=>"ok","rows"=>$out]); exit;
}
if ($action == 'admin_audit_logins') {
    _require_admin();
    $limit = (int)($_POST['limit'] ?? 50);
    $rows = _read_audit(5000);
    $out = [];
    for ($i=count($rows)-1; $i>=0 && count($out)<$limit; $i--) {
        $a = $rows[$i]["action"] ?? '';
        if ($a === 'login' || $a === 'logout') $out[] = $rows[$i];
    }
    echo json_encode(["status"=>"ok","rows"=>$out]); exit;
}

// Library / Profiles detailed
if ($action == 'admin_profiles_detailed') {
    _require_admin();
    $p = [];
    if (file_exists('profiles_db.json')) $p = json_decode(file_get_contents('profiles_db.json'), true);
    if(!is_array($p)) $p=[];
    $out=[];
    foreach ($p as $i => $it) {
        $out[] = [
            "id"=>$i,
            "name"=>$it["name"] ?? "",
            "brand"=>$it["brand"] ?? "",
            "ecu_model"=>$it["ecu_model"] ?? "",
            "brand_group"=>$it["brand_group"] ?? "",
            "brand_logo"=>$it["brand_logo"] ?? "",
            "package_id"=>$it["package_id"] ?? "",
            "size"=>$it["size"] ?? 0,
            "read_methods_count"=>is_array($it["read_methods"]??null)?count($it["read_methods"]):0,
            "read_tables_count"=>is_array($it["read_tables"]??null)?count($it["read_tables"]):0,
            "patch_tables_count"=>is_array($it["patch_tables"]??null)?count($it["patch_tables"]):0
        ];
    }
    echo json_encode(["status"=>"ok","profiles"=>$out]); exit;
}
if ($action == 'admin_profile_update_meta') {
    _require_admin();
    $id = (int)($_POST['id'] ?? -1);
    $brand = trim((string)($_POST['brand'] ?? ''));
    $ecu = trim((string)($_POST['ecu_model'] ?? ''));
    $grp = trim((string)($_POST['brand_group'] ?? ''));
    $pkg = trim((string)($_POST['package_id'] ?? ''));
    $p = [];
    if (file_exists('profiles_db.json')) $p = json_decode(file_get_contents('profiles_db.json'), true);
    if(!is_array($p)) $p=[];
    if (!isset($p[$id])) { echo json_encode(["status"=>"error","msg"=>"notfound"]); exit; }
    $p[$id]["brand"] = $brand;
    $p[$id]["ecu_model"] = $ecu;
    $p[$id]["brand_group"] = $grp;
    $p[$id]["package_id"] = $pkg;
    file_put_contents('profiles_db.json', json_encode($p, JSON_PRETTY_PRINT|JSON_UNESCAPED_UNICODE));
    _audit('library_update_meta', ["id"=>$id,"brand"=>$brand,"ecu_model"=>$ecu]);
    echo json_encode(["status"=>"ok"]); exit;
}
if ($action == 'admin_library_stats') {
    _require_admin();
    $p = [];
    if (file_exists('profiles_db.json')) $p = json_decode(file_get_contents('profiles_db.json'), true);
    if(!is_array($p)) $p=[];
    $byBrand = [];
    foreach ($p as $it) {
        $b = trim((string)($it["brand"] ?? "Unknown"));
        if ($b === '') $b = "Unknown";
        if (!isset($byBrand[$b])) $byBrand[$b] = ["brand"=>$b,"profiles"=>0,"code_count"=>0];
        $byBrand[$b]["profiles"] += 1;
        $byBrand[$b]["code_count"] += (is_array($it["patch_tables"]??null)?count($it["patch_tables"]):0);
    }
    $rows = array_values($byBrand);
    usort($rows, function($a,$b){ return $b["profiles"] <=> $a["profiles"]; });
    echo json_encode(["status"=>"ok","brands"=>$rows,"total_profiles"=>count($p)]); exit;
}


?>