#!/usr/bin/python3
"""Mesa field app -- the gen tracker, for a tech standing at a site.

Same idea as m.py: pick a site, see that site. Different audience, though, so
this is NOT the status page in mobile clothing.

  * No nav menu. This carries the mesa_gen_tracker access key, so Mesa opens it,
    and they must not be handed our internal page list -- same rule as
    service_plan.py.
  * It borrows mobile.css and the m.py shell (sticky header, .mrow list rows,
    sheets) so it looks native to the family, but NOT the status JS stack:
    status_data.js is the whole internal model (miners, pods, power) and
    status_gen_modal.js carries gen control. Mesa gets the layout language and
    its own thin data layer off /api/status.
  * Region comes from WHO LOGGED IN -- MESA-ND sees ND sites, MESA-TX sees TX --
    so a tech never has to know the split exists.

What a tech can do here: see the inspect / swap / PM work at their site, flag a
gen as being serviced (which locks out remote engine control while they work),
clear an inspect with a note, retag a gen as swap or waiting-on-parts, mark a PM
complete, add notes, and ask us to shut a group down or start it back up.
"""
import cgi
import cgitb
import json
import sys

sys.path.insert(0, '/opt/ngon/apps')
sys.path.insert(0, '/var/www/html/ngon')
from managers.auth_manager import AuthManager, generate_login_page_html, generate_access_denied_html

cgitb.enable()
form = cgi.FieldStorage()
auth = AuthManager('mesa_gen_tracker')
auth_required, should_exit, headers = auth.require_auth(form)

if should_exit:
    print("Content-Type: application/json")
    if headers:
        print(headers)
    print("")
    print('{"success": false, "error": "Authentication required"}' if auth_required
          else '{"success": true}')
    sys.exit(0)

print("Content-Type: text/html; charset=utf-8")
print("Cache-Control: no-cache, no-store, must-revalidate")
print()

if auth_required:
    print(generate_access_denied_html("Mesa Field") if auth.is_authenticated()
          else generate_login_page_html("Mesa Field"))
    sys.exit(0)

user = AuthManager.get_current_user() or 'mesa'
# MESA-ND -> ND, MESA-TX -> TX. Anyone else (an NGON login checking the app)
# sees everything rather than an empty page.
u = user.upper()
region = 'ND' if u.endswith('-ND') else ('TX' if u.endswith('-TX') else '')


def gen_statuses():
    """Maintenance-tag palette from misc.display — the one source constants_cgi.py
    serves the browser and gen_manager.py reads, so a tag is the same colour here
    as everywhere else."""
    fallback = {
        'active': {'label': 'Active', 'color': '#00ff00', 'textColor': 'black'},
        'active-swap': {'label': 'Active-Swap', 'color': '#ff6600', 'textColor': 'white'},
        'swap': {'label': 'Swap', 'color': '#ff6600', 'textColor': 'white'},
        'inspect': {'label': 'Inspect', 'color': '#ffaa00', 'textColor': 'black'},
        'parts': {'label': 'Parts', 'color': '#ff4444', 'textColor': 'white'},
        'gas': {'label': 'Gas', 'color': '#4a9eff', 'textColor': 'white'},
        'oos': {'label': 'OOS', 'color': '#888888', 'textColor': 'white'},
    }
    try:
        with open('/opt/ngon/config/master_config.json') as f:
            got = ((json.load(f).get('misc') or {}).get('display') or {}).get('genStatuses')
        return got or fallback
    except Exception:
        return fallback


print('''<!DOCTYPE html>
<html lang="en">
<head>
<meta charset="utf-8">
<meta name="viewport" content="width=device-width, initial-scale=1, viewport-fit=cover">
<meta name="theme-color" content="#141414">
<title>Mesa Field</title>
<link rel="stylesheet" href="https://cdnjs.cloudflare.com/ajax/libs/font-awesome/6.0.0-beta3/css/all.min.css">
<link rel="stylesheet" href="mobile.css?v=18">
<style>
/* Additions only — the shell, rows and sheet styling come from mobile.css. */
.mhead .title { color: var(--text); }
.mtag {
    display: inline-block; padding: 1px 6px; border-radius: 3px;
    font-size: 0.68em; font-weight: 700; vertical-align: middle; margin-left: 4px;
}
/* Live load, sat beside the gen number. Dim and lighter than the id so it reads
   as a reading about the gen, not part of its name. */
.kw {
    font-weight: 400; font-size: 0.82em; color: var(--dim);
    margin-left: 6px; white-space: nowrap;
}
.mtag.pm { border-radius: 9px; border: 1px solid; }
.mtag.pm.overdue  { color:#ff8a8a; border-color:rgba(255,68,68,.55);   background:rgba(255,68,68,.13); }
.mtag.pm.due      { color:#ffb27a; border-color:rgba(255,102,0,.55);   background:rgba(255,102,0,.13); }
.mtag.pm.upcoming { color:#ffd48a; border-color:rgba(255,170,0,.45);   background:rgba(255,170,0,.10); }
/* Same hazard striping as the status page, so "a tech is on this" looks the
   same wherever it's seen. Pattern rather than colour — it has to survive
   sitting next to every other tag. */
.mtag.svc { background:#ffb300; color:#17130a; }
/* The bar is a PSEUDO-ELEMENT, not border-image. .mrow (from mobile.css) has a
   border on ALL FOUR sides, and border-image paints every border an element has
   -- so the stripe was drawing a full box of tape around the row instead of a
   bar down its left edge. This also respects .mrow's border-radius, which
   border-image ignores. */
.mrow.servicing {
    position:relative; overflow:hidden;
    background:rgba(255,179,0,.07);
}
.mrow.servicing::before {
    content:''; position:absolute; left:0; top:0; bottom:0; width:5px;
    background: repeating-linear-gradient(45deg,#ffb300 0 7px,#20180a 7px 14px);
}
/* The gen a chat link just sent us to, so it's findable in the list behind its
   own sheet. */
.mrow.focus { border-color:#4a9eff; background:rgba(74,158,255,.07); }
/* Standing reminder while anything is flagged. A forgotten flag blocks remote
   start and auto-restart indefinitely, so it should never be possible to walk
   away without seeing that one is still set. */
.svcbar {
    margin:10px; padding:10px 12px 10px 17px; border-radius:8px;
    background:rgba(255,179,0,.12); color:#ffd98a; font-size:0.86em; line-height:1.45;
    position:relative; overflow:hidden;
}
.svcbar::before {
    content:''; position:absolute; left:0; top:0; bottom:0; width:5px;
    background: repeating-linear-gradient(45deg,#ffb300 0 7px,#20180a 7px 14px);
}
.svcbar b { color:#ffb300; }
.svcbar .who { display:block; color:#c9b88a; }
/* The bar itself now comes from mobile.css's .sheet .svcnow::before -- keeping a
   border-image copy here would have drawn a second one beside it. Only the
   radius and the top margin are local. */
.sheet .svcnow {
    margin-top:10px; border-radius:6px;
}
.sheet .svcnow b { color:#ffb300; }
/* Group band: the shutdown request lives here because a shutdown IS per group. */
.grpbar {
    display:flex; align-items:center; gap:8px; margin:14px 10px 6px;
    padding:8px 10px; background:var(--panel); border:1px solid var(--edge);
    border-radius:8px;
}
.grpbar .gn { flex:1; font-weight:600; font-size:0.9em; }
.grpbar button, .sheet button.act {
    min-height:38px; padding:8px 12px; border:none; border-radius:7px;
    font-weight:700; font-size:0.82em; cursor:pointer;
}
.bstop { background:#7a1f1f; color:#ffd9d9; }
.bstart { background:#1f5a33; color:#d9ffe6; }
.sheet {
    position:fixed; inset:0; z-index:1000; background:rgba(0,0,0,.72);
    display:flex; align-items:flex-end;
}
.sheet .card {
    width:100%; max-height:92vh; overflow-y:auto; background:#161616;
    border-top-left-radius:14px; border-top-right-radius:14px;
    border-top:1px solid var(--edge); padding:14px 14px 22px;
}
.sheet h3 { margin:0 0 2px; font-size:1.1em; color:var(--text); }
.sheet .sub2 { color:var(--dim); font-size:0.8em; margin-bottom:10px; }
.sheet .grid { display:grid; grid-template-columns:1fr 1fr; gap:8px; margin-top:10px; }
.sheet .grid.one { grid-template-columns:1fr; }
.sheet textarea {
    width:100%; box-sizing:border-box; min-height:64px; margin-top:10px;
    background:#0e0e0e; border:1px solid var(--edge); color:var(--text);
    border-radius:8px; padding:9px; font:inherit; font-size:0.9em;
}
.bnote { background:#2a4a6a; color:#dbeaff; }
.bpm   { background:#2a7edf; color:#fff; }
.bsvc  { background:#7a4fb5; color:#fff; }
.bclr  { background:#1f5a33; color:#d9ffe6; }
.btag  { background:#5a4a10; color:#ffe9a8; }
.bclose{ background:#333; color:#ddd; }
.sheet button:disabled { opacity:.4; }
.kv { display:flex; justify-content:space-between; padding:4px 0;
      border-bottom:1px solid var(--edge); font-size:0.85em; }
.kv .k { color:var(--dim); }
.note { background:#0e131a; border-left:3px solid #4a9eff; border-radius:0 6px 6px 0;
        padding:8px 10px; margin-top:10px; font-size:0.86em; white-space:pre-wrap; }
.toast {
    position:fixed; left:50%; bottom:24px; transform:translateX(-50%);
    background:#222; border:1px solid var(--edge); color:var(--text);
    padding:10px 16px; border-radius:8px; z-index:1200; font-size:0.86em;
    max-width:86vw; text-align:center;
}
.chatwrap { padding:0 10px 20px; }
.cmsg { background:var(--panel); border:1px solid var(--edge); border-radius:8px;
        padding:7px 9px; margin-bottom:6px; font-size:0.86em; }
.cmsg .who { color:var(--info); font-weight:700; font-size:0.82em; }
.cmsg .when { color:var(--dim); font-size:0.76em; float:right; }
/* A gen id in a chat line is a tap target that opens that gen. Underlined as
   well as blue — on a phone, colour alone doesn't read as "tappable", and these
   sit mid-sentence. Padded vertically so a thumb has something to hit without
   the line spacing changing. */
.cmsg .genlink {
    color:#4a9eff; font-weight:700; text-decoration:underline;
    cursor:pointer; padding:3px 0;
}
.crow { display:flex; gap:6px; margin-top:8px; }
.crow textarea { flex:1; min-height:40px; margin:0; }
.crow button { min-width:64px; }
/* Send wears the same shape as the Work/Chat tabs — panel fill, outlined blue —
   so this screen doesn't read as two different button systems. Matches the
   ACTIVE tab rather than the inactive one: it's an action, not a choice. */
.bsend {
    min-width:72px; padding:8px 12px; cursor:pointer;
    border:1px solid #4a9eff; border-radius:8px;
    background:var(--panel); color:var(--text);
    font-weight:700; font-size:0.84em;
}
.bsend:disabled { opacity:.4; }
/* Header and tabs stick together as one unit. The tab bar used to scroll away
   with the content, so once the Chat tab jumped to the newest message you had
   to scroll 60 messages back up to reach Work. Wrapping rather than giving
   .tabs its own `top` offset avoids hardcoding the header's height. */
.mtop { position:sticky; top:0; z-index:900; background:#141414; }
.mtop .mhead { position:static; border-bottom:none; }  /* one edge for the block, on .tabs */
.tabs {
    display:flex; gap:6px; padding:8px 10px;
    background:#141414; border-bottom:1px solid var(--edge);
}
.tabs button {
    flex:1; min-height:38px; border:1px solid var(--edge); border-radius:8px;
    background:var(--panel); color:var(--dim); font-weight:700; font-size:0.84em;
}
.tabs button.on { color:var(--text); border-color:#4a9eff; }
</style>
</head>
<body>

<div class="mtop">
    <div class="mhead">
        <button class="mback" id="back" aria-label="Back" style="display:none">&lsaquo;</button>
        <div class="title" id="title">Mesa Field</div>
        <button class="mgear" id="sitegear" aria-label="Choose sites"><i class="fas fa-gear"></i></button>
        <div class="mconn" id="conn" title="Live"></div>
    </div>
    <div class="tabs">
        <button id="tabWork" class="on">Work</button>
        <button id="tabChat">Chat</button>
    </div>
</div>

<div id="app"><div class="empty">Loading&hellip;</div></div>
''')

print('<script>')
print(f'window.MESA_USER = {json.dumps(user)};')
print(f'window.MESA_REGION = {json.dumps(region)};')
print(f'window.GEN_STATUSES = {json.dumps(gen_statuses())};')
print('</script>')
print('<script src="mesa_mobile.js?v=11"></script>')
print('</body></html>')
