#!/usr/bin/env python3
import json
import os
import subprocess
import threading
import time
from datetime import datetime, timezone
from http.server import SimpleHTTPRequestHandler, ThreadingHTTPServer
from pathlib import Path
from urllib.parse import urlsplit

ROOT = Path(__file__).resolve().parent
METRICS = ROOT / "data" / "metrics.json"
GIT_PROJECT_ROOT = ROOT / "repo"
GIT_HTTP_BACKEND = Path("/usr/lib/git-core/git-http-backend")
GIT_PREFIX = "/git"
GIT_REPOSITORY = "/html-canvas-plugin.git"
MAX_GIT_REQUEST_BYTES = 1024 * 1024


def memory_percent():
    values = {}
    with open("/proc/meminfo", encoding="utf-8") as handle:
        for line in handle:
            key, value = line.split(":", 1)
            values[key] = int(value.strip().split()[0])
    total = values.get("MemTotal", 1)
    available = values.get("MemAvailable", 0)
    return round((total - available) * 100 / total, 1)


def update_metrics():
    sequence = 0
    while True:
        sequence += 1
        load = os.getloadavg()[0]
        cpu_count = max(1, os.cpu_count() or 1)
        payload = {
            "cpu": round(min(100.0, load * 100 / cpu_count), 1),
            "memory": memory_percent(),
            "sequence": sequence,
            "updatedAt": datetime.now(timezone.utc).isoformat(timespec="seconds"),
        }
        temp = METRICS.with_suffix(".tmp")
        temp.write_text(json.dumps(payload, separators=(",", ":")), encoding="utf-8")
        temp.replace(METRICS)
        time.sleep(2)


class Handler(SimpleHTTPRequestHandler):
    def __init__(self, *args, **kwargs):
        super().__init__(*args, directory=str(ROOT), **kwargs)

    def end_headers(self):
        self.send_header("Access-Control-Allow-Origin", "*")
        self.send_header("Cache-Control", "no-store")
        self.send_header("X-Content-Type-Options", "nosniff")
        super().end_headers()

    def _git_request(self):
        parsed = urlsplit(self.path)
        if not parsed.path.startswith(f"{GIT_PREFIX}/"):
            return False
        path_info = parsed.path[len(GIT_PREFIX):]
        info_refs = path_info == f"{GIT_REPOSITORY}/info/refs"
        upload_pack = path_info == f"{GIT_REPOSITORY}/git-upload-pack"
        if self.command == "GET":
            allowed = info_refs and parsed.query == "service=git-upload-pack"
        elif self.command == "POST":
            allowed = upload_pack and not parsed.query
        else:
            allowed = False
        if not allowed:
            self.send_error(404)
            return True

        try:
            length = int(self.headers.get("Content-Length", "0"))
        except ValueError:
            self.send_error(400)
            return True
        if length < 0 or length > MAX_GIT_REQUEST_BYTES:
            self.send_error(413)
            return True
        body = self.rfile.read(length) if length else b""
        env = {
            **os.environ,
            "GIT_PROJECT_ROOT": str(GIT_PROJECT_ROOT),
            "GIT_HTTP_EXPORT_ALL": "1",
            "GIT_CONFIG_NOSYSTEM": "1",
            "PATH_INFO": path_info,
            "QUERY_STRING": parsed.query,
            "REQUEST_METHOD": self.command,
            "CONTENT_TYPE": self.headers.get("Content-Type", ""),
            "CONTENT_LENGTH": str(length),
            "HTTP_GIT_PROTOCOL": self.headers.get("Git-Protocol", ""),
            "REMOTE_ADDR": self.client_address[0],
            "SERVER_PROTOCOL": self.protocol_version,
            "SERVER_NAME": self.server.server_name,
            "SERVER_PORT": str(self.server.server_port),
        }
        try:
            result = subprocess.run(
                [str(GIT_HTTP_BACKEND)],
                input=body,
                capture_output=True,
                env=env,
                timeout=30,
                check=False,
            )
        except (OSError, subprocess.TimeoutExpired):
            self.send_error(502)
            return True
        if result.returncode != 0:
            self.send_error(502)
            return True

        separator = b"\r\n\r\n" if b"\r\n\r\n" in result.stdout else b"\n\n"
        try:
            raw_headers, response_body = result.stdout.split(separator, 1)
        except ValueError:
            self.send_error(502)
            return True
        status = 200
        headers = []
        for raw_line in raw_headers.replace(b"\r", b"").split(b"\n"):
            if not raw_line:
                continue
            name, value = raw_line.decode("latin-1").split(":", 1)
            value = value.strip()
            if name.lower() == "status":
                status = int(value.split(" ", 1)[0])
            elif name.lower() in {"content-type", "cache-control", "expires", "pragma"}:
                headers.append((name, value))
        self.send_response(status)
        for name, value in headers:
            self.send_header(name, value)
        self.send_header("Content-Length", str(len(response_body)))
        self.end_headers()
        self.wfile.write(response_body)
        return True

    def do_GET(self):
        if self._git_request():
            return
        super().do_GET()

    def do_POST(self):
        if self._git_request():
            return
        self.send_error(404)

    def log_message(self, fmt, *args):
        return


if __name__ == "__main__":
    METRICS.parent.mkdir(parents=True, exist_ok=True)
    threading.Thread(target=update_metrics, daemon=True).start()
    ThreadingHTTPServer(("127.0.0.1", 38081), Handler).serve_forever()
