# Copyright 2019-2024 Chris Croome
#
# This file is part of the Webarchitects PHP Ansible role.
#
# The Webarchitects PHP Ansible role is free software: you can redistribute it and/or modify it under the terms of the GNU General Public License as published by the Free Software Foundation, either version 3 of the License, or (at your option) any later version.
#
# The Webarchitects PHP Ansible role is distributed in the hope that it will be useful, but WITHOUT ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU General Public License for more details.
#
# You should have received a copy of the GNU General Public License along with the Webarchitects PHP Ansible role. If not, see <https://www.gnu.org/licenses/>.
---
- name: PHP configuration file edited
  block:

    - name: Slurp existing PHP configuration file
      ansible.builtin.slurp:
        path: "{{ php_conf_file }}"
      register: php_conf_file_b64encoded

    - name: Set a fact for the existing PHP configuration file variables
      ansible.builtin.set_fact:
        php_conf_file_existing_vars: "{{ php_conf_file_b64encoded['content'] | ansible.builtin.b64decode | community.general.jc('ini') }}"
        php_conf_file_no_extra_spaces: "{%- if (php_conf_file | ansible.builtin.dirname | ansible.builtin.split('/') | last) == 'mods-available' -%}true{%- else -%}false{%- endif -%}"

    - name: Debug the existing PHP configuration file variables
      ansible.builtin.debug:
        var: php_conf_file_existing_vars
        verbosity: "{% if ansible_check_mode | bool %}1{% else %}2{% endif %}"

    - name: Debug the PHP configuration no extra spaces variable
      ansible.builtin.debug:
        var: php_conf_file_no_extra_spaces
        verbosity: "{% if ansible_check_mode | bool %}1{% else %}2{% endif %}"

    - name: Set a fact for the existing PHP configuration file sections
      block:

        - name: Set a fact to indicate that the existing PHP configuration file has sections
          ansible.builtin.set_fact:
            php_conf_file_existing_sections: >-
              {%- if (php_conf_file_existing_vars |
              ansible.builtin.json_query('*[]') |
              map('ansible.builtin.type_debug') |
              unique)[0] == "dict"
              -%}true{%- else -%}false{%- endif -%}

        - name: Debug the existing PHP configuration file sections variables
          ansible.builtin.debug:
            var: php_conf_file_existing_sections
            verbosity: "{% if ansible_check_mode | bool %}1{% else %}2{% endif %}"

        - name: Debug the existing PHP configuration file sections
          ansible.builtin.debug:
            var: php_conf_file_existing_vars.keys()
            verbosity: "{% if ansible_check_mode | bool %}1{% else %}2{% endif %}"
          when: php_conf_file_existing_sections | bool

      when: php_conf_file_existing_vars | length > 0

    - name: Set a fact for the proposed PHP configuration file variables
      ansible.builtin.set_fact:
        php_conf_file_proposed_vars: "{{ php_config | community.general.json_query(php_conf_file_proposed_vars_json_query) }}"
      vars:
        php_conf_file_proposed_vars_json_query: "[?state=='present'].files[]|[?path=='{{ php_conf_file }}'].conf|[0]"

    - name: Debug the proposed PHP configuration file variables
      ansible.builtin.debug:
        var: php_conf_file_proposed_vars
        verbosity: "{% if ansible_check_mode | bool %}1{% else %}2{% endif %}"

    - name: The proposed PHP configuration file variables are empty or a dictionary
      ansible.builtin.assert:
        that:
          - ( php_conf_file_proposed_vars | length == 0 ) or ( php_conf_file_proposed_vars | ansible.builtin.type_debug == "dict" )
        quiet: "{% if ansible_verbosity == 0 %}true{% else %}false{% endif %}"
        fail_msg: >-
          The php_conf_file_proposed_vars variable should be empty or be a dictionary, neither appears to be the case, it is set to
          {{ php_conf_file_proposed_vars }}

    - name: Set a fact for the absent PHP configuration file variables
      ansible.builtin.set_fact:
        php_conf_file_absent_vars: "{{ php_config | community.general.json_query(php_conf_file_absent_vars_json_query) }}"
      vars:
        php_conf_file_absent_vars_json_query: "[?state=='present'].files[]|[?path=='{{ php_conf_file }}'].conf_absent|[0]"

    - name: Debug the absent PHP configuration file variables
      ansible.builtin.debug:
        var: php_conf_file_absent_vars
        verbosity: "{% if ansible_check_mode | bool %}1{% else %}2{% endif %}"

    - name: The absent PHP configuration file variables are empty or a dictionary
      ansible.builtin.assert:
        that:
          - ( php_conf_file_absent_vars | length == 0 ) or ( php_conf_file_absent_vars | ansible.builtin.type_debug == "dict" )
        quiet: "{% if ansible_verbosity == 0 %}true{% else %}false{% endif %}"
        fail_msg: >-
          The php_conf_file_absent_vars variable should be empty or be a dictionary, neither appears to be the case, it is set to
          {{ php_conf_file_absent_vars }}

    - name: Set a fact for the proposed PHP configuration file sections
      block:

        - name: Set a fact to indicate that the proposed PHP configuration file has sections
          ansible.builtin.set_fact:
            php_conf_file_proposed_sections: >-
              {%- if (php_conf_file_proposed_vars |
              ansible.builtin.json_query('*[]') |
              map('ansible.builtin.type_debug') |
              unique)[0] == "dict"
              -%}true{%- else -%}false{%- endif -%}

        - name: Debug the proposed PHP configuration file sections variables
          ansible.builtin.debug:
            var: php_conf_file_proposed_sections
            verbosity: "{% if ansible_check_mode | bool %}1{% else %}2{% endif %}"

        - name: Debug the proposed PHP configuration file sections
          ansible.builtin.debug:
            var: php_conf_file_proposed_vars.keys()
            verbosity: "{% if ansible_check_mode | bool %}1{% else %}2{% endif %}"
          when: php_conf_file_proposed_sections | bool

        - name: When the existing files has sections and proposed config also has sections
          ansible.builtin.assert:
            that:
              - php_conf_file_existing_sections == php_conf_file_proposed_sections
            quiet: "{% if ansible_verbosity == 0 %}true{% else %}false{% endif %}"
            fail_msg: >-
              The php_conf_file_existing_sections and php_conf_file_proposed_sections variables should match
              when they are defined, they don't, they are set to {{ php_conf_file_existing_sections }} and
              {{ php_conf_file_proposed_sections }}
          when: php_conf_file_existing_vars | length > 0

      when: php_conf_file_proposed_vars | length > 0

    - name: Set facts for the PHP configuration file
      ansible.builtin.set_fact:
        php_conf_file_backup: "{{ php_conf_file | ansible.builtin.dirname }}/.{{ php_conf_file | ansible.builtin.basename }}.{{ ansible_date_time.iso8601_basic_short }}.bak"
        php_conf_file_changed: false
        php_conf_file_sapi: "{{ php_conf_file | ansible.builtin.split(php_file_path_separator) | community.general.json_query('[4]') }}"
        php_conf_file_version: "{{ php_conf_file | ansible.builtin.split(php_file_path_separator) | community.general.json_query('[3]') }}"

    - name: Debug the PHP configuration file backup path
      ansible.builtin.debug:
        var: php_conf_file_backup
        verbosity: "{% if ansible_check_mode | bool %}1{% else %}2{% endif %}"

    - name: Debug the PHP configuration file PHP SAPI
      ansible.builtin.debug:
        var: php_conf_file_sapi
        verbosity: "{% if ansible_check_mode | bool %}1{% else %}2{% endif %}"

    - name: Debug the PHP configuration file PHP version
      ansible.builtin.debug:
        var: php_conf_file_version
        verbosity: "{% if ansible_check_mode | bool %}1{% else %}2{% endif %}"

    - name: File backup present
      ansible.builtin.copy:
        src: "{{ php_conf_file }}"
        dest: "{{ php_conf_file_backup }}"
        remote_src: true
        mode: "0644"
        owner: root
        group: root
      changed_when: false

    - name: Include the tasks to edit the section-less PHP configuration file
      ansible.builtin.include_tasks: file_edited_present.yml
      when:
        - php_conf_file_proposed_vars is defined
        - php_conf_file_proposed_vars | length > 0
        - php_conf_file_proposed_vars | ansible.builtin.type_debug == "dict"
        - not php_conf_file_existing_sections | bool
        - not php_conf_file_proposed_sections | bool

    - name: Include the tasks to remove config from the section-less PHP configuration file
      ansible.builtin.include_tasks: file_edited_absent.yml
      when:
        - php_conf_file_absent_vars is defined
        - php_conf_file_absent_vars | length > 0
        - php_conf_file_absent_vars | ansible.builtin.type_debug == "dict"
        - not php_conf_file_existing_sections | bool
        - not php_conf_file_proposed_sections | bool

    - name: Include the PHP configuration file sections config edited tasks
      ansible.builtin.include_tasks: file_sections_edited.yml
      when:
        - php_conf_file_proposed_vars is defined
        - php_conf_file_proposed_vars | length > 0
        - php_conf_file_proposed_vars | ansible.builtin.type_debug == "dict"
        - php_conf_file_existing_sections | bool
        - php_conf_file_proposed_sections | bool

    - name: Include the PHP configuration file sections config absent tasks
      ansible.builtin.include_tasks: file_sections_absent.yml
      when:
        - php_conf_file_absent_vars is defined
        - php_conf_file_absent_vars | length > 0
        - php_conf_file_absent_vars | ansible.builtin.type_debug == "dict"
        - php_conf_file_existing_sections | bool
        - php_conf_file_proposed_sections | bool

    - name: Ansible managed comment present at the top of the file
      ansible.builtin.lineinfile:
        path: "{{ php_conf_file }}"
        line: "; {{ php_ansible_managed }}"
        state: present
        insertbefore: BOF
        mode: "0644"
        owner: root
        group: root

    - name: Vim syntaxhighlighting modeline present at the end of the file
      ansible.builtin.lineinfile:
        path: "{{ php_conf_file }}"
        line: "; vim: syntax=dosini"
        state: present
        insertafter: EOF
        mode: "0644"
        owner: root
        group: root

    - name: Test and reload PHP configuration file when file is in a FPM directory and the init system is systemd
      block:

        - name: Test PHP configuration
          block:

            - name: PHP FPM configtest
              ansible.builtin.command: "php-fpm{{ php_conf_file_version }} --test"
              check_mode: false
              changed_when: false
              register: php_fpm_test
              failed_when: >-
                ( php_fpm_test.rc != 0 ) or
                ( "Failed" in php_fpm_test.stderr )

          rescue:

            - name: Copy broken PHP configuration file
              ansible.builtin.copy:
                src: "{{ php_conf_file }}"
                dest: "{{ php_conf_file_backup }}.broken"
                remote_src: true
                mode: "0644"
                owner: root
                group: root

            - name: Check if a backup files exists
              ansible.builtin.stat:
                path: "{{ php_conf_file_backup }}"
              register: php_conf_file_backup_path

            - name: Copy backup file over edited file as the configuration test failed
              ansible.builtin.copy:
                src: "{{ php_conf_file_backup }}"
                dest: "{{ php_conf_file }}"
                remote_src: true
                mode: "0644"
                owner: root
                group: root
              when: php_conf_file_backup_path.stat.exists | bool

            - name: Debug PHP configuration file test failure
              ansible.builtin.debug:
                var: php_fpm_test.stderr_lines

            - name: Fail as there was a problem with the updated configuration file
              ansible.builtin.fail:
                msg: "The original configuration has been restored and the broken file is available at {{ php_conf_file_backup }}.broken"

        - name: PHP FPM reloaded
          ansible.builtin.systemd_service:
            name: "php{{ php_conf_file_version }}-fpm"
            state: reloaded

        - name: Check that PHP FPM is running
          ansible.builtin.service_facts:
          register: php_service_facts
          until: php_service_facts | community.general.json_query(php_service_jpq) == "running"
          retries: 20
          delay: 1
          vars:
            php_service_jpq: 'ansible_facts.services.["php{{ php_conf_file_version }}-fpm.service"]|[0].state'

      when:
        - php_init is defined
        - php_init == "systemd"
        - php_conf_file_version in php_ver_installed
        - php_conf_file_changed | bool
        - php_conf_file_sapi is defined
        - php_conf_file_sapi == "fpm"

    - name: File backup absent when the PHP configuration file is unchanged
      ansible.builtin.file:
        path: "{{ php_conf_file_backup }}"
        state: absent
      changed_when: false
      when: not php_conf_file_changed | bool

  tags:
    - php
    - php_cfg
    - php_conf
...