load('ext://namespace', 'namespace_create')
load('ext://secret', 'secret_create_generic')
load('ext://helm_remote', 'helm_remote')
load('ext://restart_process', 'docker_build_with_restart')

config.define_string('topolvm_backing_size')
config.define_bool('orb')
cfg = config.parse()
ORB = cfg.get('orb', False)
# Sparse loopback file size. Sparse means this is a ceiling, not a reservation —
# 20 GiB on a fresh cluster costs ~0 bytes. Bump if your test workloads fill more.
TOPOLVM_BACKING_SIZE = cfg.get('topolvm_backing_size', '20G')

# Tilt's background docker pruner: drop stale tilt-* tags it created earlier.
docker_prune_settings(
  num_builds=5,
  keep_recent=1,
  max_age_mins=60,
)

# Check which optional credential files exist
has_github_credentials = os.path.exists('./.env.github') and os.path.exists('./.github-private-key.pem')
has_domain_connect_key = os.path.exists('./.domain-connect-private-key.pem')
has_depot_credentials = os.path.exists('./.env.depot')

# The stripe CLI is "logged in" when it has a configured account. When it is,
# Tilt writes the shared webhook signing secret into both env files and forwards
# test events to the dashboard and ctrl-api webhooks; otherwise this is skipped.
has_stripe_login = str(local(
    "stripe config --list 2>/dev/null | grep -q account_id && echo yes || echo no",
    quiet=True,
    echo_off=True,
)).strip() == "yes"

local_resource(
  'namespace',
  'kubectl apply -f k8s/manifests/namespace.yaml',
  labels=['setup'],
)

local_resource(
  'rbac',
  'kubectl apply -f k8s/manifests/rbac.yaml',
  labels=['setup'],
)

local_resource(
  'node-labels',
  'kubectl label node --all node-class=untrusted --overwrite',
  labels=['setup'],
)


local_resource(
  'registry',
  '''
  REGISTRY_IP=$(docker inspect ctlptl-registry --format '{{range .NetworkSettings.Networks}}{{.IPAddress}} {{end}}' | awk '{print $NF}')
  echo "Registry IP: $REGISTRY_IP"
  cat <<EOF | kubectl apply -f -
apiVersion: v1
kind: Service
metadata:
  name: ctlptl-registry
  namespace: teral
spec:
  ports:
    - port: 5000
      targetPort: 5000
---
apiVersion: discovery.k8s.io/v1
kind: EndpointSlice
metadata:
  name: ctlptl-registry
  namespace: teral
  labels:
    kubernetes.io/service-name: ctlptl-registry
addressType: IPv4
endpoints:
  - addresses:
      - "$REGISTRY_IP"
ports:
  - port: 5000
EOF
  ''',
  resource_deps=['namespace'],
  labels=['setup'],
)

if ORB:
    # The orb kernel lacks the eBPF JIT required by Cilium. Keep the CNP CRD so
    # Krane can reconcile its normal objects, but use bridge networking without
    # enforcement in this isolated development cluster.
    local_resource(
      'cilium-network-policy-crd',
      'kubectl apply -f https://raw.githubusercontent.com/cilium/cilium/v1.19.0/pkg/k8s/apis/cilium.io/client/crds/v2/ciliumnetworkpolicies.yaml',
      resource_deps=['namespace'],
      labels=['setup'],
    )
else:
    local_resource(
      'cilium-policies',
      'kubectl apply -f k8s/manifests/cilium-policies.yaml',
      resource_deps=['namespace'],
      labels=['setup'],
    )

# ─── TopoLVM (real LVM-backed PVCs for dev) ────────────────────────────────
#
# Customer pods get ephemeral PVCs via the StorageClass krane reads from
# config (`topolvm-ssd`). TopoLVM dynamically carves an LV per PVC out of
# `topolvm-vg`, formats it ext4, and mounts it in the customer pod — same
# semantics as ebs-csi-gp3 in prod, just backed by a loopback file inside
# the minikube node instead of an EBS volume.
#
# Three steps that have to run in order on every `tilt up`:
#   1. setup-vg.sh: create/reattach the loopback device + activate the VG
#      (idempotent, also handles `minikube stop`/`start` recovery).
#   2. helm install topolvm with the cert-manager-free / no-webhook config.
#   3. apply our StorageClass (provisioner: topolvm.io, fsType: ext4).
#
# The kubernetes-sigs csi-hostpath-driver addon is replaced — TopoLVM gives
# us per-volume filesystems so heimdall's statfs reports accurate per-pod
# usage and `df` inside the customer pod shows the allocated size, not the
# host disk.
local_resource(
  'topolvm-disable-old-csi',
  'minikube addons disable csi-hostpath-driver 2>&1 | tail -1 || true',
  labels=['storage'],
)

local_resource(
  'topolvm-vg',
  # Copy the script onto the node first and then run it with no stdin.
  # `minikube ssh -- bash < file` would work, but `minikube ssh` always
  # allocates a PTY and the wrapping SSH session keeps the terminal open
  # after the script exits, hanging Tilt. Copy + exec avoids the PTY trap.
  'minikube cp k8s/topolvm/setup-vg.sh /tmp/topolvm-setup-vg.sh && ' +
  'minikube ssh -- sudo bash /tmp/topolvm-setup-vg.sh ' + TOPOLVM_BACKING_SIZE,
  deps=['k8s/topolvm/setup-vg.sh'],
  resource_deps=['topolvm-disable-old-csi'],
  labels=['storage'],
)

helm_remote(
  'topolvm',
  repo_name='topolvm',
  repo_url='https://topolvm.github.io/topolvm',
  version='15.5.4',
  namespace='topolvm-system',
  create_namespace=True,
  values=['k8s/topolvm/values.yaml'],
)
# Force ordering so the helm install can't run before the VG exists.
# topolvm-lvmd-0 is the lvmd sidecar DaemonSet (one device class = one
# numbered DaemonSet); topolvm-node is the CSI node plugin DaemonSet.
k8s_resource(
  workload='topolvm-controller',
  resource_deps=['topolvm-vg'],
  labels=['storage'],
)
k8s_resource(
  workload='topolvm-node',
  resource_deps=['topolvm-vg'],
  labels=['storage'],
)
k8s_resource(
  workload='topolvm-lvmd-0',
  resource_deps=['topolvm-vg'],
  labels=['storage'],
)

local_resource(
  'topolvm-storageclass',
  'kubectl apply -f k8s/topolvm/storageclass.yaml',
  deps=['k8s/topolvm/storageclass.yaml'],
  resource_deps=['topolvm-controller', 'topolvm-node', 'topolvm-lvmd-0'],
  labels=['storage'],
)

if not ORB:
    local_resource(
      'hubble',
      'kubectl -n kube-system patch configmap cilium-config --type merge -p \'{"data":{"enable-hubble":"true","hubble-listen-address":":4244"}}\' && kubectl -n kube-system rollout restart daemonset cilium && kubectl apply -f k8s/manifests/hubble.yaml && kubectl -n kube-system rollout status daemonset cilium --timeout=120s',
      resource_deps=['cilium-policies'],
      labels=['observability'],
    )

    local_resource(
      'hubble-ui',
      serve_cmd='while true; do kubectl wait --for=condition=ready pod -l k8s-app=hubble-ui -n kube-system --timeout=120s && kubectl port-forward -n kube-system svc/hubble-ui 12000:80; echo "port-forward exited, retrying in 5s..."; sleep 5; done',
      resource_deps=['hubble'],
      labels=['observability'],
      links=['http://localhost:12000'],
    )

local_resource(
    'depot-credentials',
    'if [ -f ./.env.depot ]; then kubectl -n teral create secret generic depot-credentials --from-env-file=./.env.depot -o yaml --dry-run=client | kubectl apply -f -; else echo "Using the local Kubernetes build backend (no .env.depot file)"; fi',
    resource_deps=['namespace'],
    deps=['./.env.depot'],
    labels=['setup'],
    allow_parallel=True,
)

local_resource(
    'github-credentials',
    '''if [ -f ./.env.github ] && [ -f ./.github-private-key.pem ]; then set -a && source ./.env.github && set +a && kubectl -n teral create secret generic github-credentials \
      --from-literal=TERAL_GITHUB_APP_ID="$TERAL_GITHUB_APP_ID" \
      --from-literal=TERAL_GITHUB_APP_WEBHOOK_SECRET="$TERAL_GITHUB_APP_WEBHOOK_SECRET" \
      --from-literal=NEXT_PUBLIC_GITHUB_APP_NAME="$NEXT_PUBLIC_GITHUB_APP_NAME" \
      --from-literal=TERAL_ALLOW_UNAUTHENTICATED_DEPLOYMENTS="$TERAL_ALLOW_UNAUTHENTICATED_DEPLOYMENTS" \
      --from-file=TERAL_GITHUB_PRIVATE_KEY_PEM=./.github-private-key.pem \
      -o yaml --dry-run=client | kubectl apply -f -; else echo "Skipping github-credentials (missing .env.github or .github-private-key.pem)"; fi''',
    resource_deps=['namespace'],
    deps=['./.env.github', './.github-private-key.pem'],
    labels=['setup'],
    allow_parallel=True,
)

local_resource(
    'domain-connect-credentials',
    '''if [ -f ./.domain-connect-private-key.pem ]; then kubectl -n teral create secret generic domain-connect-credentials \
      --from-file=TERAL_DOMAIN_CONNECT_PRIVATE_KEY=./.domain-connect-private-key.pem \
      -o yaml --dry-run=client | kubectl apply -f -; else echo "Skipping domain-connect-credentials (no .domain-connect-private-key.pem file)"; fi''',
    resource_deps=['namespace'],
    deps=['./.domain-connect-private-key.pem'],
    labels=['setup'],
    allow_parallel=True,
)

# When the stripe CLI is logged in, capture its signing secret (stable per
# account) and upsert it into both env files, so ctrl-api and the dashboard
# verify forwarded events with the same secret. Runs before the consumers below.
stripe_secret_dep = []
if has_stripe_login:
    local_resource(
        'stripe-webhook-secret',
        '''
        set -eu
        whsec=$(stripe listen --print-secret)
        for f in ./.env.stripe ../web/apps/dashboard/.env; do
          touch "$f"
          if grep -q '^STRIPE_WEBHOOK_SECRET=' "$f"; then
            sed -i.bak "s|^STRIPE_WEBHOOK_SECRET=.*|STRIPE_WEBHOOK_SECRET=$whsec|" "$f" && rm -f "$f.bak"
          else
            printf 'STRIPE_WEBHOOK_SECRET=%s\\n' "$whsec" >> "$f"
          fi
        done
        echo "Wrote STRIPE_WEBHOOK_SECRET to .env.stripe and dashboard .env"
        ''',
        resource_deps=['namespace'],
        labels=['setup'],
        allow_parallel=True,
    )
    stripe_secret_dep = ['stripe-webhook-secret']

local_resource(
    'stripe-credentials',
    'if [ -f ./.env.stripe ]; then kubectl -n teral create secret generic stripe-credentials --from-env-file=./.env.stripe -o yaml --dry-run=client | kubectl apply -f -; else echo "Skipping stripe-credentials (no .env.stripe file)"; fi',
    resource_deps=['namespace'] + stripe_secret_dep,
    deps=['./.env.stripe'],
    labels=['setup'],
    allow_parallel=True,
)

local_resource(
    'workos-credentials',
    'if [ -f ./.env.workos ]; then kubectl -n teral create secret generic workos-credentials --from-env-file=./.env.workos -o yaml --dry-run=client | kubectl apply -f -; else echo "Skipping workos-credentials (no .env.workos file)"; fi',
    resource_deps=['namespace'],
    deps=['./.env.workos'],
    labels=['setup'],
    allow_parallel=True,
)

local_resource(
    'resend-credentials',
    'if [ -f ./.env.resend ]; then kubectl -n teral create secret generic resend-credentials --from-env-file=./.env.resend -o yaml --dry-run=client | kubectl apply -f -; else echo "Skipping resend-credentials (no .env.resend file)"; fi',
    resource_deps=['namespace'],
    deps=['./.env.resend'],
    labels=['setup'],
    allow_parallel=True,
)

local_resource(
    'webhooks-config',
    'if [ -f ./.env.webhooks ]; then kubectl -n teral create configmap ctrl-worker-webhooks --from-env-file=./.env.webhooks -o yaml --dry-run=client | kubectl apply -f -; else kubectl -n teral delete configmap ctrl-worker-webhooks --ignore-not-found; fi',
    resource_deps=['namespace'],
    deps=['./.env.webhooks'],
    labels=['setup'],
    allow_parallel=True,
)

# Frontline TLS certificates - generates certs with mkcert if they don't exist
local_resource(
    'frontline-certs',
    'if [ ! -f ./certs/teral.local.crt ]; then echo "Generating TLS certificates..." && command -v mkcert >/dev/null || { echo "ERROR: mkcert not found. Install with: brew install mkcert"; exit 1; } && mkcert -install && mkdir -p ./certs && mkcert -cert-file ./certs/teral.local.crt -key-file ./certs/teral.local.key "*.teral.local" "teral.local" "localhost" && echo "*.crt" > ./certs/.gitignore && echo "*.key" >> ./certs/.gitignore; fi && kubectl -n frontline create configmap frontline-certs --from-file=./certs/teral.local.crt --from-file=./certs/teral.local.key -o yaml --dry-run=client | kubectl apply -f -',
    resource_deps=['namespace'],
    labels=['setup'],
)

# Redis service
k8s_yaml('k8s/manifests/redis.yaml')
k8s_resource('redis', port_forwards='6379:6379', resource_deps=[], labels=['database'])

# Keep the mysql service name so existing DSNs still work.
# Restrict rebuilds to schema and seed changes to avoid unrelated pod restarts.
docker_build(
    'teral/mysql:local',
    '..',
    dockerfile='./Dockerfile.mysql',
    only=['pkg/mysql/schema', 'dev/04-seed-workspace.sql'],
)
k8s_yaml('k8s/manifests/mysql.yaml')
k8s_resource('mysql', port_forwards=['3306:33577', '33575:33575'], resource_deps=[], labels=['database'])

local_resource(
    'seed',
    'cd .. && go run ./build/cli dev seed local',
    resource_deps=['mysql'],
    labels=['setup'],
)

# ClickHouse service
# ClickHouse image — same `only` guard as mysql: context is repo root.
docker_build(
    'teral/clickhouse:local',
    '..',
    dockerfile='./Dockerfile.clickhouse',
    only=[
        'pkg/clickhouse/migrations',
        'dev/clickhouse-local-admin.sql',
        'dev/init-clickhouse.sh',
    ],
)
k8s_yaml('k8s/manifests/clickhouse.yaml')
k8s_resource('clickhouse', port_forwards=['8123:8123','9000:9000'], resource_deps=[], labels=['database'])

# Vector Logs (collects logs from krane-managed deployments -> ClickHouse)
k8s_yaml('k8s/manifests/vector-logs.yaml')
k8s_resource('vector-logs', resource_deps=['clickhouse', 'rbac'], labels=['observability'])

# S3 (Garage) service
k8s_yaml('k8s/manifests/s3.yaml')
k8s_resource('s3', port_forwards=['3902:3902', '3903:3903'], resource_deps=[], labels=['database'])

# Restate service
k8s_yaml('k8s/manifests/restate.yaml')
k8s_resource('restate', port_forwards=['8080:8080', '9070:9070'], resource_deps=[], labels=['database'])

# Observability (Prometheus + OTEL Collector w/ Grafana)
k8s_yaml('k8s/manifests/observability.yaml')
k8s_resource('prometheus', resource_deps=[], labels=['observability'])
k8s_resource(
  'otel-collector',
  port_forwards=['4000:3000'],
  resource_deps=[],
  labels=['observability'],
  links=['http://localhost:4000'],
)

# Restate CronJobs (scheduled tasks that call Restate handlers)
k8s_yaml(helm('k8s/charts/restate-cronjobs'))
k8s_resource('restate-key-last-used-sync', resource_deps=['restate', 'ctrl-worker'], labels=['teral'])
k8s_resource('restate-key-refill', resource_deps=['restate', 'ctrl-worker'], labels=['teral'])
k8s_resource('restate-quota-check', resource_deps=['restate', 'ctrl-worker'], labels=['teral'])
k8s_resource('restate-ratelimit-global-counters-cleanup', resource_deps=['restate', 'ctrl-worker'], labels=['teral'])
k8s_resource('restate-audit-log-export', resource_deps=['restate', 'ctrl-worker'], labels=['teral'])
k8s_resource('restate-build-limit-sync', resource_deps=['restate', 'ctrl-worker'], labels=['teral'])
k8s_resource('restate-deploy-billing-push', resource_deps=['restate', 'ctrl-worker'], labels=['teral'])
k8s_resource('restate-idle-preview-deployments', resource_deps=['restate', 'ctrl-worker'], labels=['teral'])


# Service binaries are compiled on the host so rebuilds hit the local Go
# build cache (incremental, shared across all services). The image is only
# built once; after that, live_update syncs the fresh binary straight into
# the running container and the restart_process wrapper re-execs it — no
# image build, no registry push, no pod rollout. Building each service inside
# Docker would recompile the module for every image, so Tilt deliberately
# avoids it. Dashboard Compose compiles its required services together in one
# shared Docker build stage.
#
# Each service watches only the repo-local packages it actually imports, so
# editing one service's code re-fires just that compile, not all seven. Edits
# to a widely-shared package still fan out to every service that imports it,
# which is correct. GOARCH follows the host because the minikube node runs the
# same architecture as the machine hosting it.

# Broad fallback: watch all Go sources if the per-service import graph can't be
# computed (e.g. the service doesn't currently compile).
GO_SERVICE_DEPS = ['../build', '../svc', '../internal', '../pkg', '../gen', '../go.mod', '../go.sum']

# Scoped deps are absolute repo paths outside dev/, so the test-file ignore is
# anchored at the repo root (an ignore glob relative to dev/ wouldn't match
# them). Editing a _test.go then never recompiles or restarts a service.
REPO_ROOT = os.path.abspath('..')
GO_TEST_IGNORE = [REPO_ROOT + '/**/*_test.go']

def go_local_deps(name):
    # Repo-local package dirs this service imports, resolved once at tilt up via
    # `go list -deps`. We select packages in the main module (.Module.Main) so
    # the filter is independent of where the repo is checked out; matching on
    # the filesystem path would only work when the clone happens to live under
    # a matching directory. Adding a brand-new import needs a Tiltfile reload
    # before the new edge is watched. `|| true` keeps a broken build from
    # crashing the Tiltfile; we then fall back to the broad list so a service
    # never silently stops rebuilding.
    out = str(local(
        'cd .. && go list -deps -f "{{if .Module}}{{if .Module.Main}}{{.Dir}}{{end}}{{end}}" ./build/%s 2>/dev/null || true' % name,
        quiet=True, echo_off=True,
    )).strip()
    dirs = [d for d in out.split('\n') if d]
    if not dirs:
        return GO_SERVICE_DEPS
    return dirs + ['../go.mod', '../go.sum']

def go_service_image(ref, name):
    # The context dir must exist when the Tiltfile is evaluated, before the
    # compile resource has produced a binary (fresh clone case).
    local('mkdir -p bin/' + name, quiet=True, echo_off=True)
    compile_name = name + '-compile'
    local_resource(
        compile_name,
        'cd .. && ' +
        'CGO_ENABLED=0 GOOS=linux GOARCH=$(go env GOARCH) ' +
        'go build -o dev/bin/%s/teral ./build/%s' % (name, name),
        deps=go_local_deps(name),
        ignore=GO_TEST_IGNORE,
        labels=['build'],
        allow_parallel=True,
    )
    docker_build_with_restart(
        ref=ref,
        context='bin/' + name,
        dockerfile='Dockerfile.binary',
        entrypoint=['/teral'],
        live_update=[sync('bin/%s/teral' % name, '/teral')],
    )
    return compile_name

# ─── Vault ─────────────────────────────────────────────────────────────────
go_service_image('teral/vault:dev', 'vault')
k8s_yaml('k8s/manifests/vault.yaml')
k8s_resource(
  'vault',
  port_forwards='8060:8060',
  resource_deps=['s3', 'vault-compile'],
  labels=['teral'],
  auto_init=True,
  trigger_mode=TRIGGER_MODE_AUTO
)

# ─── API ───────────────────────────────────────────────────────────────────
go_service_image('teral/api:dev', 'api')
if ORB and not has_github_credentials:
    # Kubernetes omits optional secret values rather than setting a typed zero.
    # Use a valid integer so TOML parsing succeeds while GitHub remains disabled.
    api_manifest = str(read_file('k8s/manifests/api.yaml')).replace(
        'app_id = ${TERAL_GITHUB_APP_ID}',
        'app_id = 0',
    )
    k8s_yaml(blob(api_manifest))
else:
    k8s_yaml('k8s/manifests/api.yaml')
k8s_resource(
  'api',
  port_forwards='7070:7070',
  resource_deps=['mysql', 'clickhouse', 'redis', 'api-compile', 'github-credentials'],
  labels=['teral'],
  auto_init=True,
  trigger_mode=TRIGGER_MODE_AUTO
)

# ─── Ctrl API ──────────────────────────────────────────────────────────────
go_service_image('teral/control-api:dev', 'control-api')
if ORB and not has_github_credentials:
    ctrl_api_manifest = str(read_file('k8s/manifests/ctrl-api.yaml')).replace(
        'app_id = ${TERAL_GITHUB_APP_ID}',
        'app_id = 0\n    allow_unauthenticated_deployments = true',
    )
    k8s_yaml(blob(ctrl_api_manifest))
else:
    k8s_yaml('k8s/manifests/ctrl-api.yaml')
ctrl_api_deps = ['mysql', 'clickhouse', 'restate', 'vault', 'github-credentials', 'domain-connect-credentials', 'stripe-credentials', 'control-api-compile']
k8s_resource(
  'ctrl-api',
  port_forwards='7091:7091',
  resource_deps=ctrl_api_deps,
  labels=['teral'],
  auto_init=True,
  trigger_mode=TRIGGER_MODE_AUTO
)

# ─── Ctrl Worker (Restate workflow handlers) ───────────────────────────────
go_service_image('teral/control-worker:dev', 'control-worker')
ctrl_worker_manifest = str(read_file('k8s/manifests/ctrl-worker.yaml'))
if has_depot_credentials:
    ctrl_worker_manifest = ctrl_worker_manifest.replace(
        '''    # Local builds run as one-off BuildKit Jobs inside the minikube cluster;
    # no Depot account or token is needed. Production keeps the default
    # backend = "depot".
    [build]
    backend = "kubernetes"

    [build.kubernetes]
    namespace = "teral"
    image = "moby/buildkit:v0.26.3"

    # Built images are pushed to the ctlptl-provisioned local registry. The
    # "ctlptl-registry" name resolves both from pods (via the Service the
    # Tiltfile creates in the teral namespace) and from the minikube node's
    # containerd (via docker network DNS), so the same image reference works
    # for pushing and pulling. The registry has no TLS and no auth, hence
    # insecure and empty credentials.
    [registry]
    repository = "ctlptl-registry:5000/deployments"
    insecure = true
''',
        '''    # A local .env.depot opts into the production-equivalent remote backend.
    [build]
    backend = "depot"

    [build.depot]
    api_url = "https://api.depot.dev"
    project_region = "us-east-1"
    project_prefix = "builds-local"

    [registry]
    repository = "registry.depot.dev/72dvmc7lv7"
    username = "x-token"
    password = "${TERAL_REGISTRY_PASSWORD}"
''',
    ).replace(
        '''            - name: TERAL_VAULT_TOKEN
              value: vault-test-token-123
''',
        '''            - name: TERAL_VAULT_TOKEN
              value: vault-test-token-123
            - name: TERAL_REGISTRY_PASSWORD
              valueFrom:
                secretKeyRef:
                  name: depot-credentials
                  key: TERAL_REGISTRY_PASSWORD
''',
    )
if ORB:
    goarch = str(local('go env GOARCH', quiet=True, echo_off=True)).strip()
    ctrl_worker_manifest = ctrl_worker_manifest.replace(
        'build_platform = "linux/arm64"',
        'build_platform = "linux/%s"' % goarch,
    )
    if not has_github_credentials:
        ctrl_worker_manifest = ctrl_worker_manifest.replace(
            'app_id = ${TERAL_GITHUB_APP_ID}',
            'app_id = 0',
        )
k8s_yaml(blob(ctrl_worker_manifest))
ctrl_worker_deps = ['mysql', 'clickhouse', 'restate', 'registry', 'webhooks-config', 'github-credentials', 'stripe-credentials', 'control-worker-compile', 'workos-credentials', 'resend-credentials']
if has_depot_credentials:
    ctrl_api_deps.append('depot-credentials')
k8s_resource(
  'ctrl-worker',
  port_forwards='7092:7092',
  resource_deps=ctrl_worker_deps,
  labels=['teral'],
  auto_init=True,
  trigger_mode=TRIGGER_MODE_AUTO
)

# ─── GitHub webhook tunnel (ngrok -> ctrl-api) ─────────────────────────────
# Auto-updates the GitHub App webhook URL when credentials are present.
if has_github_credentials:
    local_resource(
        'github-tunnel',
        serve_cmd='cd .. && go run ./build/cli dev github tunnel',
        resource_deps=['ctrl-api'],
        labels=['teral'],
        auto_init=True,
        links=['http://localhost:4040'],
    )

# ─── Frontline (TLS termination proxy for *.teral.local) ───────────────────
go_service_image('teral/frontline:dev', 'frontline')
k8s_yaml('k8s/manifests/frontline.yaml')
k8s_resource(
  'frontline',
  port_forwards=['9443:7443', '9080:7070'],
  resource_deps=['mysql', 'ctrl-api', 'vault', 'frontline-certs', 'frontline-compile'],
  labels=['teral'],
  auto_init=True,
  trigger_mode=TRIGGER_MODE_AUTO,
  links=['https://app.teral.local'],
)

# ─── Krane ─────────────────────────────────────────────────────────────────
go_service_image('teral/krane:dev', 'krane')
krane_resources = read_yaml_stream('k8s/manifests/krane.yaml')
if has_depot_credentials:
    for resource in krane_resources:
        metadata = resource.get('metadata', {})
        if resource.get('kind') == 'ConfigMap' and metadata.get('name') == 'krane-config':
            resource['data']['teral.toml'] += '''
[registry]
url = "registry.depot.dev"
username = "x-token"
password = "${TERAL_REGISTRY_PASSWORD}"
'''
        if resource.get('kind') == 'Deployment' and metadata.get('name') == 'krane':
            for container in resource['spec']['template']['spec']['containers']:
                if container.get('name') == 'krane':
                    container['env'] = [{
                        'name': 'TERAL_REGISTRY_PASSWORD',
                        'valueFrom': {
                            'secretKeyRef': {
                                'name': 'depot-credentials',
                                'key': 'TERAL_REGISTRY_PASSWORD',
                            },
                        },
                    }]
k8s_yaml(encode_yaml_stream(krane_resources))
krane_network_deps = ['cilium-network-policy-crd'] if ORB else ['cilium-policies']
krane_deps = ['ctrl-api', 'rbac', 'namespace', 'registry', 'topolvm-storageclass', 'krane-compile'] + krane_network_deps
if has_depot_credentials:
    krane_deps.append('depot-credentials')
k8s_resource(
  'krane',
  port_forwards='8070:8070',
  resource_deps=krane_deps,
  labels=['teral'],
  auto_init=True,
  trigger_mode=TRIGGER_MODE_AUTO
)

# ─── Heimdall (resource metering agent) ────────────────────────────────────
if not ORB:
    go_service_image('teral/heimdall:dev', 'heimdall')
    k8s_yaml('k8s/manifests/heimdall.yaml')
    k8s_resource(
      'heimdall',
      resource_deps=['clickhouse', 'rbac', 'heimdall-compile'],
      labels=['teral'],
      auto_init=True,
      trigger_mode=TRIGGER_MODE_AUTO
    )


# ─── Logdrain (streams audit logs to customer sinks) ───────────────────────
go_service_image('teral/logdrain:dev', 'logdrain')
k8s_yaml('k8s/manifests/logdrain.yaml')
k8s_resource(
  'logdrain',
  resource_deps=['mysql', 'clickhouse', 'vault', 'logdrain-compile'],
  labels=['teral'],
  auto_init=True,
  trigger_mode=TRIGGER_MODE_AUTO
)


# Portal - standalone K8s service for local development. Amp runs the Vite
# server directly, so omit this manual resource in orb mode to avoid port 3100
# ownership conflicts.
if not ORB:
    local_resource(
        'build-portal-image',
        'docker build -t teral/portal:local -f ../web/apps/portal/Dockerfile .. && minikube image load teral/portal:local',
        deps=['../web/apps/portal', '../web/internal/ui', '../web/internal/icons', '../web/internal/id'],
        labels=['build'],
        auto_init=False,
    )

    k8s_yaml('k8s/manifests/portal.yaml')
    k8s_resource(
        'portal',
        port_forwards='3100:3000',
        resource_deps=['api', 'build-portal-image'],
        labels=['teral'],
        auto_init=False,
        trigger_mode=TRIGGER_MODE_MANUAL,
        links=['http://localhost:3100'],
    )

# Dashboard - runs locally with HMR
# Injects GitHub App env vars from dev/.env.github and dev/.github-private-key.pem
local_resource(
    'dashboard',
    serve_cmd='''
      set -a
      [ -f ./.env.github ] && source ./.env.github
      [ -f ./.github-private-key.pem ] && export TERAL_GITHUB_PRIVATE_KEY_PEM="$(cat ./.github-private-key.pem)"
      export GITHUB_APP_ID="$TERAL_GITHUB_APP_ID"
      export PORT=3000
      set +a
      cd ../web && pnpm --filter=@teral/dashboard dev
    ''',
    deps=[],
    resource_deps=['mysql', 'clickhouse', 'ctrl-api'] + stripe_secret_dep,
    labels=['teral'],
    auto_init=True,
    readiness_probe=probe(http_get=http_get_action(port=3000, path='/'), period_secs=5, failure_threshold=30),
    links=['http://localhost:3000'],
)

# ─── Stripe webhook listeners (stripe CLI -> dashboard + ctrl-api) ──────────
# Forward test-mode events to both webhook endpoints so local billing flows
# (subscribe, invoice.created close) fire end to end. Only when logged in; the
# signing secret was written into both env files by stripe-webhook-secret above.
# --forward-to takes one URL, so one listener per endpoint.
if has_stripe_login:
    local_resource(
        'stripe-listen-dashboard',
        serve_cmd='stripe listen --forward-to http://localhost:3000/api/webhooks/stripe',
        resource_deps=['dashboard'],
        labels=['teral'],
        auto_init=True,
    )
    local_resource(
        'stripe-listen-ctrl',
        serve_cmd='stripe listen --forward-to http://localhost:7091/webhooks/stripe',
        resource_deps=['ctrl-api'],
        labels=['teral'],
        auto_init=True,
    )

print("""
Tilt is ready!


Web UI: http://localhost:10350

Tips:
- Press 's' to open a shell in running containers
- Press 'r' to manually trigger rebuilds
- Use 'tilt down' to stop all services
- Use 'tilt logs <service>' to view specific service logs

Frontline (HTTPS on *.teral.local):
- Run 'sudo minikube tunnel' in a separate terminal to bind ports 80/443
- Then access https://app.teral.local (requires DNS setup via setup-wildcard-dns.sh)

""")
