#!/usr/bin/env python3
"""Static checks that do not require Linux, Helm, Ansible or a Kubernetes cluster."""

from __future__ import annotations

import json
import py_compile
import re
import sys
from pathlib import Path

import yaml


DEPLOY_ROOT = Path(__file__).resolve().parents[1]
REQUIRED_FILES = [
    "README.md",
    "versions.lock.yaml",
    "config/site.example.yaml",
    "config/site-ha.example.yaml",
    "config/secrets.example.yaml",
    "inventories/single/hosts.example.yml",
    "inventories/ha/hosts.example.yml",
    "scripts/deploy.sh",
    "scripts/verify-bundle.sh",
    "ansible/site.yml",
    "charts/trackwalker/Chart.yaml",
    "charts/trackwalker/values.yaml",
    "charts/trackwalker/values-single.yaml",
    "charts/trackwalker/values-ha.yaml",
    "charts/trackwalker/templates/applications.yaml",
    "charts/trackwalker/templates/stateful-services.yaml",
    "charts/trackwalker/files/cpu-models.json",
    "charts/trackwalker/files/server-models.json",
]
APP_COMPONENTS = [
    "frontend",
    "backend",
    "uav-access-service",
    "vision-inference",
    "pointcloud-analysis",
    "artifact-installer",
]


def main() -> int:
    errors: list[str] = []
    files = [
        path
        for path in DEPLOY_ROOT.rglob("*")
        if path.is_file()
        and "__pycache__" not in path.parts
        and ".state" not in path.parts
        and "artifacts" not in path.parts
        and "dist" not in path.parts
    ]
    text_suffixes = {
        "",
        ".cfg",
        ".gitignore",
        ".j2",
        ".json",
        ".md",
        ".py",
        ".sh",
        ".tpl",
        ".txt",
        ".yaml",
        ".yml",
    }

    for relative_path in REQUIRED_FILES:
        if not (DEPLOY_ROOT / relative_path).is_file():
            errors.append(f"missing required file: {relative_path}")

    for path in files:
        relative = path.relative_to(DEPLOY_ROOT)
        data = path.read_bytes()
        if b"\r\n" in data:
            errors.append(f"CRLF is not allowed in offline Linux artifact: {relative}")
        if b"\x00" in data:
            errors.append(f"NUL byte found: {relative}")
        if path.suffix in text_suffixes or path.name == ".gitignore":
            try:
                text = data.decode("utf-8")
            except UnicodeDecodeError as exc:
                errors.append(f"text file is not UTF-8: {relative}: {exc}")
                continue
            if any(line.endswith((" ", "\t")) for line in text.splitlines()):
                errors.append(f"trailing whitespace found: {relative}")
            if path.suffix in {".yaml", ".yml", ".tpl"}:
                latest_pattern = r"(?i):" + r"latest(?:[\"'\s}]|$)"
                if re.search(latest_pattern, text):
                    errors.append(f"unlocked latest image tag: {relative}")
            if ("ansible" + ".utils.") in text:
                errors.append(f"external Ansible collection dependency: {relative}")

    for path in DEPLOY_ROOT.rglob("*.py"):
        try:
            py_compile.compile(str(path), doraise=True)
        except py_compile.PyCompileError as exc:
            errors.append(f"Python compile failed for {path.relative_to(DEPLOY_ROOT)}: {exc}")

    yaml_paths = [
        path
        for path in files
        if path.suffix in {".yaml", ".yml"}
        and "templates" not in path.parts
    ]
    for path in yaml_paths:
        try:
            list(yaml.safe_load_all(path.read_text(encoding="utf-8")))
        except yaml.YAMLError as exc:
            errors.append(f"YAML parse failed for {path.relative_to(DEPLOY_ROOT)}: {exc}")

    for path in DEPLOY_ROOT.rglob("*.json"):
        try:
            json.loads(path.read_text(encoding="utf-8"))
        except json.JSONDecodeError as exc:
            errors.append(f"JSON parse failed for {path.relative_to(DEPLOY_ROOT)}: {exc}")

    applications = (
        DEPLOY_ROOT / "charts/trackwalker/templates/applications.yaml"
    ).read_text(encoding="utf-8")
    for component in APP_COMPONENTS:
        if component not in applications:
            errors.append(f"Helm application component is missing: {component}")
    for environment_name in (
        "FLIGHTHUB_ORGANIZATION_UUID",
        "FLIGHTHUB_PROJECT_UUID",
        "FLIGHTHUB_MQTT_TOPIC_FILTER",
        "MINIO_ACCESS_KEY",
        "MINIO_SECRET_KEY",
    ):
        if environment_name not in applications:
            errors.append(f"required application environment mapping is missing: {environment_name}")

    values_ha = yaml.safe_load(
        (DEPLOY_ROOT / "charts/trackwalker/values-ha.yaml").read_text(encoding="utf-8")
    )
    base_values = yaml.safe_load(
        (DEPLOY_ROOT / "charts/trackwalker/values.yaml").read_text(encoding="utf-8")
    )
    version_lock = yaml.safe_load(
        (DEPLOY_ROOT / "versions.lock.yaml").read_text(encoding="utf-8")
    )
    if set(base_values["images"]) != set(version_lock["images"]):
        errors.append("Helm image keys and versions.lock.yaml image keys must match")
    if values_ha["bundledDataServices"]["enabled"] is not False:
        errors.append("HA values must disable bundled stateful services")
    if values_ha["replicaCount"]["backend"] != 1:
        errors.append("backend must remain single replica until it is made HA-safe")

    bundle_verifier = (
        DEPLOY_ROOT / "scripts/verify-bundle.sh"
    ).read_text(encoding="utf-8")
    if "--key" not in bundle_verifier or "--trusted-public-key" not in bundle_verifier:
        errors.append("formal bundle verification must use an explicitly trusted public key")
    if "--certificate" in bundle_verifier:
        errors.append("a certificate shipped inside the bundle must not be its own trust root")

    deployment_entrypoint = (
        DEPLOY_ROOT / "scripts/deploy.sh"
    ).read_text(encoding="utf-8")
    if 'render_helm_values.py" \\' not in deployment_entrypoint or "--versions" not in deployment_entrypoint:
        errors.append("deployment entrypoint must inject locked image digests into Helm values")

    for error in errors:
        print(f"ERROR: {error}", file=sys.stderr)
    if errors:
        print(f"Static validation failed with {len(errors)} error(s)", file=sys.stderr)
        return 1
    print(f"Static validation passed for {len(files)} deployment files")
    return 0


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