---
# Tailscale Mesh Management
# Validates mesh connectivity, manages keys, and monitors VPN performance
# Run with: ansible-playbook -i hosts.ini playbooks/tailscale_mesh_management.yml

- name: Tailscale Mesh Management
  hosts: all
  gather_facts: yes
  vars:
    tailscale_expected_nodes:
      - "homelab"
      - "atlantis"
      - "calypso"
      - "setillo"
      - "pi-5"
      - "pi-5-kevin"
      - "vish-concord-nuc"
      - "pve"
      - "truenas-scale"
      - "homeassistant"

    performance_test_targets:
      - "100.64.0.1"  # Tailscale coordinator
      - "atlantis"
      - "calypso"

  tasks:
    - name: Check if Tailscale is installed
      command: which tailscale
      register: tailscale_installed
      failed_when: false
      changed_when: false

    - name: Get Tailscale status
      command: tailscale status --json
      register: tailscale_status_raw
      when: tailscale_installed.rc == 0
      become: yes

    - name: Parse Tailscale status
      set_fact:
        tailscale_status: "{{ tailscale_status_raw.stdout | from_json }}"
      when: tailscale_installed.rc == 0 and tailscale_status_raw.stdout != ""

    - name: Get Tailscale IP
      command: tailscale ip -4
      register: tailscale_ip
      when: tailscale_installed.rc == 0
      become: yes

    - name: Display Tailscale node info
      debug:
        msg: |
          Tailscale Status for {{ inventory_hostname }}:
          - Installed: {{ 'Yes' if tailscale_installed.rc == 0 else 'No' }}
          {% if tailscale_installed.rc == 0 %}
          - IP Address: {{ tailscale_ip.stdout }}
          - Backend State: {{ tailscale_status.BackendState }}
          - Version: {{ tailscale_status.Version }}
          - Online: {{ tailscale_status.Self.Online }}
          - Exit Node: {{ tailscale_status.Self.ExitNode | default('None') }}
          {% endif %}

    - name: Get peer information
      set_fact:
        tailscale_peers: "{{ tailscale_status.Peer | dict2items | map(attribute='value') | list }}"
      when: tailscale_installed.rc == 0 and tailscale_status.Peer is defined

    - name: Analyze mesh connectivity
      set_fact:
        online_peers: "{{ tailscale_peers | selectattr('Online', 'equalto', true) | list }}"
        offline_peers: "{{ tailscale_peers | selectattr('Online', 'equalto', false) | list }}"
        expected_missing: "{{ tailscale_expected_nodes | difference(tailscale_peers | map(attribute='HostName') | list + [tailscale_status.Self.HostName]) }}"
      when: tailscale_installed.rc == 0 and tailscale_peers is defined

    - name: Display mesh analysis
      debug:
        msg: |
          Tailscale Mesh Analysis:
          - Total Peers: {{ tailscale_peers | length if tailscale_peers is defined else 0 }}
          - Online Peers: {{ online_peers | length if online_peers is defined else 0 }}
          - Offline Peers: {{ offline_peers | length if offline_peers is defined else 0 }}
          - Expected Nodes: {{ tailscale_expected_nodes | length }}
          - Missing Nodes: {{ expected_missing | length if expected_missing is defined else 0 }}

          {% if offline_peers is defined and offline_peers | length > 0 %}
          Offline Peers:
          {% for peer in offline_peers %}
          - {{ peer.HostName }} ({{ peer.TailscaleIPs[0] }})
          {% endfor %}
          {% endif %}

          {% if expected_missing is defined and expected_missing | length > 0 %}
          Missing Expected Nodes:
          {% for node in expected_missing %}
          - {{ node }}
          {% endfor %}
          {% endif %}
      when: tailscale_installed.rc == 0

    - name: Test connectivity to key nodes
      shell: |
        echo "=== Connectivity Tests ==="
        {% for target in performance_test_targets %}
        echo "Testing {{ target }}..."
        if ping -c 3 -W 2 {{ target }} >/dev/null 2>&1; then
          latency=$(ping -c 3 {{ target }} | tail -1 | awk -F '/' '{print $5}')
          echo "✓ {{ target }}: ${latency}ms avg"
        else
          echo "✗ {{ target }}: Unreachable"
        fi
        {% endfor %}
      register: connectivity_tests
      when: tailscale_installed.rc == 0

    - name: Check Tailscale service status
      systemd:
        name: tailscaled
      register: tailscale_service
      when: tailscale_installed.rc == 0
      become: yes

    - name: Get Tailscale logs
      shell: journalctl -u tailscaled --since "1 hour ago" --no-pager | tail -20
      register: tailscale_logs
      when: tailscale_installed.rc == 0
      become: yes

    - name: Check for Tailscale updates
      shell: |
        current_version=$(tailscale version | head -1 | awk '{print $1}')
        echo "Current version: $current_version"

        # Check if update is available (this is a simplified check)
        if command -v apt >/dev/null 2>&1; then
          apt list --upgradable 2>/dev/null | grep tailscale || echo "No updates available via apt"
        elif command -v yum >/dev/null 2>&1; then
          yum check-update tailscale 2>/dev/null || echo "No updates available via yum"
        else
          echo "Package manager not supported for update check"
        fi
      register: update_check
      when: tailscale_installed.rc == 0
      become: yes

    - name: Generate network performance report
      shell: |
        echo "=== Network Performance Report ==="
        echo "Timestamp: $(date)"
        echo "Host: {{ inventory_hostname }}"
        echo ""

        {% if tailscale_installed.rc == 0 %}
        echo "=== Tailscale Interface ==="
        ip addr show tailscale0 2>/dev/null || echo "Tailscale interface not found"
        echo ""

        echo "=== Route Table ==="
        ip route | grep -E "(tailscale|100\.)" || echo "No Tailscale routes found"
        echo ""

        echo "=== DNS Configuration ==="
        tailscale status --peers=false --self=false 2>/dev/null | grep -E "(DNS|MagicDNS)" || echo "DNS info not available"
        {% else %}
        echo "Tailscale not installed on this host"
        {% endif %}
      register: performance_report
      when: tailscale_installed.rc == 0

    - name: Check exit node configuration
      shell: tailscale status --json | jq -r '.ExitNodeStatus // "No exit node configured"'
      register: exit_node_status
      when: tailscale_installed.rc == 0
      become: yes
      failed_when: false

    - name: Validate Tailscale ACLs (if admin)
      uri:
        url: "https://api.tailscale.com/api/v2/tailnet/{{ tailscale_tailnet | default('example.com') }}/acl"
        method: GET
        headers:
          Authorization: "Bearer {{ tailscale_api_key }}"
      register: acl_check
      when:
        - tailscale_api_key is defined
        - check_acls | default(false) | bool
      delegate_to: localhost
      run_once: true
      failed_when: false

    - name: Generate Tailscale mesh report
      copy:
        content: |
          # Tailscale Mesh Report - {{ inventory_hostname }}
          Generated: {{ ansible_date_time.iso8601 }}

          ## Node Status
          - Tailscale Installed: {{ 'Yes' if tailscale_installed.rc == 0 else 'No' }}
          {% if tailscale_installed.rc == 0 %}
          - IP Address: {{ tailscale_ip.stdout }}
          - Backend State: {{ tailscale_status.BackendState }}
          - Version: {{ tailscale_status.Version }}
          - Online: {{ tailscale_status.Self.Online }}
          - Service Status: {{ tailscale_service.status.ActiveState }}
          {% endif %}

          {% if tailscale_peers is defined %}
          ## Mesh Connectivity
          - Total Peers: {{ tailscale_peers | length }}
          - Online Peers: {{ online_peers | length }}
          - Offline Peers: {{ offline_peers | length }}

          ### Online Peers
          {% for peer in online_peers %}
          - {{ peer.HostName }} ({{ peer.TailscaleIPs[0] }}) - Last Seen: {{ peer.LastSeen }}
          {% endfor %}

          {% if offline_peers | length > 0 %}
          ### Offline Peers
          {% for peer in offline_peers %}
          - {{ peer.HostName }} ({{ peer.TailscaleIPs[0] }}) - Last Seen: {{ peer.LastSeen }}
          {% endfor %}
          {% endif %}
          {% endif %}

          ## Connectivity Tests
          ```
          {{ connectivity_tests.stdout if connectivity_tests is defined else 'Not performed' }}
          ```

          ## Performance Report
          ```
          {{ performance_report.stdout if performance_report is defined else 'Not available' }}
          ```

          ## Recent Logs
          ```
          {{ tailscale_logs.stdout if tailscale_logs is defined else 'Not available' }}
          ```

          ## Update Status
          ```
          {{ update_check.stdout if update_check is defined else 'Not checked' }}
          ```
        dest: "/tmp/tailscale_mesh_{{ inventory_hostname }}_{{ ansible_date_time.epoch }}.md"
      delegate_to: localhost

    - name: Display mesh summary
      debug:
        msg: |
          Tailscale Mesh Summary for {{ inventory_hostname }}:
          - Status: {{ 'Connected' if tailscale_installed.rc == 0 and tailscale_status.BackendState == 'Running' else 'Disconnected' }}
          - IP: {{ tailscale_ip.stdout if tailscale_installed.rc == 0 else 'N/A' }}
          - Peers: {{ tailscale_peers | length if tailscale_peers is defined else 0 }}
          - Report: /tmp/tailscale_mesh_{{ inventory_hostname }}_{{ ansible_date_time.epoch }}.md
