from __future__ import annotations

import argparse
import hmac
import json
import os
import sys
from http.server import BaseHTTPRequestHandler, ThreadingHTTPServer
from pathlib import Path
from typing import Any


ROOT = Path(__file__).resolve().parents[1]
CONNECTOR = ROOT / "plugins" / "1c" / "connector"
if str(CONNECTOR) not in sys.path:
    sys.path.insert(0, str(CONNECTOR))

import repository_control  # noqa: E402


def bearer_token() -> str:
    env_name = os.environ.get("ONEC_REPOSITORY_RUNNER_TOKEN_ENV", "ONEC_REPOSITORY_RUNNER_TOKEN")
    return os.environ.get(env_name, "")


def authorized(header: str) -> bool:
    expected = bearer_token()
    if not expected:
        return os.environ.get("ONEC_REPOSITORY_RUNNER_ALLOW_UNAUTHENTICATED", "").strip().casefold() == "true"
    prefix = "Bearer "
    return header.startswith(prefix) and hmac.compare_digest(header[len(prefix):], expected)


class Handler(BaseHTTPRequestHandler):
    server_version = "OneCRepositoryRunner/1"

    def _json(self, status: int, value: dict[str, Any]) -> None:
        body = json.dumps(value, ensure_ascii=False).encode("utf-8")
        self.send_response(status)
        self.send_header("Content-Type", "application/json; charset=utf-8")
        self.send_header("Content-Length", str(len(body)))
        self.end_headers()
        self.wfile.write(body)

    def do_GET(self) -> None:  # noqa: N802
        if self.path == "/healthz":
            self._json(200, {"status": "ok", "service": "onec-repository-runner", "auth_configured": bool(bearer_token())})
            return
        self._json(404, {"status": "not_found"})

    def do_POST(self) -> None:  # noqa: N802
        if self.path not in {"/repository/execute", "/configuration/activation/debug"}:
            self._json(404, {"status": "not_found"})
            return
        if not authorized(self.headers.get("Authorization", "")):
            self._json(401, {"status": "unauthorized"})
            return
        try:
            length = int(self.headers.get("Content-Length", "0"))
            if length <= 0 or length > 1024 * 1024:
                raise ValueError("invalid request size")
            payload = json.loads(self.rfile.read(length).decode("utf-8"))
            if not isinstance(payload, dict):
                raise ValueError("request must be an object")
        except (ValueError, json.JSONDecodeError, UnicodeDecodeError) as exc:
            self._json(400, {"status": "invalid_request", "message": str(exc)})
            return
        base_id = str(payload.get("base_id") or "").strip()
        if self.path == "/configuration/activation/debug":
            layer = str(payload.get("layer") or "all").strip()
            mode = str(payload.get("mode") or "debug").strip().casefold()
            request_id = str(payload.get("request_id") or "").strip()
            fingerprint = str(payload.get("fingerprint") or "").strip().casefold()
            if not base_id or layer not in {"all", "base_saved_state", "extension_saved_state"} or mode != "debug":
                self._json(
                    400,
                    {
                        "status": "invalid_request",
                        "message": "base_id, a supported layer, and mode=debug are required",
                    },
                )
                return
            if bool(request_id) != bool(fingerprint) or (
                request_id
                and (
                    not request_id.startswith("actreq-")
                    or len(request_id) != len("actreq-") + 32
                    or any(char not in "0123456789abcdef" for char in request_id[len("actreq-"):].casefold())
                    or len(fingerprint) != 64
                    or any(char not in "0123456789abcdef" for char in fingerprint)
                )
            ):
                self._json(
                    400,
                    {
                        "status": "invalid_request",
                        "message": "request_id and a 64-hex fingerprint must be supplied together",
                    },
                )
                return
            config, error = repository_control.repository_config(base_id)
            if error:
                self._json(400, error)
                return
            if (config.get("runner") or {}).get("kind") != "local":
                self._json(
                    400,
                    {
                        "status": "invalid_config",
                        "message": "Windows runner base configuration must use runner.kind=local",
                    },
                )
                return
            result = repository_control.activation_debug_probe(
                base_id,
                config,
                layer=layer,
                timeout_seconds=10,
                request_id=request_id,
                fingerprint=fingerprint,
            )
            self._json(200 if result.get("status") in {"ready", "not_ready"} else 409, result)
            return
        action = str(payload.get("action") or "").strip().casefold()
        objects = payload.get("objects") or []
        if not base_id or action not in {"report", "lock", "unlock", "commit"}:
            self._json(400, {"status": "invalid_request", "message": "base_id and a supported action are required"})
            return
        if not isinstance(objects, list) or any(not isinstance(item, str) or not item.strip() for item in objects) or len(objects) > 10000:
            self._json(400, {"status": "invalid_request", "message": "objects must be a bounded string array"})
            return
        config, error = repository_control.repository_config(base_id)
        if error:
            self._json(400, error)
            return
        if (config.get("runner") or {}).get("kind") != "local":
            self._json(400, {"status": "invalid_config", "message": "Windows runner base configuration must use runner.kind=local"})
            return
        result = repository_control._execute_repository(
            base_id, config, action, 180, objects=objects,
            comment=str(payload.get("comment") or ""), keep_locked=payload.get("keep_locked") is True,
        )
        self._json(200 if result.get("status") == "ok" else 409, result)

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


def main() -> None:
    parser = argparse.ArgumentParser(description="Authenticated Windows runner for standard 1C Designer repository commands.")
    parser.add_argument("--host", default=os.environ.get("ONEC_REPOSITORY_RUNNER_HOST", "127.0.0.1"))
    parser.add_argument("--port", type=int, default=int(os.environ.get("ONEC_REPOSITORY_RUNNER_PORT", "8121")))
    args = parser.parse_args()
    ThreadingHTTPServer((args.host, args.port), Handler).serve_forever()


if __name__ == "__main__":
    main()
