stages:
  - test
  - build
  - deploy

variables:
  PIP_CACHE_DIR: "$CI_PROJECT_DIR/.cache/pip"
  CI_PYTHON_PACKAGES_DIR: "$CI_PROJECT_DIR/.ci-packages"
  APP_IMAGE_REPOSITORY: "$CI_REGISTRY_IMAGE/app"
  ASTERISK_IMAGE_REPOSITORY: "$CI_REGISTRY_IMAGE/asterisk"
  APP_IMAGE_TAG: "$CI_COMMIT_SHA"
  ASTERISK_IMAGE_TAG: "$CI_COMMIT_SHA"
  APP_IMAGE: "$CI_REGISTRY_IMAGE/app:$CI_COMMIT_SHA"
  ASTERISK_IMAGE: "$CI_REGISTRY_IMAGE/asterisk:$CI_COMMIT_SHA"
  DOCKER_TLS_CERTDIR: "/certs"
  DOCKER_HOST: "tcp://docker:2376"
  DOCKER_TLS_VERIFY: "1"
  DOCKER_CERT_PATH: "$DOCKER_TLS_CERTDIR/client"
  PYTHON_BIN: "python3"
  CI_POSTGRES_ADMIN_URL: ""

.python_job:
  tags:
    - crm-shell
  before_script:
    - rm -rf "$CI_PYTHON_PACKAGES_DIR"
    - mkdir -p "$CI_PYTHON_PACKAGES_DIR"
    - $PYTHON_BIN -m pip install --target "$CI_PYTHON_PACKAGES_DIR" -r requirements.txt
    - export PYTHONPATH="$CI_PYTHON_PACKAGES_DIR${PYTHONPATH:+:$PYTHONPATH}"

test:
  extends: .python_job
  stage: test
  script:
    - $PYTHON_BIN -m pytest -q --ignore=tests/test_realtime_voice_service.py
  allow_failure: true

postgres-readiness:
  extends: .python_job
  stage: test
  rules:
    - if: '$ENABLE_POSTGRES_READINESS == "1" && $CI_POSTGRES_ADMIN_URL'
    - when: never
  variables:
    SCHEMA_MANAGEMENT_MODE: migrations
    CC_DATA_DIR: .testdata_pg
    EVENT_BUS_ENABLED: "0"
    APP_TOKEN_SECRET: ci-secret
    ALLOW_LEGACY_HEADER_AUTH: "1"
    TELEGRAM_BOT_ENABLED: "0"
    WHATSAPP_OUTBOUND_ENABLED: "0"
    AI_PROVIDER: stub
    AI_TELEGRAM_ENABLED: "0"
    AI_WHATSAPP_ENABLED: "0"
    AI_VOICE_ENABLED: "0"
  script:
    - |
      export CI_PG_DB="mvpcc_ci_${CI_PIPELINE_IID}_${CI_JOB_ID}"
      $PYTHON_BIN - <<'PY'
      import os
      import psycopg

      admin_url = os.environ["CI_POSTGRES_ADMIN_URL"]
      db_name = os.environ["CI_PG_DB"]

      with psycopg.connect(admin_url, autocommit=True) as conn:
          with conn.cursor() as cur:
              cur.execute(f'DROP DATABASE IF EXISTS "{db_name}" WITH (FORCE)')
              cur.execute(f'CREATE DATABASE "{db_name}"')
      PY
    - export DATABASE_URL="postgresql://postgres@127.0.0.1:5433/${CI_PG_DB}"
    - $PYTHON_BIN scripts/migrate_core_db.py
    - $PYTHON_BIN -m pytest -q tests/test_postgres_readiness.py
  after_script:
    - |
      export CI_PG_DB="mvpcc_ci_${CI_PIPELINE_IID}_${CI_JOB_ID}"
      $PYTHON_BIN - <<'PY'
      import os
      import psycopg

      admin_url = os.environ["CI_POSTGRES_ADMIN_URL"]
      db_name = os.environ["CI_PG_DB"]

      with psycopg.connect(admin_url, autocommit=True) as conn:
          with conn.cursor() as cur:
              cur.execute(f'DROP DATABASE IF EXISTS "{db_name}" WITH (FORCE)')
      PY

.docker_build:
  tags:
    - call-center-prod
  variables:
    DOCKER_HOST: ""
    DOCKER_TLS_VERIFY: ""
    DOCKER_CERT_PATH: ""
    DOCKER_TLS_CERTDIR: ""
  image: docker:27
  services:
    - name: docker:27-dind
      alias: docker
  before_script:
    - command -v docker >/dev/null
    - echo "$CI_REGISTRY_PASSWORD" | docker login "$CI_REGISTRY" -u "$CI_REGISTRY_USER" --password-stdin
  rules:
    - if: '$ENABLE_REGISTRY_BUILD == "1" && $CI_COMMIT_BRANCH == $CI_DEFAULT_BRANCH'
    - if: '$ENABLE_REGISTRY_BUILD == "1" && $CI_COMMIT_TAG'
    - when: never

build-app-image:
  extends: .docker_build
  stage: build
  needs:
    - test
    - job: postgres-readiness
      optional: true
  script:
    - docker build -t "$APP_IMAGE" .
    - docker push "$APP_IMAGE"
    - |
      if [ "$CI_COMMIT_BRANCH" = "$CI_DEFAULT_BRANCH" ]; then
        docker tag "$APP_IMAGE" "$APP_IMAGE_REPOSITORY:latest"
        docker push "$APP_IMAGE_REPOSITORY:latest"
      fi
    - |
      if [ -n "$CI_COMMIT_TAG" ]; then
        docker tag "$APP_IMAGE" "$APP_IMAGE_REPOSITORY:$CI_COMMIT_TAG"
        docker push "$APP_IMAGE_REPOSITORY:$CI_COMMIT_TAG"
      fi

build-asterisk-image:
  extends: .docker_build
  stage: build
  needs:
    - test
    - job: postgres-readiness
      optional: true
  script:
    - docker build -f deployment/asterisk/Dockerfile -t "$ASTERISK_IMAGE" .
    - docker push "$ASTERISK_IMAGE"
    - |
      if [ "$CI_COMMIT_BRANCH" = "$CI_DEFAULT_BRANCH" ]; then
        docker tag "$ASTERISK_IMAGE" "$ASTERISK_IMAGE_REPOSITORY:latest"
        docker push "$ASTERISK_IMAGE_REPOSITORY:latest"
      fi
    - |
      if [ -n "$CI_COMMIT_TAG" ]; then
        docker tag "$ASTERISK_IMAGE" "$ASTERISK_IMAGE_REPOSITORY:$CI_COMMIT_TAG"
        docker push "$ASTERISK_IMAGE_REPOSITORY:$CI_COMMIT_TAG"
      fi

deploy-production:
  stage: deploy
  tags:
    - call-center-prod
  image: alpine:3.21
  needs:
    - build-app-image
    - build-asterisk-image
  environment:
    name: production
  before_script:
    - command -v ssh >/dev/null
    - command -v rsync >/dev/null
    - mkdir -p ~/.ssh
    - chmod 700 ~/.ssh
    - printf '%s\n' "$DEPLOY_SSH_PRIVATE_KEY" > ~/.ssh/id_ed25519
    - chmod 600 ~/.ssh/id_ed25519
    - ssh-keyscan -H "$DEPLOY_HOST" >> ~/.ssh/known_hosts
  script:
    - test -n "$DEPLOY_HOST"
    - test -n "$DEPLOY_USER"
    - test -n "$DEPLOY_PATH"
    - ssh "$DEPLOY_USER@$DEPLOY_HOST" "mkdir -p '$DEPLOY_PATH/deployment'"
    - rsync -av deployment/ "$DEPLOY_USER@$DEPLOY_HOST:$DEPLOY_PATH/deployment/"
    - |
      ssh "$DEPLOY_USER@$DEPLOY_HOST" \
        "mkdir -p '$DEPLOY_PATH/.data_local/generated_ivr_yandex/ivr' '$DEPLOY_PATH/.data_local/recordings' '$DEPLOY_PATH/.asterisk_assets/ivr'"
    - |
      if [ -d .asterisk_assets/ ]; then
        rsync -av .asterisk_assets/ "$DEPLOY_USER@$DEPLOY_HOST:$DEPLOY_PATH/.asterisk_assets/"
      fi
    - |
      if [ -d .asterisk_assets/ivr/ ]; then
        rsync -av --delete .asterisk_assets/ivr/ "$DEPLOY_USER@$DEPLOY_HOST:$DEPLOY_PATH/.data_local/generated_ivr_yandex/ivr/"
      fi
    - |
      ssh "$DEPLOY_USER@$DEPLOY_HOST" \
        "printf 'APP_IMAGE=%s\nASTERISK_IMAGE=%s\n' '$APP_IMAGE' '$ASTERISK_IMAGE' > '$DEPLOY_PATH/deployment/.env.images'"
    - |
      ssh "$DEPLOY_USER@$DEPLOY_HOST" \
        "echo '$CI_REGISTRY_PASSWORD' | docker login '$CI_REGISTRY' -u '$CI_REGISTRY_USER' --password-stdin && \
        cd '$DEPLOY_PATH/deployment' && \
        docker compose --env-file .env.images -f docker-compose.server.registry.yml pull && \
        for container in \$(docker ps -aq --filter publish=9019); do docker rm -f \"\$container\"; done && \
        docker rm -f call-center-ai-voice-runtime-service-1 >/dev/null 2>&1 || true && \
        docker compose --env-file .env.images -f docker-compose.server.registry.yml up -d && \
        docker compose --env-file .env.images -f docker-compose.asterisk.server.registry.yml pull && \
        docker compose --env-file .env.images -f docker-compose.asterisk.server.registry.yml up -d"
  rules:
    - if: '$ENABLE_REGISTRY_BUILD == "1" && $CI_COMMIT_BRANCH == $CI_DEFAULT_BRANCH'
      when: manual
    - when: never
  allow_failure: false

deploy-production-source:
  stage: deploy
  tags:
    - call-center-prod
  needs:
    - test
    - job: postgres-readiness
      optional: true
  variables:
    DOCKER_HOST: ""
    DOCKER_TLS_VERIFY: ""
    DOCKER_CERT_PATH: ""
    DOCKER_TLS_CERTDIR: ""
    APP_IMAGE_NAME: call-center-app
  environment:
    name: production
  before_script:
    - command -v docker >/dev/null
    - command -v ssh >/dev/null
    - command -v scp >/dev/null
    - command -v gzip >/dev/null
    - mkdir -p ~/.ssh
    - chmod 700 ~/.ssh
    - cp "$DEPLOY_SSH_PRIVATE_KEY" ~/.ssh/id_ed25519
    - chmod 600 ~/.ssh/id_ed25519
    - ssh-keyscan -H "$DEPLOY_HOST" >> ~/.ssh/known_hosts
  script:
    - test -n "$DEPLOY_HOST"
    - test -n "$DEPLOY_USER"
    - test -n "$DEPLOY_COMPOSE_FILE"
    - IMAGE_ARCHIVE="call-center-app-${CI_COMMIT_SHORT_SHA}.tar.gz"
    - docker build -t "${APP_IMAGE_NAME}:${CI_COMMIT_SHORT_SHA}" -t "${APP_IMAGE_NAME}:active-main" -t "${APP_IMAGE_NAME}:latest" -t "${APP_IMAGE_NAME}:ai-orchestrator" -t "${APP_IMAGE_NAME}:streaming-asr" -t "${APP_IMAGE_NAME}:asterisk-bridge" -t "${APP_IMAGE_NAME}:reporting-supervisor" .
    - docker save "${APP_IMAGE_NAME}:${CI_COMMIT_SHORT_SHA}" "${APP_IMAGE_NAME}:active-main" "${APP_IMAGE_NAME}:latest" "${APP_IMAGE_NAME}:ai-orchestrator" "${APP_IMAGE_NAME}:streaming-asr" "${APP_IMAGE_NAME}:asterisk-bridge" "${APP_IMAGE_NAME}:reporting-supervisor" | gzip > "$IMAGE_ARCHIVE"
    - |
      DEPLOY_COMPOSE_DIR="${DEPLOY_COMPOSE_FILE%/*}"
      if [ "$DEPLOY_COMPOSE_DIR" = "$DEPLOY_COMPOSE_FILE" ]; then
        DEPLOY_COMPOSE_DIR="."
      fi
      DEPLOY_SOURCE_ROOT="${DEPLOY_PATH:-}"
      if [ -z "$DEPLOY_SOURCE_ROOT" ]; then
        DEPLOY_SOURCE_ROOT="$(ssh "${DEPLOY_USER}@${DEPLOY_HOST}" "
          set -e
          compose_dir='$DEPLOY_COMPOSE_DIR'
          parent_dir=\$(dirname \"\$compose_dir\")
          if [ -f \"\$parent_dir/call-center/.env.production\" ]; then
            printf '%s' \"\$parent_dir/call-center\"
          elif [ -f \"\$parent_dir/.env.production\" ]; then
            printf '%s' \"\$parent_dir\"
          else
            printf '%s' \"\$parent_dir\"
          fi
        ")"
      fi
      DEPLOY_SOURCE_COMPOSE_DIR="$DEPLOY_SOURCE_ROOT/deployment"
      DEPLOY_SOURCE_COMPOSE_FILE="$DEPLOY_SOURCE_COMPOSE_DIR/docker-compose.server.yml"
      ssh "${DEPLOY_USER}@${DEPLOY_HOST}" "mkdir -p '$DEPLOY_SOURCE_COMPOSE_DIR'"
      scp -r deployment/. "${DEPLOY_USER}@${DEPLOY_HOST}:$DEPLOY_SOURCE_COMPOSE_DIR/"
    - scp "$IMAGE_ARCHIVE" "${DEPLOY_USER}@${DEPLOY_HOST}:/tmp/$IMAGE_ARCHIVE"
    - |
      ssh "${DEPLOY_USER}@${DEPLOY_HOST}" "
        set -e
        gunzip -c /tmp/$IMAGE_ARCHIVE | sudo docker load
        rm -f /tmp/$IMAGE_ARCHIVE
        test -f '$DEPLOY_SOURCE_ROOT/.env.production'
        for container in \$(sudo docker ps -aq --filter publish=9019); do sudo docker rm -f \"\$container\"; done
        sudo docker rm -f call-center-ai-voice-runtime-service-1 >/dev/null 2>&1 || true
        sudo env CALL_CENTER_IMAGE='${APP_IMAGE_NAME}:active-main' APP_IMAGE='${APP_IMAGE_NAME}:active-main' docker compose -f '$DEPLOY_SOURCE_COMPOSE_FILE' config -q
        sudo env CALL_CENTER_IMAGE='${APP_IMAGE_NAME}:active-main' APP_IMAGE='${APP_IMAGE_NAME}:active-main' docker compose -f '$DEPLOY_SOURCE_COMPOSE_FILE' up -d --no-build --remove-orphans --force-recreate
        if sudo docker ps -a --format '{{.Names}}' | grep -Fxq 'call-center-ai-voice-runtime-service-1'; then
          sudo docker inspect --format '{{range .Config.Env}}{{println .}}{{end}}' call-center-ai-voice-runtime-service-1 > /tmp/call-center-ai-voice-runtime.env
          sudo docker rm -f call-center-ai-voice-runtime-service-1
          sudo docker run -d --name call-center-ai-voice-runtime-service-1 --restart unless-stopped --network call-center_default --network-alias ai-voice-runtime-service -v '$DEPLOY_SOURCE_ROOT/.data_local:/app/.data_local' --add-host host.docker.internal:host-gateway --env-file /tmp/call-center-ai-voice-runtime.env call-center-app:active-main sh -c 'uvicorn \${APP_MODULE} --host 0.0.0.0 --port 8000'
          rm -f /tmp/call-center-ai-voice-runtime.env
        fi
        sudo docker image prune -f --filter dangling=true
        sudo docker ps --filter 'name=^call-center-app$' --format 'table {{.Names}}\t{{.Image}}\t{{.Status}}'
      "
  rules:
    - if: '$CI_PIPELINE_SOURCE == "push" && $CI_COMMIT_BRANCH == $CI_DEFAULT_BRANCH'
    - if: '$CI_PIPELINE_SOURCE == "web" && $CI_COMMIT_BRANCH == $CI_DEFAULT_BRANCH'
  allow_failure: false

deploy-sales-source:
  stage: deploy
  tags:
    - call-center-prod
  needs:
    - test
    - job: postgres-readiness
      optional: true
  variables:
    DOCKER_HOST: ""
    DOCKER_TLS_VERIFY: ""
    DOCKER_CERT_PATH: ""
    DOCKER_TLS_CERTDIR: ""
    APP_IMAGE_NAME: call-center-app
    SALES_APP_IMAGE_TAG: active-sales
    SALES_DEPLOY_COMPOSE_FILE: /opt/konturai-migration/sales-call-center/docker-compose.yml
  environment:
    name: sales
    url: https://sales.konturai.kz
  before_script:
    - command -v docker >/dev/null
    - command -v ssh >/dev/null
    - command -v scp >/dev/null
    - command -v gzip >/dev/null
    - mkdir -p ~/.ssh
    - chmod 700 ~/.ssh
    - cp "$DEPLOY_SSH_PRIVATE_KEY" ~/.ssh/id_ed25519
    - chmod 600 ~/.ssh/id_ed25519
    - ssh-keyscan -H "$DEPLOY_HOST" >> ~/.ssh/known_hosts
  script:
    - test -n "$DEPLOY_HOST"
    - test -n "$DEPLOY_USER"
    - test -n "$SALES_DEPLOY_COMPOSE_FILE"
    - IMAGE_ARCHIVE="call-center-app-sales-${CI_COMMIT_SHORT_SHA}.tar.gz"
    - docker build -t "${APP_IMAGE_NAME}:sales-${CI_COMMIT_SHORT_SHA}" -t "${APP_IMAGE_NAME}:${SALES_APP_IMAGE_TAG}" .
    - docker save "${APP_IMAGE_NAME}:sales-${CI_COMMIT_SHORT_SHA}" "${APP_IMAGE_NAME}:${SALES_APP_IMAGE_TAG}" | gzip > "$IMAGE_ARCHIVE"
    - scp "$IMAGE_ARCHIVE" "${DEPLOY_USER}@${DEPLOY_HOST}:/tmp/$IMAGE_ARCHIVE"
    - |
      ssh "${DEPLOY_USER}@${DEPLOY_HOST}" "
        set -e
        SALES_COMPOSE_FILE='$SALES_DEPLOY_COMPOSE_FILE'
        SALES_COMPOSE_DIR=\$(dirname \"\$SALES_COMPOSE_FILE\")
        test -f \"\$SALES_COMPOSE_FILE\"
        test -f \"\$SALES_COMPOSE_DIR/.env.production\"
        gunzip -c /tmp/$IMAGE_ARCHIVE | sudo docker load
        rm -f /tmp/$IMAGE_ARCHIVE
        sudo env CALL_CENTER_IMAGE='${APP_IMAGE_NAME}:${SALES_APP_IMAGE_TAG}' docker compose -f \"\$SALES_COMPOSE_FILE\" config -q
        sudo env CALL_CENTER_IMAGE='${APP_IMAGE_NAME}:${SALES_APP_IMAGE_TAG}' docker compose -f \"\$SALES_COMPOSE_FILE\" run --rm migrate
        sudo env CALL_CENTER_IMAGE='${APP_IMAGE_NAME}:${SALES_APP_IMAGE_TAG}' docker compose -f \"\$SALES_COMPOSE_FILE\" up -d --no-build --remove-orphans --force-recreate
        sudo docker image prune -f --filter dangling=true
        sudo docker ps --filter 'name=^sales-call-center-app$' --format 'table {{.Names}}\t{{.Image}}\t{{.Status}}'
      "
  rules:
    - if: '$CI_PIPELINE_SOURCE == "push" && $CI_COMMIT_BRANCH == "sales"'
    - if: '$CI_PIPELINE_SOURCE == "web" && $CI_COMMIT_BRANCH == "sales"'
  allow_failure: false

hotfix-analyst-assets:
  stage: deploy
  tags:
    - call-center-prod
  variables:
    DOCKER_HOST: ""
    DOCKER_TLS_VERIFY: ""
    DOCKER_CERT_PATH: ""
    DOCKER_TLS_CERTDIR: ""
  needs:
    - test
  script:
    - command -v docker >/dev/null
    - |
      GATEWAY_CONTAINER="$(docker ps --format '{{.Names}}' | grep -E '^(call-center-app|call-center-api-gateway($|[-_]))' | head -n1)"
      test -n "$GATEWAY_CONTAINER"
      echo "Using gateway container: $GATEWAY_CONTAINER"
      docker exec "$GATEWAY_CONTAINER" sh -lc 'mkdir -p /app/gateway /app/ui/analyst /app/ui/operator'
      docker cp gateway/app.py "$GATEWAY_CONTAINER:/app/gateway/app.py"
      docker cp ui/analyst/index.html "$GATEWAY_CONTAINER:/app/ui/analyst/index.html"
      docker cp ui/analyst/app.js "$GATEWAY_CONTAINER:/app/ui/analyst/app.js"
      docker cp ui/analyst/mock-analytics.json "$GATEWAY_CONTAINER:/app/ui/analyst/mock-analytics.json"
      docker cp ui/operator/styles.css "$GATEWAY_CONTAINER:/app/ui/operator/styles.css"
      docker restart "$GATEWAY_CONTAINER"
  rules:
    - if: '$CI_COMMIT_BRANCH == $CI_DEFAULT_BRANCH'
      when: manual
    - when: never
  allow_failure: false

prune-demo-data-production:
  stage: deploy
  tags:
    - call-center-prod
  needs:
    - deploy-production-source
  variables:
    DOCKER_HOST: ""
    DOCKER_TLS_VERIFY: ""
    DOCKER_CERT_PATH: ""
    DOCKER_TLS_CERTDIR: ""
    DEPLOY_DIR: /home/gitlab-runner/deploy/call-center
    KEEP_LATEST_INTERACTIONS: "6"
  script:
    - command -v python3 >/dev/null
    - test -d "$DEPLOY_DIR"
    - test -f "$DEPLOY_DIR/.env.production"
    - mkdir -p "$DEPLOY_DIR/.db_backups"
    - python3 scripts/prune_demo_interactions.py --env-file "$DEPLOY_DIR/.env.production" --base-dir "$DEPLOY_DIR" --backup-dir "$DEPLOY_DIR/.db_backups" --keep-latest-interactions "$KEEP_LATEST_INTERACTIONS"
  rules:
    - if: '$CI_COMMIT_BRANCH == $CI_DEFAULT_BRANCH'
      when: manual
    - when: never
  allow_failure: false
