"""Строит матрицу «приложение -> используемый технологический сервис» для контура.

    py scan_contour.py <имя-кластера> <выход.json>

Имя кластера — директория в clusters/, например любая из перечисленных
в корневом CLAUDE.md. Сканируются только *.yaml в apps/<app>/base и
apps/<app>/<кластер>; документация (*.md) игнорируется, иначе прозаические
упоминания переменных дают ложные срабатывания.
"""
import re, pathlib, json, sys, io

sys.stdout = io.TextIOWrapper(sys.stdout.buffer, encoding="utf-8")
if len(sys.argv) != 3:
    sys.exit(__doc__)
CLUSTER, OUT = sys.argv[1], pathlib.Path(sys.argv[2])
ROOT = pathlib.Path(__file__).resolve().parents[2]

# Считаем развёрнутыми все приложения репозитория. Имя кластера определяет,
# какой оверлей читать поверх base, но состав приложений им не ограничивается:
# часть сервисов работает в контуре, не будучи заявленной в kustomization.
apps = sorted(d.name for d in (ROOT / "apps").iterdir() if d.is_dir())
with_overlay = [a for a in apps if (ROOT / "apps" / a / CLUSTER).is_dir()]
print(f"приложений: {len(apps)}, из них с оверлеем {CLUSTER}: {len(with_overlay)} "
      f"(остальные читаются только из base)\n")

MARKERS = {
    "postgres": [r"POSTGRES_[A-Z]", r"DB__HOST", r"postgresql\.postgresql\.svc",
                 r"secrets/data/apps/[a-z0-9-]+/postgres"],
    "rabbitmq": [r"RABBITMQ", r"AMQP__", r"rabbitmq\.rabbitmq\.svc"],
    "kafka":    [r"KAFKA", r"kafka\.kafka\.svc", r"BOOTSTRAP_SERVERS"],
    "camunda":  [r"ZEEBE", r"CAMUNDA"],
    "s3":       [r"S3__", r"S3_ENDPOINT", r"S3_ACCESS", r"S3_BUCKET"],
    "redis":    [r"REDIS_HOST", r"REDIS__", r"redis\.[a-z-]+\.svc"],
    "vault":    [r"vault\.hashicorp\.com/agent-inject"],
    "oidc":     [r"ZITADEL", r"JWKS", r"OIDC", r"jwt-public"],
    "smtp":     [r"SMTP[_A-Z]*_HOST", r"SMTP__HOST", r"FROM_EMAIL", r"EMAIL_FROM"],
}

def read(paths):
    return "\n".join(p.read_text(encoding="utf-8", errors="replace") for p in paths)

def scan(app):
    """Возвращает по сервису источник связи: manifest, doc или False.

    Манифесты — основной источник, но часть переменных приходит из k8s-секретов
    (блоки secretEnvs) и в YAML не видна. Поэтому CONFIGURATION.md / ENDPOINTS.md
    рядом с манифестами используются как второй источник: связь реальна, просто
    не выражена в конфиге.
    """
    yaml_blob = read([f for sub in ("base", CLUSTER)
                      if (ROOT / "apps" / app / sub).is_dir()
                      for f in (ROOT / "apps" / app / sub).rglob("*.yaml")])
    doc_blob = read(sorted((ROOT / "apps" / app).glob("*.md")))
    out = {}
    for svc, pats in MARKERS.items():
        if any(re.search(p, yaml_blob) for p in pats):
            out[svc] = "manifest"
        elif any(re.search(p, doc_blob, re.I) for p in pats):
            out[svc] = "doc"
        else:
            out[svc] = False
    return out

matrix = {a: scan(a) for a in apps}

istio_path = ROOT / "infrastructure" / "istio-config" / CLUSTER / "istio-config.yaml"
exposed_ns = []
if istio_path.is_file():
    raw = istio_path.read_text(encoding="utf-8")
    body = "\n".join(l for l in raw.splitlines() if not l.lstrip().startswith("#"))
    exposed_ns = sorted({ns for _, ns in re.findall(r"service:\s*([a-z0-9-]+)\.([a-z0-9-]+)\.svc", body)})

hdr = ["app"] + list(MARKERS) + ["istio"]
print(f"приложений: {len(apps)}\n")
print(" | ".join(h.ljust(9) for h in hdr))
print("-" * 112)
MARK = {"manifest": "+", "doc": "d", False: ""}
for app, row in matrix.items():
    cells = [app.ljust(17)] + [MARK[row[s]].ljust(9) for s in MARKERS]
    cells.append("+".ljust(9) if app in exposed_ns else "".ljust(9))
    print(" | ".join(cells))
print("\n  + связь видна в манифестах, d — только в документации (значение из секрета)")

print("\nпотребителей на сервис:")
for s in MARKERS:
    m = sum(1 for r in matrix.values() if r[s] == "manifest")
    d = sum(1 for r in matrix.values() if r[s] == "doc")
    print(f"  {s:10s} {m + d:2d}" + (f"  (из них {d} только по документации)" if d else ""))
print(f"  {'istio':10s} {len([a for a in apps if a in exposed_ns]):2d}")

OUT.write_text(json.dumps({"apps": apps, "matrix": matrix, "exposed": exposed_ns},
                          ensure_ascii=False, indent=2), encoding="utf-8")
print(f"\nзаписано: {OUT}")
