#!/usr/bin/python

# Copyright (c) 2019, Nurfet Becirevic <nurfet.becirevic@gmail.com>
# Copyright (c) 2017, Tomas Karasek <tom.to.the.k@gmail.com>
# GNU General Public License v3.0+ (see LICENSES/GPL-3.0-or-later.txt or https://www.gnu.org/licenses/gpl-3.0.txt)
# SPDX-License-Identifier: GPL-3.0-or-later

from __future__ import annotations

DOCUMENTATION = r"""
module: packet_volume

short_description: Create/delete a volume in Packet host

description:
  - Create/delete a volume in Packet host.
  - API is documented at U(https://www.packet.com/developers/api/#volumes).
version_added: '0.2.0'

author:
  - Tomas Karasek (@t0mk) <tom.to.the.k@gmail.com>
  - Nurfet Becirevic (@nurfet-becirevic) <nurfet.becirevic@gmail.com>

extends_documentation_fragment:
  - community.general._attributes

attributes:
  check_mode:
    support: full
  diff_mode:
    support: none

options:
  state:
    description:
      - Desired state of the volume.
    default: present
    choices: ['present', 'absent']
    type: str

  project_id:
    description:
      - ID of project of the device.
    required: true
    type: str

  auth_token:
    description:
      - Packet API token. You can also supply it in environment variable E(PACKET_API_TOKEN).
    type: str

  name:
    description:
      - Selector for API-generated name of the volume.
    type: str

  description:
    description:
      - User-defined description attribute for Packet volume.
      - It is used used as idempotent identifier - if volume with given description exists, new one is not created.
    type: str

  id:
    description:
      - UUID of a volume.
    type: str

  plan:
    description:
      - V(storage_1) for standard tier, V(storage_2) for premium (performance) tier.
      - Tiers are described at U(https://www.packet.com/cloud/storage/).
    choices: ['storage_1', 'storage_2']
    default: 'storage_1'
    type: str

  facility:
    description:
      - Location of the volume.
      - Volumes can only be attached to device in the same location.
    type: str

  size:
    description:
      - Size of the volume in gigabytes.
    type: int

  locked:
    description:
      - Create new volume locked.
    type: bool
    default: false

  billing_cycle:
    description:
      - Billing cycle for new volume.
    choices: ['hourly', 'monthly']
    default: 'hourly'
    type: str

  snapshot_policy:
    description:
      - Snapshot policy for new volume.
    type: dict

    suboptions:
      snapshot_count:
        description:
          - How many snapshots to keep, a positive integer.
        required: true
        type: int

      snapshot_frequency:
        description:
          - Frequency of snapshots.
        required: true
        choices: ["15min", "1hour", "1day", "1week", "1month", "1year"]
        type: str

requirements:
  - "packet-python >= 1.35"
"""

EXAMPLES = r"""
# All the examples assume that you have your Packet API token in env var PACKET_API_TOKEN.
# You can also pass the api token in module param auth_token.

- hosts: localhost
  vars:
    volname: testvol123
    project_id: 53000fb2-ee46-4673-93a8-de2c2bdba33b

  tasks:
    - name: Create volume
      community.general.packet_volume:
        description: "{{ volname }}"
        project_id: "{{ project_id }}"
        facility: 'ewr1'
        plan: 'storage_1'
        state: present
        size: 10
        snapshot_policy:
          snapshot_count: 10
          snapshot_frequency: 1day
      register: result_create

    - name: Delete volume
      community.general.packet_volume:
        id: "{{ result_create.id }}"
        project_id: "{{ project_id }}"
        state: absent
"""

RETURN = r"""
id:
  description: UUID of specified volume.
  type: str
  returned: success
  sample: 53000fb2-ee46-4673-93a8-de2c2bdba33c
name:
  description: The API-generated name of the volume resource.
  type: str
  returned: if volume is attached/detached to/from some device
  sample: "volume-a91dc506"
description:
  description: The user-defined description of the volume resource.
  type: str
  returned: success
  sample: "Just another volume"
"""

import uuid

from ansible.module_utils.basic import AnsibleModule, env_fallback

HAS_PACKET_SDK = True


try:
    import packet
except ImportError:
    HAS_PACKET_SDK = False


PACKET_API_TOKEN_ENV_VAR = "PACKET_API_TOKEN"

VOLUME_PLANS = ["storage_1", "storage_2"]
VOLUME_STATES = ["present", "absent"]
BILLING = ["hourly", "monthly"]


def is_valid_uuid(myuuid):
    try:
        val = uuid.UUID(myuuid, version=4)
    except ValueError:
        return False
    return str(val) == myuuid


def get_volume_selector(module):
    if module.params.get("id"):
        i = module.params.get("id")
        if not is_valid_uuid(i):
            raise Exception(f"Volume ID '{i}' is not a valid UUID")
        return lambda v: v["id"] == i
    elif module.params.get("name"):
        n = module.params.get("name")
        return lambda v: v["name"] == n
    elif module.params.get("description"):
        d = module.params.get("description")
        return lambda v: v["description"] == d


def get_or_fail(params, key):
    item = params.get(key)
    if item is None:
        raise Exception(f"{key} must be specified for new volume")
    return item


def act_on_volume(target_state, module, packet_conn):
    return_dict = {"changed": False}
    s = get_volume_selector(module)
    project_id = module.params.get("project_id")
    api_method = f"projects/{project_id}/storage"
    all_volumes = packet_conn.call_api(api_method, "GET")["volumes"]
    matching_volumes = [v for v in all_volumes if s(v)]

    if target_state == "present":
        if len(matching_volumes) == 0:
            params = {
                "description": get_or_fail(module.params, "description"),
                "size": get_or_fail(module.params, "size"),
                "plan": get_or_fail(module.params, "plan"),
                "facility": get_or_fail(module.params, "facility"),
                "locked": get_or_fail(module.params, "locked"),
                "billing_cycle": get_or_fail(module.params, "billing_cycle"),
                "snapshot_policies": module.params.get("snapshot_policy"),
            }

            new_volume_data = packet_conn.call_api(api_method, "POST", params)
            return_dict["changed"] = True
            for k in ["id", "name", "description"]:
                return_dict[k] = new_volume_data[k]

        else:
            for k in ["id", "name", "description"]:
                return_dict[k] = matching_volumes[0][k]

    else:
        if len(matching_volumes) > 1:
            _msg = f"More than one volume matches in module call for absent state: {matching_volumes}"
            module.fail_json(msg=_msg)

        if len(matching_volumes) == 1:
            volume = matching_volumes[0]
            packet_conn.call_api(f"storage/{volume['id']}", "DELETE")
            return_dict["changed"] = True
            for k in ["id", "name", "description"]:
                return_dict[k] = volume[k]

    return return_dict


def main():
    module = AnsibleModule(
        argument_spec=dict(
            id=dict(type="str"),
            description=dict(type="str"),
            name=dict(type="str"),
            state=dict(choices=VOLUME_STATES, default="present"),
            auth_token=dict(type="str", fallback=(env_fallback, [PACKET_API_TOKEN_ENV_VAR]), no_log=True),
            project_id=dict(required=True),
            plan=dict(choices=VOLUME_PLANS, default="storage_1"),
            facility=dict(type="str"),
            size=dict(type="int"),
            locked=dict(type="bool", default=False),
            snapshot_policy=dict(type="dict"),
            billing_cycle=dict(type="str", choices=BILLING, default="hourly"),
        ),
        supports_check_mode=True,
        required_one_of=[("name", "id", "description")],
        mutually_exclusive=[
            ("name", "id"),
            ("id", "description"),
            ("name", "description"),
        ],
    )

    if not HAS_PACKET_SDK:
        module.fail_json(msg="packet required for this module")

    if not module.params.get("auth_token"):
        _fail_msg = f"if Packet API token is not in environment variable {PACKET_API_TOKEN_ENV_VAR}, the auth_token parameter is required"
        module.fail_json(msg=_fail_msg)

    auth_token = module.params.get("auth_token")

    packet_conn = packet.Manager(auth_token=auth_token)

    state = module.params.get("state")

    if state in VOLUME_STATES:
        if module.check_mode:
            module.exit_json(changed=False)

        try:
            module.exit_json(**act_on_volume(state, module, packet_conn))
        except Exception as e:
            module.fail_json(msg=f"failed to set volume state {state}: {e}")
    else:
        module.fail_json(msg=f"{state} is not a valid state for this module")


if __name__ == "__main__":
    main()
