#!/usr/bin/env python3
"""Validate the repo-managed Tailscale policy without contacting Tailscale."""

from __future__ import annotations

import json
import pathlib
import re
import sys


REPO_ROOT = pathlib.Path(__file__).resolve().parents[1]
POLICY_PATH = REPO_ROOT / "infra" / "tailscale" / "tailnet-policy.hujson"
INVENTORY_PATH = REPO_ROOT / "homelab.yml"


def strip_hujson(source: str) -> str:
    output: list[str] = []
    index = 0
    in_string = False
    escaped = False

    while index < len(source):
        char = source[index]
        next_char = source[index + 1] if index + 1 < len(source) else ""

        if in_string:
            output.append(char)
            if escaped:
                escaped = False
            elif char == "\\":
                escaped = True
            elif char == '"':
                in_string = False
            index += 1
            continue

        if char == '"':
            in_string = True
            output.append(char)
            index += 1
            continue

        if char == "/" and next_char == "/":
            index += 2
            while index < len(source) and source[index] not in "\r\n":
                index += 1
            continue

        if char == "/" and next_char == "*":
            index += 2
            while index + 1 < len(source) and source[index : index + 2] != "*/":
                index += 1
            index += 2
            continue

        output.append(char)
        index += 1

    normalized = "".join(output)
    cleaned: list[str] = []
    index = 0
    in_string = False
    escaped = False
    while index < len(normalized):
        char = normalized[index]
        if in_string:
            cleaned.append(char)
            if escaped:
                escaped = False
            elif char == "\\":
                escaped = True
            elif char == '"':
                in_string = False
            index += 1
            continue

        if char == '"':
            in_string = True
            cleaned.append(char)
            index += 1
            continue

        if char == ",":
            lookahead = index + 1
            while lookahead < len(normalized) and normalized[lookahead].isspace():
                lookahead += 1
            if lookahead < len(normalized) and normalized[lookahead] in "]}":
                index += 1
                continue

        cleaned.append(char)
        index += 1

    return "".join(cleaned)


def parse_simple_inventory(path: pathlib.Path) -> dict[str, str]:
    values: dict[str, str] = {}
    stack: list[tuple[int, str]] = []
    pattern = re.compile(r"^(\s*)([A-Za-z_][A-Za-z0-9_]*):(?:\s*(.*?))?\s*$")

    for raw_line in path.read_text(encoding="utf-8").splitlines():
        if not raw_line.strip() or raw_line.lstrip().startswith("#") or raw_line.lstrip().startswith("- "):
            continue
        match = pattern.match(raw_line)
        if not match:
            continue
        indent = len(match.group(1))
        key = match.group(2)
        value = (match.group(3) or "").strip()
        while stack and stack[-1][0] >= indent:
            stack.pop()
        path_key = ".".join([item[1] for item in stack] + [key])
        if value == "":
            stack.append((indent, key))
            continue
        if " #" in value:
            value = value.split(" #", 1)[0].strip()
        values[path_key] = value.strip("\"'")

    return values


def require(condition: bool, message: str, failures: list[str]) -> None:
    if not condition:
        failures.append(message)


def main() -> int:
    failures: list[str] = []
    policy = json.loads(strip_hujson(POLICY_PATH.read_text(encoding="utf-8")))
    inventory = parse_simple_inventory(INVENTORY_PATH)

    require(isinstance(policy.get("hosts"), dict), "policy must define hosts", failures)
    require(isinstance(policy.get("acls"), list) and policy["acls"], "policy must define non-empty acls", failures)
    require(isinstance(policy.get("tagOwners"), dict), "policy must define tagOwners", failures)

    hosts = policy.get("hosts", {})
    if isinstance(hosts, dict):
        expected_hosts = {
            "debian": inventory.get("hosts.debian.tailscale_ip"),
            "rpi4": inventory.get("hosts.rpi4.tailscale_ip"),
            "oci-edge": inventory.get("hosts.oci_edge.tailscale_ip"),
            "traefik-lan": inventory.get("network.metallb.traefik_ip"),
        }
        for name, expected_value in expected_hosts.items():
            require(bool(expected_value), f"homelab.yml missing inventory value for {name}", failures)
            require(hosts.get(name) == expected_value, f"policy host {name}={hosts.get(name)!r}, expected {expected_value!r}", failures)

    for index, acl in enumerate(policy.get("acls", [])):
        require(acl.get("action") == "accept", f"acl[{index}] action must be accept", failures)
        require(isinstance(acl.get("src"), list) and acl["src"], f"acl[{index}] must have non-empty src list", failures)
        require(isinstance(acl.get("dst"), list) and acl["dst"], f"acl[{index}] must have non-empty dst list", failures)

    if failures:
        print("tailnet policy validation failed:", file=sys.stderr)
        for failure in failures:
            print(f"  - {failure}", file=sys.stderr)
        return 1

    print("tailnet policy static validation passed")
    return 0


if __name__ == "__main__":
    raise SystemExit(main())
