#!/usr/bin/python
# -*- coding: utf-8 -*-
# (c) 2016, Eric D Helms <ericdhelms@gmail.com>
#
# This program 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.
#
# This program 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 this program.  If not, see <http://www.gnu.org/licenses/>.

from __future__ import absolute_import, division, print_function
__metaclass__ = type


DOCUMENTATION = '''
---
module: content_view
version_added: 1.0.0
short_description: Manage Content Views
description:
    - Create and manage content views
author: "Eric D Helms (@ehelms)"
options:
  name:
    description:
      - Name of the Content View
    required: true
    type: str
  description:
    description:
      - Description of the Content View
    type: str
  label:
    description:
      - Label of the Content View. This field cannot be updated.
    type: str
  repositories:
    description:
      - List of repositories that include name and product.
      - Cannot be combined with I(composite=True).
    type: list
    elements: dict
    suboptions:
      name:
        description:
          - Name of the Repository to be added
        type: str
        required: true
      product:
        description:
          - Product of the Repository to be added
        type: str
        required: true
  auto_publish:
    description:
      - Auto publish composite view when a new version of a component content view is created.
      - Also note auto publish will only happen when the component is marked "latest".
    default: false
    type: bool
  solve_dependencies:
    description:
      - Solve RPM dependencies by default on Content View publish
    type: bool
  import_only:
    description:
      - Designate this Content View for importing from upstream servers only.
    type: bool
    version_added: 3.14.0
  composite:
    description:
      - A composite view contains other content views.
    default: false
    type: bool
  components:
    description:
      - List of content views to includes content_view and either version or latest.
      - Ignored if I(composite=False).
    type: list
    elements: dict
    suboptions:
      content_view:
        description:
          - Content View name to be added to the Composite Content View
        type: str
        required: true
      latest:
        description:
          - Always use the latest Content View Version
        type: bool
        default: false
      content_view_version:
        description:
          - Version of the Content View to add
        type: str
        aliases:
          - version
extends_documentation_fragment:
  - theforeman.foreman.foreman
  - theforeman.foreman.foreman.entity_state_with_defaults
  - theforeman.foreman.foreman.organization
'''

EXAMPLES = '''
- name: "Create or update Fedora content view"
  theforeman.foreman.content_view:
    username: "admin"
    password: "changeme"
    server_url: "https://foreman.example.com"
    name: "Fedora CV"
    organization: "My Cool new Organization"
    repositories:
      - name: 'Fedora 26'
        product: 'Fedora'

- name: "Create a composite content view"
  theforeman.foreman.content_view:
    username: "admin"
    password: "changeme"
    server_url: "https://foreman.example.com"
    name: "Fedora CCV"
    organization: "My Cool new Organization"
    composite: true
    auto_publish: true
    components:
      - content_view: Fedora CV
        content_view_version: 1.0
      - content_view: Internal CV
        latest: true
'''

RETURN = '''
entity:
  description: Final state of the affected entities grouped by their type.
  returned: success
  type: dict
  contains:
    content_views:
      description: List of content views.
      type: list
      elements: dict
'''

import copy
from ansible_collections.theforeman.foreman.plugins.module_utils.foreman_helper import KatelloEntityAnsibleModule


cvc_foreman_spec = {
    'id': {'invisible': True},
    'content_view': {'type': 'entity', 'required': True},
    'latest': {'type': 'bool', 'default': False},
    'content_view_version': {'type': 'entity', 'aliases': ['version']},
}


class KatelloContentViewModule(KatelloEntityAnsibleModule):
    pass


def main():
    module = KatelloContentViewModule(
        foreman_spec=dict(
            name=dict(required=True),
            description=dict(),
            label=dict(),
            composite=dict(type='bool', default=False),
            auto_publish=dict(type='bool', default=False),
            solve_dependencies=dict(type='bool'),
            import_only=dict(type='bool'),
            components=dict(type='nested_list', foreman_spec=cvc_foreman_spec, resolve=False),
            repositories=dict(type='entity_list', elements='dict', resolve=False, options=dict(
                name=dict(required=True),
                product=dict(required=True),
            )),
        ),
        argument_spec=dict(
            state=dict(default='present', choices=['present_with_defaults', 'present', 'absent']),
        ),
        mutually_exclusive=[['repositories', 'components']],
        entity_opts=dict(thin=False),
    )

    # components is None when we're managing a CCV but don't want to adjust its components
    components = module.foreman_params.pop('components', None)
    if components:
        for component in components:
            if not component['latest'] and component.get('content_view_version') is None:
                module.fail_json(msg="Content View Component must either have latest=True or provide a Content View Version.")

    with module.api_connection():
        entity = module.lookup_entity('entity')
        scope = module.scope_for('organization')

        if not module.desired_absent:
            if 'repositories' in module.foreman_params:
                if module.foreman_params['composite']:
                    module.fail_json(msg="Repositories cannot be parts of a Composite Content View.")
                else:
                    repositories = []
                    for repository in module.foreman_params['repositories']:
                        product = module.find_resource_by_name('products', repository['product'], params=scope, thin=True)
                        repositories.append(module.find_resource_by_name('repositories', repository['name'], params={'product_id': product['id']}, thin=True))
                    module.foreman_params['repositories'] = repositories

        if entity and module.desired_absent:
            for lce in entity.get('environments', []):
                module.resource_action('content_views', 'remove_from_environment', {'id': entity['id'], 'environment_id': lce['id']})

        content_view_entity = module.run()

        # only update CVC's of newly created or updated CV's that are composite if components are specified
        update_dependent_entities = (module.state == 'present' or (module.state == 'present_with_defaults' and module.changed))
        if update_dependent_entities and content_view_entity['composite'] and components is not None:
            if not module.changed:
                content_view_entity['content_view_components'] = entity['content_view_components']
            current_cvcs = content_view_entity.get('content_view_components', [])

            # only record a subset of data
            current_cvcs_record = []
            for cvc in current_cvcs:
                entry = {"id": cvc['id'], "content_view_id": cvc['content_view']['id'], "latest": cvc['latest']}
                if 'content_view_version' in cvc and isinstance(cvc['content_view_version'], dict):
                    entry['content_view_version_id'] = cvc['content_view_version'].get('id')
                current_cvcs_record.append(entry)
            module.record_before('content_views/components', {'composite_content_view_id': content_view_entity['id'],
                                                              'content_view_components': current_cvcs_record})
            final_cvcs_record = copy.deepcopy(current_cvcs_record)

            components_to_add = []
            ccv_scope = {'composite_content_view_id': content_view_entity['id']}
            for component in components:
                cvc = {
                    'content_view': module.find_resource_by_name('content_views', name=component['content_view'], params=scope),
                    'latest': component['latest'],
                }
                cvc_matched = next((item for item in current_cvcs if item['content_view']['id'] == cvc['content_view']['id']), None)
                if not cvc['latest']:
                    search = "content_view_id={0},version={1}".format(cvc['content_view']['id'], component['content_view_version'])
                    cvc['content_view_version'] = module.find_resource('content_view_versions', search=search, thin=True)
                    cvc['latest'] = False
                    if cvc_matched and cvc_matched['latest']:
                        # When changing to latest=False & version is the latest we must send 'content_view_version' to the server
                        # Let's fake, it wasn't there...
                        cvc_matched.pop('content_view_version', None)
                        cvc_matched.pop('content_view_version_id', None)
                if cvc_matched:
                    module.ensure_entity(
                        'content_view_components', cvc, cvc_matched, state='present', foreman_spec=cvc_foreman_spec, params=ccv_scope)
                    current_cvcs.remove(cvc_matched)
                else:
                    cvc['content_view_id'] = cvc.pop('content_view')['id']
                    if 'content_view_version' in cvc:
                        cvc['content_view_version_id'] = cvc.pop('content_view_version')['id']
                    components_to_add.append(cvc)

            if components_to_add:
                payload = {
                    'composite_content_view_id': content_view_entity['id'],
                    'components': components_to_add,
                }
                module.resource_action('content_view_components', 'add_components', payload)

                final_cvcs_record.extend(components_to_add)

            # desired cvcs have already been updated and removed from `current_cvcs`
            components_to_remove = [item['id'] for item in current_cvcs]
            if components_to_remove:
                payload = {
                    'composite_content_view_id': content_view_entity['id'],
                    'component_ids': components_to_remove,
                }
                module.resource_action('content_view_components', 'remove_components', payload)

                # some entries in "final" don't have an id yet, as it is only assigned on creation of a cv component,
                # which didn't happen yet when we record the data
                final_cvcs_record = [item for item in final_cvcs_record if item.get('id', 'NEW_ID') not in components_to_remove]

            module.record_after('content_views/components', {'composite_content_view_id': content_view_entity['id'],
                                                             'content_view_components': final_cvcs_record})
            module.record_after_full('content_views/components', {'composite_content_view_id': content_view_entity['id'],
                                                                  'content_view_components': final_cvcs_record})


if __name__ == '__main__':
    main()
