#!/usr/bin/env python3
"""
Ask server for Caddy on-demand TLS.

Caddy calls GET http://127.0.0.1:8101/ask?domain=<hostname> before
requesting a certificate for a new tenant subdomain. This server checks
whether the domain is registered as a CNAME in GoatCounter's SQLite database
and returns:
  200  domain is known → Caddy may proceed with ACME
  404  domain is unknown → Caddy skips ACME for this hostname

Run as: /opt/stats/goatcounter-ask (via systemd stats-ask.service)
Listens on 127.0.0.1:8101 (loopback only, never externally accessible).

Installed and started by install.sh alongside stats.service.
"""

import sqlite3
import http.server
import urllib.parse
import os
import sys

DB_PATH = os.environ.get("STATS_DB", "/opt/stats/data/db.sqlite")
LISTEN_HOST = "127.0.0.1"
LISTEN_PORT = 8101
BASE_DOMAIN = ".stats.wise-relations.com"


class AskHandler(http.server.BaseHTTPRequestHandler):
    def log_message(self, fmt, *args):
        # Suppress per-request noise; errors still go to stderr.
        pass

    def do_GET(self):
        parsed = urllib.parse.urlparse(self.path)
        if parsed.path != "/ask":
            self.send_response(404)
            self.end_headers()
            return

        qs = urllib.parse.parse_qs(parsed.query)
        domains = qs.get("domain", [])
        if not domains:
            self.send_response(400)
            self.end_headers()
            return

        domain = domains[0].lower().strip()

        # Always allow the apex and static domain (they have regular certs,
        # not on-demand, but this guard is harmless).
        always_allow = {
            "stats.wise-relations.com",
            "static.stats.wise-relations.com",
            "www.stats.wise-relations.com",
        }
        if domain in always_allow:
            self.send_response(200)
            self.end_headers()
            return

        # Two lookup paths:
        #   1. SaaS tenant: <code>.stats.wise-relations.com → match sites.code.
        #   2. Custom domain: customer's own hostname → match sites.cname.
        try:
            con = sqlite3.connect(f"file:{DB_PATH}?mode=ro", uri=True, timeout=2)
            found = False
            if domain.endswith(BASE_DOMAIN):
                code = domain[: -len(BASE_DOMAIN)]
                if code and "." not in code:
                    cur = con.execute(
                        "SELECT 1 FROM sites WHERE lower(code) = ? AND state = 'a' LIMIT 1",
                        (code,),
                    )
                    found = cur.fetchone() is not None
            if not found:
                cur = con.execute(
                    "SELECT 1 FROM sites WHERE lower(cname) = ? AND state = 'a' LIMIT 1",
                    (domain,),
                )
                found = cur.fetchone() is not None
            con.close()
        except Exception as exc:
            print(f"ask-server: db error for {domain!r}: {exc}", file=sys.stderr)
            # Fail closed: deny cert on DB error rather than issuing spurious certs.
            self.send_response(500)
            self.end_headers()
            return

        if found:
            self.send_response(200)
        else:
            self.send_response(404)
        self.end_headers()


if __name__ == "__main__":
    server = http.server.HTTPServer((LISTEN_HOST, LISTEN_PORT), AskHandler)
    print(f"ask-server: listening on {LISTEN_HOST}:{LISTEN_PORT}", file=sys.stderr)
    try:
        server.serve_forever()
    except KeyboardInterrupt:
        pass
