---
# Gravity Platform Installation Playbook
# Installs core platform services from DOCR (DigitalOcean Container Registry)
#
# Usage:
#   ansible-playbook -i inventory/production.yml playbooks/install.yml
#
# Or via gravity CLI (reads .env.production automatically):
#   unoverse deploy
#
# Prerequisites:
#   - .env.production configured with DEPLOY_HOST, DOCR_TOKEN, DATABASE_URL, etc.
#   - (Legacy) ansible/files/.env also supported for backwards compatibility
#
# This playbook installs CORE PLATFORM only (from DOCR images).
# For customer packages (custom nodes, design system), run deploy-packages.yml

- name: Install Gravity Platform
  hosts: all
  become: yes

  vars:
    gravity_dir: /opt/gravity
    core_services: "unoverse memory canvas umap documents"  # server + workflow + mcp-server retired — unoverse serves :4101 (engine in-process), the public :4105 API, and native MCP (:4105/mcp, :4106/mcp internal)

  pre_tasks:
    - name: "[1/10] Check current system"
      debug:
        msg: "Installing Gravity Platform on {{ inventory_hostname }} ({{ ansible_host }})"

    - name: "[2/10] Install Docker via convenience script"
      shell: |
        if ! command -v docker &> /dev/null; then
          echo "Installing Docker..."
          curl -fsSL https://get.docker.com | sh
        else
          echo "Docker already installed"
        fi
      args:
        creates: /usr/bin/docker
      register: docker_install

    - name: "[2/10] Docker install output"
      debug:
        msg: "{{ docker_install.stdout_lines | default(['Docker already present']) }}"

    - name: "[3/10] Start Docker service"
      service:
        name: docker
        state: started
        enabled: yes

    - name: "[3/10] Verify Docker running"
      command: docker --version
      register: docker_version

    - name: "[3/10] Docker version"
      debug:
        msg: "{{ docker_version.stdout }}"

    - name: "[4/10] Add user to docker group"
      user:
        name: "{{ ansible_user }}"
        groups: docker
        append: yes

    - name: "[4.5/10] Install rsync (required for package deployment)"
      package:
        name: rsync
        state: present

  tasks:
    - name: "[5/12] Install Node.js 20 LTS (Debian/Ubuntu)"
      shell: |
        if command -v node &> /dev/null; then
          echo "Node.js already installed: $(node --version)"
        else
          echo "Installing Node.js 20 LTS..."
          curl -fsSL https://deb.nodesource.com/setup_20.x | bash -
          apt-get install -y nodejs
          echo "Installed: $(node --version)"
        fi
      register: node_install
      when: ansible_facts['os_family'] == "Debian"

    - name: "[5/12] Install Node.js 20 LTS (RHEL/Amazon Linux)"
      shell: |
        if command -v node &> /dev/null; then
          echo "Node.js already installed: $(node --version)"
        else
          echo "Installing Node.js 20 LTS..."
          curl -fsSL https://rpm.nodesource.com/setup_20.x | bash -
          dnf install -y nodejs
          echo "Installed: $(node --version)"
        fi
      register: node_install_rhel
      when: ansible_facts['os_family'] == "RedHat"

    - name: "[5/12] Node.js version"
      debug:
        msg: "{{ (node_install.stdout_lines | default(node_install_rhel.stdout_lines | default(['Node.js already present']))) }}"

    - name: "[6/12] Read DOCR token from local .env"
      shell: grep '^DOCR_TOKEN=' {{ env_file | default('../../.env.production') }} | cut -d= -f2
      register: docr_token_result
      delegate_to: localhost
      become: no
      changed_when: false
      ignore_errors: yes

    - name: "[6/12] Set DOCR credentials"
      set_fact:
        docr_token: "{{ docr_token_result.stdout | default('') }}"

    - name: "[7/12] Create gravity directory"
      file:
        path: "{{ gravity_dir }}"
        state: directory
        owner: "{{ ansible_user }}"
        mode: "0755"

    - name: "[7/12] Copy docker-compose.yml"
      copy:
        src: "{{ universe_root | default(playbook_dir + '/../..') }}/docker-compose.yml"
        dest: "{{ gravity_dir }}/docker-compose.yml"
        owner: "{{ ansible_user }}"
        mode: "0644"

    # NO WORKBENCH STEP HERE. This used to force UNOVERSE_WORKBENCH=0 in the deployed
    # compose, back when Studio shipped inside the platform image and had to be switched
    # off for production. Studio is a separate CLI-launched app now and the variable is
    # gone from docker-compose.yml entirely, so the task matched nothing and reported "ok"
    # anyway — a step that proves a safeguard is working while doing nothing at all is
    # worse than no step.
    - name: "[8/12] Copy .env file"
      copy:
        # Same trap as the compose above: a path relative to the playbook is the monorepo's
        # layout, not an installed CLI's. deploy always passes env_file, so this default is
        # a fallback only — anchored to the universe rather than to node_modules.
        src: "{{ env_file | default(universe_root | default(playbook_dir + '/../..') + '/.env.production') }}"
        dest: /opt/gravity/.env
        owner: "{{ ansible_user }}"
        mode: "0600"

    - name: "[9/12] Login to DOCR"
      shell: echo "{{ docr_token }}" | docker login registry.digitalocean.com -u "{{ docr_token }}" --password-stdin
      when: docr_token != ""
      no_log: true
      register: docr_login

    - name: "[9/12] DOCR login status"
      debug:
        msg: "Logged in to DigitalOcean Container Registry"
      when: docr_token != ""

    - name: "[10/12] Pull Gravity images"
      command: docker compose pull --quiet
      args:
        chdir: /opt/gravity
      register: pull_result

    - name: "[10/12] Images pulled"
      debug:
        msg: "Docker images pulled successfully"

    - name: "[11/12] Stop any existing Gravity services"
      command: docker compose down --remove-orphans
      args:
        chdir: "{{ gravity_dir }}"
      ignore_errors: yes

    - name: "[11/12] Start core Gravity services"
      command: docker compose up -d {{ core_services }}
      args:
        chdir: "{{ gravity_dir }}"
      register: start_result

    - name: "[11/12] Services started"
      debug:
        msg: "{{ start_result.stdout_lines | default(['Services starting...']) }}"

  post_tasks:
    # The in-process engine (:4101) initializes AFTER the unoverse server binds :4105 —
    # give it a head start so the :4101 check usually passes first try (less retry noise).
    - name: "Wait for services to initialize"
      pause:
        seconds: 25

    - name: "Health check — Unoverse public (:4105)"
      uri:
        url: "http://localhost:4105/health"
        status_code: 200
      register: unoverse_health
      until: unoverse_health.status == 200
      retries: 30
      delay: 2
      ignore_errors: yes

    - name: "Health check — Engine (in-process, :4101)"
      uri:
        url: "http://localhost:4101/health"
        status_code: 200
      register: engine_health
      until: engine_health.status == 200
      retries: 30
      delay: 2
      ignore_errors: yes

    - name: "Health check — Memory (:4104)"
      uri:
        url: "http://localhost:4104/health"
        status_code: 200
      register: memory_health
      until: memory_health.status == 200
      retries: 30
      delay: 2
      ignore_errors: yes

    - name: "=== DEPLOYMENT SUMMARY ==="
      debug:
        msg: |
          ============================================
          UNOVERSE PLATFORM DEPLOYED
          ============================================
          Host: {{ inventory_hostname }} ({{ ansible_host }})

          Service Health:
            - Unoverse (:4105):  {{ 'OK' if (unoverse_health.status | default(0)) == 200 else 'FAILED' }}
            - Engine   (:4101):  {{ 'OK' if (engine_health.status | default(0)) == 200 else 'FAILED' }}
            - Memory   (:4104):  {{ 'OK' if (memory_health.status | default(0)) == 200 else 'FAILED' }}

          Access URLs:
            - Canvas:    http://{{ ansible_host }}:3001
            - API:       http://{{ ansible_host }}:4105
            - Memory:    http://{{ ansible_host }}:4104/dashboard
          ============================================
