<?php
/**
 * check-missing-wasabi-photos-v1.php
 *
 * READ ONLY.
 * - No MLS Grid requests.
 * - No INSERT / UPDATE / DELETE.
 * - Looks at active listings modified between 2 and 24 hours ago.
 * - Gets the first MediaKey from bonstmedia3.
 * - Checks whether <MediaKey>.MedRes.jpeg exists in the public Wasabi bucket.
 *
 * If this accurately identifies the listings showing the Coming Soon image,
 * a later version can feed ONLY those missing listings to the proven manual
 * MLS Grid repair routine.
 */

set_time_limit(300);

$db_host   = 'localhost';
$db_name   = 'bonstdata3db';
$db_user   = 'bonstdata3user';
$db_pass   = 'Lb3@e9#23rhCVnYQ';

$wasabi_base_url = 'https://s3.wasabisys.com/mlsgrid/';
$minimum_age_hours = 2;
$lookback_hours = 24;
$default_limit = 1000;

$allowed_counties = [
    'anoka','carver','chisago','dakota','hennepin','isanti','le sueur','mille lacs',
    'ramsey','scott','sherburne','sibley','washington','wright','pierce','st. croix','st croix'
];

function h($v) {
    return htmlspecialchars((string)$v, ENT_QUOTES, 'UTF-8');
}

/**
 * Lightweight existence check.
 * Uses a tiny ranged GET rather than downloading the JPEG.
 * 200 or 206 = image exists.
 */
function wasabi_image_exists($url) {
    $ch = curl_init($url);
    curl_setopt_array($ch, [
        CURLOPT_RETURNTRANSFER => true,
        CURLOPT_FOLLOWLOCATION => true,
        CURLOPT_MAXREDIRS => 3,
        CURLOPT_CONNECTTIMEOUT => 5,
        CURLOPT_TIMEOUT => 10,
        CURLOPT_RANGE => '0-0',
        CURLOPT_HTTPHEADER => ['Accept: image/*'],
        CURLOPT_USERAGENT => 'Brymark-Wasabi-Photo-Check/1.0',
    ]);

    $body = curl_exec($ch);
    $errno = curl_errno($ch);
    $error = curl_error($ch);
    $code = (int)curl_getinfo($ch, CURLINFO_HTTP_CODE);
    $type = (string)curl_getinfo($ch, CURLINFO_CONTENT_TYPE);
    curl_close($ch);

    if ($errno) {
        return [
            'exists' => false,
            'status' => 'ERROR',
            'http' => $code,
            'type' => $type,
            'error' => $error
        ];
    }

    if ($code === 200 || $code === 206) {
        return [
            'exists' => true,
            'status' => 'FOUND',
            'http' => $code,
            'type' => $type,
            'error' => ''
        ];
    }

    if ($code === 404) {
        return [
            'exists' => false,
            'status' => 'MISSING',
            'http' => 404,
            'type' => $type,
            'error' => ''
        ];
    }

    return [
        'exists' => false,
        'status' => 'CHECK ERROR',
        'http' => $code,
        'type' => $type,
        'error' => 'Unexpected HTTP response'
    ];
}

$run = (($_GET['run'] ?? '') === '1');
$show = $_GET['show'] ?? 'missing';
if (!in_array($show, ['missing','all'], true)) $show = 'missing';

$limit = (int)($_GET['limit'] ?? $default_limit);
if ($limit < 1) $limit = $default_limit;
if ($limit > 2000) $limit = 2000;

$rows = [];
$error = '';
$summary = [
    'candidates' => 0,
    'found' => 0,
    'missing' => 0,
    'no_media' => 0,
    'check_error' => 0,
    'mls_requests' => 0
];

$now = new DateTimeImmutable('now', new DateTimeZone('UTC'));
$oldest = $now->modify("-{$lookback_hours} hours")->format('Y-m-d\TH:i:s.v\Z');
$newest = $now->modify("-{$minimum_age_hours} hours")->format('Y-m-d\TH:i:s.v\Z');

if ($run) {
    try {
        $pdo = new PDO(
            "mysql:host=$db_host;dbname=$db_name;charset=utf8mb4",
            $db_user,
            $db_pass,
            [
                PDO::ATTR_ERRMODE => PDO::ERRMODE_EXCEPTION,
                PDO::ATTR_DEFAULT_FETCH_MODE => PDO::FETCH_ASSOC
            ]
        );

        $ph = implode(',', array_fill(0, count($allowed_counties), '?'));

        /*
         * The correlated subquery deliberately mirrors the website behavior:
         * first media record by MediaObjectID ASC.
         */
        $sql = "
            SELECT
                p.ListingId,
                p.CountyOrParish,
                p.StandardStatus,
                p.PhotosCount,
                p.OriginalEntryTimestamp,
                p.ModificationTimestamp,
                (
                    SELECT m.MediaKey
                    FROM bonstmedia3 m
                    WHERE m.ResourceRecordID = p.ListingId
                    ORDER BY m.MediaObjectID ASC
                    LIMIT 1
                ) AS FirstMediaKey,
                (
                    SELECT COUNT(*)
                    FROM bonstmedia3 m2
                    WHERE m2.ResourceRecordID = p.ListingId
                ) AS MediaRows,
                (
                    SELECT SUM(CASE WHEN m3.store_image = 1 THEN 1 ELSE 0 END)
                    FROM bonstmedia3 m3
                    WHERE m3.ResourceRecordID = p.ListingId
                ) AS Downloaded
            FROM bonstlistingsnew3 p
            WHERE p.StandardStatus = 'Active'
              AND LOWER(TRIM(p.CountyOrParish)) IN ($ph)
              AND COALESCE(p.PhotosCount, 0) > 0
              AND p.ModificationTimestamp >= ?
              AND p.ModificationTimestamp <= ?
            ORDER BY p.ModificationTimestamp ASC, p.ListingId ASC
            LIMIT $limit
        ";

        $st = $pdo->prepare($sql);
        $st->execute(array_merge($allowed_counties, [$oldest, $newest]));
        $candidates = $st->fetchAll();

        $summary['candidates'] = count($candidates);

        foreach ($candidates as $r) {
            $mediaKey = trim((string)($r['FirstMediaKey'] ?? ''));
            $x = $r;
            $x['ImageURL'] = '';
            $x['WasabiStatus'] = '';
            $x['HTTP'] = '';
            $x['ContentType'] = '';
            $x['CheckError'] = '';

            if ($mediaKey === '') {
                $x['WasabiStatus'] = 'NO MEDIA ROW';
                $summary['no_media']++;
                $rows[] = $x;
                continue;
            }

            $url = $wasabi_base_url . rawurlencode($mediaKey) . '.MedRes.jpeg';
            $check = wasabi_image_exists($url);

            $x['ImageURL'] = $url;
            $x['WasabiStatus'] = $check['status'];
            $x['HTTP'] = $check['http'];
            $x['ContentType'] = $check['type'];
            $x['CheckError'] = $check['error'];

            if ($check['exists']) {
                $summary['found']++;
            } elseif ($check['status'] === 'MISSING') {
                $summary['missing']++;
            } else {
                $summary['check_error']++;
            }

            $rows[] = $x;

            // Be polite to Wasabi. This is NOT MLS Grid.
            usleep(50000); // 0.05 sec
        }

    } catch (Throwable $e) {
        $error = $e->getMessage();
    }
}

$displayRows = array_values(array_filter($rows, function($r) use ($show) {
    if ($show === 'all') return true;
    return ($r['WasabiStatus'] ?? '') !== 'FOUND';
}));
?>
<!doctype html>
<html>
<head>
<meta charset="utf-8">
<title>Wasabi Missing Photo Detector V1</title>
<style>
body{font-family:Arial,sans-serif;margin:24px;color:#222}
h1{margin-bottom:6px}
.note{background:#eef6ff;border:1px solid #b8d7f5;padding:12px;margin:14px 0}
.warn{background:#fff4e5;border:1px solid #e9c27d;padding:12px;margin:14px 0}
.error{background:#ffecec;border:1px solid #e1a3a3;padding:12px;white-space:pre-wrap}
form{margin:16px 0}
label{margin-right:8px}
input,select,button{padding:7px;margin-right:10px}
.cards{display:flex;gap:10px;flex-wrap:wrap;margin:18px 0}
.card{border:1px solid #ccc;padding:10px 14px;min-width:120px}
.card b{display:block;font-size:22px}
table{border-collapse:collapse;width:100%;font-size:13px}
th,td{border:1px solid #ccc;padding:6px;text-align:left;vertical-align:top}
th{background:#f2f2f2}
.missing{background:#ffe7e7}
.nomedia{background:#fff0cf}
.checkerror{background:#f3e7ff}
.found{background:#eaf7ea}
.small{font-size:12px;color:#555}
code{font-family:Consolas,monospace}
</style>
</head>
<body>

<h1>Wasabi Missing Photo Detector V1</h1>

<div class="note">
<b>Read only.</b> This script makes <b>zero MLS Grid requests</b> and makes no database changes.
It checks the first <code>bonstmedia3</code> MediaKey against the public Wasabi <code>.MedRes.jpeg</code> image.
</div>

<div class="warn">
Candidate window: Active listings in the 16-county market whose <b>ModificationTimestamp is between
<?=h($lookback_hours)?> and <?=h($minimum_age_hours)?> hours old</b>.
This gives the normal media pipeline at least <?=h($minimum_age_hours)?> hours before flagging a missing image.
</div>

<form method="get">
    <input type="hidden" name="run" value="1">
    <label>Show
        <select name="show">
            <option value="missing" <?=$show==='missing'?'selected':''?>>Problems only</option>
            <option value="all" <?=$show==='all'?'selected':''?>>All checked listings</option>
        </select>
    </label>
    <label>Limit
        <input type="number" name="limit" min="1" max="2000" value="<?=h($limit)?>">
    </label>
    <button type="submit">Run Wasabi Check</button>
</form>

<?php if ($error): ?>
<div class="error"><b>Error:</b> <?=h($error)?></div>
<?php endif; ?>

<?php if ($run && !$error): ?>
<div class="small">
UTC window: <?=h($oldest)?> through <?=h($newest)?>
</div>

<div class="cards">
    <div class="card">Candidates<b><?=h($summary['candidates'])?></b></div>
    <div class="card">Image Found<b><?=h($summary['found'])?></b></div>
    <div class="card">Image Missing<b><?=h($summary['missing'])?></b></div>
    <div class="card">No Media Row<b><?=h($summary['no_media'])?></b></div>
    <div class="card">Check Errors<b><?=h($summary['check_error'])?></b></div>
    <div class="card">MLS Grid Requests<b>0</b></div>
</div>

<p><b>Rows displayed:</b> <?=count($displayRows)?> (<?=h($show==='all'?'all checked listings':'problems only')?>)</p>

<table>
<thead>
<tr>
    <th>ListingId</th>
    <th>County</th>
    <th>Photos</th>
    <th>Media Rows</th>
    <th>Downloaded</th>
    <th>ModificationTimestamp</th>
    <th>First MediaKey</th>
    <th>Wasabi</th>
    <th>HTTP</th>
</tr>
</thead>
<tbody>
<?php foreach ($displayRows as $r):
    $status = $r['WasabiStatus'] ?? '';
    $class = $status==='FOUND' ? 'found' :
             ($status==='MISSING' ? 'missing' :
             ($status==='NO MEDIA ROW' ? 'nomedia' : 'checkerror'));
?>
<tr class="<?=h($class)?>">
    <td><?=h($r['ListingId'])?></td>
    <td><?=h($r['CountyOrParish'])?></td>
    <td><?=h($r['PhotosCount'])?></td>
    <td><?=h($r['MediaRows'])?></td>
    <td><?=h($r['Downloaded'] ?? 0)?></td>
    <td><?=h($r['ModificationTimestamp'])?></td>
    <td><?=h($r['FirstMediaKey'])?></td>
    <td>
        <b><?=h($status)?></b>
        <?php if (!empty($r['CheckError'])): ?>
            <div class="small"><?=h($r['CheckError'])?></div>
        <?php endif; ?>
    </td>
    <td><?=h($r['HTTP'])?></td>
</tr>
<?php endforeach; ?>
</tbody>
</table>
<?php endif; ?>

</body>
</html>
