#!/usr/bin/env python3
"""Static update server with JSONL request archives for Maodou clients."""

from __future__ import annotations

import argparse
import json
import os
from datetime import datetime, timezone
from http.server import SimpleHTTPRequestHandler, ThreadingHTTPServer
from pathlib import Path
from typing import Any
from urllib.parse import urlsplit


DEFAULT_DIRECTORY = "/home/app"
DEFAULT_ARCHIVE = "/home/app/maodou/update-archive/requests.jsonl"
DEFAULT_PREFIX = "/maodou/"


def read_header(handler: SimpleHTTPRequestHandler, name: str) -> str | None:
    value = handler.headers.get(name)
    if value is None:
        return None
    value = value.strip()
    return value or None


def normalize_status(value: Any) -> int | str:
    if isinstance(value, int):
        return value
    try:
        return int(str(value))
    except (TypeError, ValueError):
        return str(value)


def client_ip(handler: SimpleHTTPRequestHandler) -> str:
    forwarded_for = read_header(handler, "X-Forwarded-For")
    if forwarded_for:
        return forwarded_for.split(",", 1)[0].strip()
    return str(handler.client_address[0])


class UpdateArchiveHandler(SimpleHTTPRequestHandler):
    archive_path: Path
    archive_prefix: str

    def log_message(self, format: str, *args: Any) -> None:
        return

    def log_request(self, code: int | str = "-", size: int | str = "-") -> None:
        parsed = urlsplit(self.path)
        if not parsed.path.startswith(self.archive_prefix):
            return

        record = {
            "ts": datetime.now(timezone.utc).isoformat(),
            "method": self.command,
            "path": parsed.path,
            "query": parsed.query or None,
            "status": normalize_status(code),
            "size": normalize_status(size),
            "clientIp": client_ip(self),
            "userAgent": read_header(self, "User-Agent"),
            "range": read_header(self, "Range"),
            "ifNoneMatch": read_header(self, "If-None-Match"),
            "ifModifiedSince": read_header(self, "If-Modified-Since"),
            "maodou": {
                "client": read_header(self, "X-Maodou-Client"),
                "clientId": read_header(self, "X-Maodou-Client-Id"),
                "version": read_header(self, "X-Maodou-Version"),
                "platform": read_header(self, "X-Maodou-Platform"),
                "arch": read_header(self, "X-Maodou-Arch"),
                "channel": read_header(self, "X-Maodou-Channel"),
            },
        }

        self.archive_path.parent.mkdir(parents=True, exist_ok=True)
        with self.archive_path.open("a", encoding="utf-8") as archive:
            archive.write(json.dumps(record, ensure_ascii=False, separators=(",", ":")) + "\n")


def parse_args() -> argparse.Namespace:
    parser = argparse.ArgumentParser(description="Serve Maodou update files and archive client update requests.")
    parser.add_argument("--directory", default=os.environ.get("MAODOU_UPDATE_ROOT", DEFAULT_DIRECTORY))
    parser.add_argument("--archive", default=os.environ.get("MAODOU_UPDATE_ARCHIVE", DEFAULT_ARCHIVE))
    parser.add_argument("--prefix", default=os.environ.get("MAODOU_UPDATE_ARCHIVE_PREFIX", DEFAULT_PREFIX))
    parser.add_argument("--bind", default=os.environ.get("MAODOU_UPDATE_BIND", "0.0.0.0"))
    parser.add_argument("--port", type=int, default=int(os.environ.get("MAODOU_UPDATE_PORT", "8081")))
    return parser.parse_args()


def main() -> None:
    args = parse_args()
    handler_class = lambda *handler_args, **handler_kwargs: UpdateArchiveHandler(
        *handler_args,
        directory=args.directory,
        **handler_kwargs,
    )
    UpdateArchiveHandler.archive_path = Path(args.archive)
    UpdateArchiveHandler.archive_prefix = args.prefix

    server = ThreadingHTTPServer((args.bind, args.port), handler_class)
    print(
        f"Serving {args.directory} on http://{args.bind}:{args.port}; "
        f"archiving {args.prefix} requests to {args.archive}",
        flush=True,
    )
    server.serve_forever()


if __name__ == "__main__":
    main()
