<?php
/**
 * Landing admin — sets the destination of the claim button.
 *
 * No database: the password hash and the current link live in ../data/config.php,
 * and saving rewrites ../link.js, which the landing loads as a plain script. That
 * keeps the landing itself static, so it still works if PHP is unavailable.
 *
 * First visit asks you to choose a password. Delete ../data/config.php to reset it.
 */
declare(strict_types=1);

const ROOT        = __DIR__ . '/..';
const CONFIG_FILE = ROOT . '/data/config.php';
const LINK_FILE   = ROOT . '/link.js';
const LOCKOUT_MAX = 8;      // failed logins before a cooldown kicks in
const LOCKOUT_SEC = 900;

session_set_cookie_params(['httponly' => true, 'samesite' => 'Lax']);
session_start();

/* ---------------------------------------------------------------- storage */

function load_config(): array {
    if (!is_file(CONFIG_FILE)) return [];
    $cfg = include CONFIG_FILE;
    return is_array($cfg) ? $cfg : [];
}

function save_config(array $cfg): bool {
    $dir = dirname(CONFIG_FILE);
    if (!is_dir($dir) && !@mkdir($dir, 0755, true)) return false;
    $php = "<?php\n// Written by admin/index.php. Do not edit while logged in.\nreturn "
         . var_export($cfg, true) . ";\n";
    return write_atomic(CONFIG_FILE, $php);
}

/** Write via a temp file + rename so a half-written file is never served. */
function write_atomic(string $path, string $data): bool {
    $tmp = $path . '.tmp' . bin2hex(random_bytes(4));
    if (@file_put_contents($tmp, $data, LOCK_EX) === false) return false;
    if (!@rename($tmp, $path)) { @unlink($tmp); return false; }
    @chmod($path, 0644);
    return true;
}

function save_link(string $url): bool {
    $js = "/* Destination for the claim button.\n"
        . " * Written by admin/index.php — edit there rather than by hand if you can. */\n"
        . 'window.LANDING_LINK = ' . json_encode($url, JSON_UNESCAPED_SLASHES) . ";\n";
    return write_atomic(LINK_FILE, $js);
}

/** Accept only absolute http(s) URLs — anything else could turn the button into
 *  a javascript: or data: payload for whoever lands on the page. */
function clean_url(string $raw): ?string {
    $raw = trim($raw);
    if ($raw === '') return null;
    if (!preg_match('~^https?://~i', $raw)) $raw = 'https://' . $raw;
    if (strlen($raw) > 2000) return null;
    if (!filter_var($raw, FILTER_VALIDATE_URL)) return null;
    $host = parse_url($raw, PHP_URL_HOST);
    if (!$host || strpos($host, '.') === false) return null;
    return $raw;
}

/* ------------------------------------------------------------------ state */

$cfg       = load_config();
$installed = isset($cfg['hash']);
$authed    = !empty($_SESSION['landing_admin']) && $installed;
$notice    = null;
$error     = null;

if (empty($_SESSION['csrf'])) $_SESSION['csrf'] = bin2hex(random_bytes(16));
$csrf = $_SESSION['csrf'];

function csrf_ok(): bool {
    return isset($_POST['csrf'], $_SESSION['csrf'])
        && hash_equals($_SESSION['csrf'], (string) $_POST['csrf']);
}

$action = $_SERVER['REQUEST_METHOD'] === 'POST' ? ($_POST['action'] ?? '') : '';

if ($action !== '' && !csrf_ok()) {
    $error  = 'Session expired. Reload the page and try again.';
    $action = '';
}

/* ---- first run: choose a password --------------------------------------- */
if ($action === 'install' && !$installed) {
    $pw  = (string) ($_POST['password'] ?? '');
    $pw2 = (string) ($_POST['password2'] ?? '');
    if (strlen($pw) < 8) {
        $error = 'Password must be at least 8 characters.';
    } elseif ($pw !== $pw2) {
        $error = 'The two passwords do not match.';
    } else {
        $cfg = [
            'hash'    => password_hash($pw, PASSWORD_DEFAULT),
            'link'    => '',
            'updated' => '',
            'fails'   => 0,
            'lock'    => 0,
        ];
        if (save_config($cfg)) {
            $installed = true;
            $_SESSION['landing_admin'] = true;
            $authed = true;
            $notice = 'Password set. You are logged in.';
        } else {
            $error = 'Could not write data/config.php — check folder permissions.';
        }
    }
}

/* ---- login --------------------------------------------------------------- */
if ($action === 'login' && $installed && !$authed) {
    $now = time();
    if (!empty($cfg['lock']) && $cfg['lock'] > $now) {
        $error = 'Too many failed attempts. Try again in '
               . (int) ceil(($cfg['lock'] - $now) / 60) . ' minutes.';
    } elseif (password_verify((string) ($_POST['password'] ?? ''), $cfg['hash'])) {
        session_regenerate_id(true);
        $_SESSION['landing_admin'] = true;
        $_SESSION['csrf'] = $csrf = bin2hex(random_bytes(16));
        $authed = true;
        $cfg['fails'] = 0;
        $cfg['lock']  = 0;
        save_config($cfg);
    } else {
        $cfg['fails'] = (int) ($cfg['fails'] ?? 0) + 1;
        if ($cfg['fails'] >= LOCKOUT_MAX) {
            $cfg['lock']  = $now + LOCKOUT_SEC;
            $cfg['fails'] = 0;
        }
        save_config($cfg);
        $error = 'Wrong password.';
    }
}

/* ---- save the link ------------------------------------------------------- */
if ($action === 'save' && $authed) {
    $url = clean_url((string) ($_POST['link'] ?? ''));
    if ($url === null) {
        $error = 'That does not look like a valid http(s) link.';
    } elseif (!save_link($url)) {
        $error = 'Could not write link.js — check that the landing folder is writable.';
    } else {
        $cfg['link']    = $url;
        $cfg['updated'] = date('Y-m-d H:i:s');
        save_config($cfg);
        $notice = 'Saved. The claim button now points at your link.';
    }
}

/* ---- change the password ------------------------------------------------- */
if ($action === 'passwd' && $authed) {
    $cur = (string) ($_POST['current'] ?? '');
    $pw  = (string) ($_POST['password'] ?? '');
    $pw2 = (string) ($_POST['password2'] ?? '');
    if (!password_verify($cur, $cfg['hash'])) {
        $error = 'Current password is wrong.';
    } elseif (strlen($pw) < 8) {
        $error = 'New password must be at least 8 characters.';
    } elseif ($pw !== $pw2) {
        $error = 'The two new passwords do not match.';
    } else {
        $cfg['hash'] = password_hash($pw, PASSWORD_DEFAULT);
        $notice = save_config($cfg) ? 'Password changed.' : null;
        if ($notice === null) $error = 'Could not write data/config.php.';
    }
}

if ($action === 'logout') {
    $_SESSION = [];
    session_destroy();
    header('Location: ' . strtok($_SERVER['REQUEST_URI'], '?'));
    exit;
}

/* What the landing is actually serving right now, read back from link.js so the
   panel shows reality rather than what we think we wrote. */
$live = '';
if (is_file(LINK_FILE) && preg_match('/LANDING_LINK\s*=\s*"([^"]*)"/', (string) file_get_contents(LINK_FILE), $m)) {
    $live = $m[1];
}
$linkOk = $live !== '' && $live !== '#';

function e(?string $s): string { return htmlspecialchars((string) $s, ENT_QUOTES, 'UTF-8'); }
?><!DOCTYPE html>
<html lang="en">
<head>
<meta charset="utf-8">
<meta name="viewport" content="width=device-width, initial-scale=1">
<meta name="robots" content="noindex, nofollow">
<title>Landing admin — Sunny Coin 2</title>
<style>
  :root { color-scheme: dark; }
  * { box-sizing: border-box; }
  body { margin: 0; padding: 40px 20px; background: #14100e; color: #f2ece7;
         font: 15px/1.6 ui-sans-serif, system-ui, "Segoe UI", Roboto, sans-serif; }
  .wrap { max-width: 620px; margin: 0 auto; }
  h1 { margin: 0 0 4px; font-size: 22px; letter-spacing: .01em; }
  .sub { margin: 0 0 28px; color: #9b8e85; font-size: 14px; }
  .card { background: #1e1815; border: 1px solid #33291f; border-radius: 10px;
          padding: 22px 22px 24px; margin: 0 0 18px; }
  .card h2 { margin: 0 0 16px; font-size: 15px; text-transform: uppercase;
             letter-spacing: .08em; color: #c9a227; }
  label { display: block; margin: 0 0 6px; font-size: 13px; color: #b6a99f; }
  input[type=url], input[type=text], input[type=password] {
    width: 100%; padding: 11px 13px; border-radius: 6px; border: 1px solid #3d3129;
    background: #100c0a; color: #fff; font: inherit; }
  input:focus { outline: 2px solid #f5321f; outline-offset: 1px; border-color: #f5321f; }
  button { margin-top: 16px; padding: 11px 22px; border: 0; border-radius: 6px;
           background: #f5321f; color: #fff; font: inherit; font-weight: 600; cursor: pointer; }
  button:hover { background: #ff4a35; }
  button.ghost { background: #2c231d; color: #d8cdc5; }
  button.ghost:hover { background: #3a2e26; }
  .msg { padding: 12px 14px; border-radius: 6px; margin: 0 0 18px; font-size: 14px; }
  .ok  { background: #14331f; border: 1px solid #2c6b40; color: #a6e9bf; }
  .bad { background: #3a1512; border: 1px solid #7d2b22; color: #ffb3a8; }
  .live { margin: 0; padding: 12px 14px; border-radius: 6px; background: #100c0a;
          border: 1px solid #33291f; font-size: 13px; word-break: break-all; }
  .live b { color: #c9a227; font-weight: 600; }
  .muted { color: #8b7f76; font-size: 13px; }
  .row { display: flex; gap: 12px; align-items: center; flex-wrap: wrap; }
  .grid { display: grid; gap: 14px; grid-template-columns: 1fr 1fr; }
  @media (max-width: 520px) { .grid { grid-template-columns: 1fr; } }
  a { color: #f5a623; }
  .foot { margin: 26px 0 0; font-size: 13px; color: #7d716a; }
</style>
</head>
<body>
<div class="wrap">
  <h1>Landing admin</h1>
  <p class="sub">Sunny Coin 2 · Khmer build — controls where the claim button sends people.</p>

  <?php if ($notice): ?><div class="msg ok"><?= e($notice) ?></div><?php endif; ?>
  <?php if ($error):  ?><div class="msg bad"><?= e($error) ?></div><?php endif; ?>

  <?php if (!$installed): ?>
    <div class="card">
      <h2>Set a password</h2>
      <p class="muted" style="margin-top:0">First run. Pick a password — it is stored hashed in
        <code>data/config.php</code>.</p>
      <form method="post" autocomplete="off">
        <input type="hidden" name="csrf" value="<?= e($csrf) ?>">
        <input type="hidden" name="action" value="install">
        <div class="grid">
          <div><label for="p1">Password</label><input id="p1" type="password" name="password" required minlength="8"></div>
          <div><label for="p2">Repeat</label><input id="p2" type="password" name="password2" required minlength="8"></div>
        </div>
        <button type="submit">Create</button>
      </form>
    </div>

  <?php elseif (!$authed): ?>
    <div class="card">
      <h2>Log in</h2>
      <form method="post" autocomplete="off">
        <input type="hidden" name="csrf" value="<?= e($csrf) ?>">
        <input type="hidden" name="action" value="login">
        <label for="pw">Password</label>
        <input id="pw" type="password" name="password" required autofocus>
        <button type="submit">Log in</button>
      </form>
    </div>

  <?php else: ?>
    <div class="card">
      <h2>Claim button link</h2>
      <p class="live">
        <b>Live right now:</b><br>
        <?= $linkOk ? e($live) : '<span style="color:#ffb3a8">not set — the button shows a warning instead of redirecting</span>' ?>
        <?php if (!empty($cfg['updated'])): ?><br><span class="muted">updated <?= e($cfg['updated']) ?></span><?php endif; ?>
      </p>
      <form method="post" style="margin-top:16px">
        <input type="hidden" name="csrf" value="<?= e($csrf) ?>">
        <input type="hidden" name="action" value="save">
        <label for="link">New destination (http/https)</label>
        <input id="link" type="text" name="link" value="<?= e($linkOk ? $live : '') ?>"
               placeholder="https://your-offer.example/signup" required>
        <p class="muted" style="margin:8px 0 0">The visitor's query string (<code>?st=…&amp;s1=…</code>)
          is appended automatically, so stream and subid tracking carries over.</p>
        <button type="submit">Save link</button>
      </form>
    </div>

    <div class="card">
      <h2>Change password</h2>
      <form method="post" autocomplete="off">
        <input type="hidden" name="csrf" value="<?= e($csrf) ?>">
        <input type="hidden" name="action" value="passwd">
        <label for="cur">Current password</label>
        <input id="cur" type="password" name="current" required>
        <div class="grid" style="margin-top:14px">
          <div><label for="n1">New password</label><input id="n1" type="password" name="password" required minlength="8"></div>
          <div><label for="n2">Repeat</label><input id="n2" type="password" name="password2" required minlength="8"></div>
        </div>
        <button type="submit" class="ghost">Update password</button>
      </form>
    </div>

    <div class="row">
      <a href="../index.html" target="_blank">Open the landing ↗</a>
      <form method="post" style="margin:0">
        <input type="hidden" name="csrf" value="<?= e($csrf) ?>">
        <input type="hidden" name="action" value="logout">
        <button type="submit" class="ghost" style="margin:0">Log out</button>
      </form>
    </div>
  <?php endif; ?>

  <p class="foot">Forgot the password? Delete <code>data/config.php</code> on the server and reload this page
    to set a new one.</p>
</div>
</body>
</html>
