"""Web entry point for shared hosting (cPanel > Setup Python App).
Startup file: passenger_wsgi.py     Entry point: application
It lets the normal dentallab.py run behind the hosting web server."""
import os, sys, io, json, time, traceback
from http import HTTPStatus
from http.client import HTTPMessage

HERE = os.path.dirname(os.path.abspath(__file__))
sys.path.insert(0, HERE)
os.environ.setdefault("DENTALLAB_HTTPS", "1")          # cookies only over https (set to 0 only for testing without https)
os.environ.setdefault("DENTALLAB_DATA", os.path.join(HERE, "data"))

_ERR = None
try:
    import dentallab as D
    D.init(); D.ensure_first_license()
except Exception:
    _ERR = traceback.format_exc()
    try: open(os.path.join(HERE, "error.log"), "a").write(time.ctime() + "\n" + _ERR + "\n")
    except Exception: pass


class _Req(object):
    """Runs one request through the existing handler class (D.H) without a socket."""
    def __init__(self, environ):
        h = D.H.__new__(D.H)
        h.status, h.hdrs, h.wfile = 200, [], io.BytesIO()
        h.path = (environ.get("PATH_INFO") or "/") + (("?" + environ["QUERY_STRING"]) if environ.get("QUERY_STRING") else "")
        h.headers = HTTPMessage()
        for k, v in environ.items():
            if k.startswith("HTTP_"): h.headers[k[5:].replace("_", "-").title()] = v
        n = int(environ.get("CONTENT_LENGTH") or 0)
        h.headers["Content-Length"] = str(n)
        h.rfile = io.BytesIO(environ["wsgi.input"].read(n) if n else b"")
        self.h = h

def _patch_handler():
    H = D.H
    def send_response(self, code, message=None): self.status = code
    def send_header(self, k, v): self.hdrs.append((k, v))
    def end_headers(self): pass
    def send_error(self, code, message=None, explain=None):
        self.status = code; self.hdrs = [("Content-Type", "text/plain; charset=utf-8")]
        self.wfile = io.BytesIO(); self.wfile.write(("Error %s" % code).encode())
    H.send_response, H.send_header, H.end_headers, H.send_error = send_response, send_header, end_headers, send_error
if not _ERR: _patch_handler()

# ---- simple protection against password guessing: 8 wrong logins in 10 minutes -> wait ----
def _fail_table():
    c = D.db(); c.execute("CREATE TABLE IF NOT EXISTS login_fail(ip TEXT, t REAL)"); return c
def _blocked(ip):
    with _fail_table() as c:
        c.execute("DELETE FROM login_fail WHERE t<?", (time.time() - 600,))
        return c.execute("SELECT count(*) FROM login_fail WHERE ip=?", (ip,)).fetchone()[0] >= 8
def _fail(ip):
    with _fail_table() as c: c.execute("INSERT INTO login_fail VALUES(?,?)", (ip, time.time()))

# ---- automatic backup (no background thread on shared hosting): after a save, at most once per backup interval ----
_last = {"t": 0}
def _auto_backup():
    try:
        mins = max(5, int(D.num(D._setting("backup_minutes", "60")) or 60))
        if time.time() - _last["t"] < mins * 60:
            return
        _last["t"] = time.time()
        files = [f for f in os.listdir(D.BACKUP_DIR) if f.endswith("-auto.db")] if os.path.isdir(D.BACKUP_DIR) else []
        newest = max([os.path.getmtime(os.path.join(D.BACKUP_DIR, f)) for f in files] or [0])
        if time.time() - newest >= mins * 60:
            D.backup_now("auto")
    except Exception:
        pass


def _respond(start_response, status, hdrs, body):
    if isinstance(body, str): body = body.encode("utf-8")
    start_response("%d %s" % (status, HTTPStatus(status).phrase), hdrs + [("Content-Length", str(len(body)))] if not any(k.lower() == "content-length" for k, _ in hdrs) else hdrs)
    return [body]


def application(environ, start_response):
    if _ERR:
        return _respond(start_response, 500, [("Content-Type", "text/plain; charset=utf-8")], "Dental Lab failed to start. See error.log in the app folder.\n\n" + _ERR)
    method = environ.get("REQUEST_METHOD", "GET")
    path = environ.get("PATH_INFO", "/")
    ip = environ.get("REMOTE_ADDR", "")
    try:
        if method == "POST" and path == "/api/login" and _blocked(ip):
            return _respond(start_response, 200, [("Content-Type", "application/json; charset=utf-8")],
                            json.dumps({"error": "Too many wrong attempts. Wait 10 minutes and try again."}))
        h = _Req(environ).h
        if method == "GET": h.do_GET()
        elif method == "POST": h.do_POST()
        else: return _respond(start_response, 405, [("Content-Type", "text/plain")], "Method not allowed")
        if method == "POST" and path == "/api/login":
            try:
                if json.loads(h.wfile.getvalue() or b"{}").get("error"): _fail(ip)
            except Exception: pass
        elif method == "POST" and h.status == 200 and path not in ("/api/logout",):
            _auto_backup()
        hdrs = list(h.hdrs) + [("X-Content-Type-Options", "nosniff"), ("Cache-Control", "no-store")]
        return _respond(start_response, h.status, hdrs, h.wfile.getvalue())
    except Exception:
        try: open(os.path.join(HERE, "error.log"), "a").write(time.ctime() + "\n" + traceback.format_exc() + "\n")
        except Exception: pass
        return _respond(start_response, 500, [("Content-Type", "application/json; charset=utf-8")], json.dumps({"error": "Server error"}))
